111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
'use client';
|
|
import React from 'react';
|
|
import { TrendingUp, Calendar, Wallet, Loader2 } from 'lucide-react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useTranslations } from 'next-intl';
|
|
import { apiRequest } from '@/shared/request/apiRequest';
|
|
import { links } from '@/shared/request/links';
|
|
|
|
// ─── Types ─────────────────────────────────────────────────────────────────────
|
|
|
|
type Stats = {
|
|
total_documents: number;
|
|
this_month_documents: number;
|
|
paid_price: number;
|
|
};
|
|
|
|
// ─── Single stat card ──────────────────────────────────────────────────────────
|
|
|
|
interface StatCardProps {
|
|
icon: React.ElementType;
|
|
label: string;
|
|
value: string;
|
|
sub?: string;
|
|
iconColor: string;
|
|
iconBg: string;
|
|
}
|
|
|
|
const StatCard: React.FC<StatCardProps> = ({
|
|
icon: Icon,
|
|
label,
|
|
value,
|
|
sub,
|
|
iconColor,
|
|
iconBg,
|
|
}) => (
|
|
<div className="bg-white rounded-2xl border border-slate-100 p-5 shadow-sm hover:shadow-md transition-shadow duration-200">
|
|
<div
|
|
className={`w-10 h-10 rounded-xl flex items-center justify-center mb-4 ${iconBg}`}
|
|
>
|
|
<Icon size={18} className={iconColor} />
|
|
</div>
|
|
<p className="text-2xl font-bold text-slate-900 tabular-nums">{value}</p>
|
|
<p className="text-xs text-slate-500 mt-0.5">{label}</p>
|
|
{sub && <p className="text-[11px] text-slate-400 mt-1">{sub}</p>}
|
|
</div>
|
|
);
|
|
|
|
const StatCardSkeleton = () => (
|
|
<div className="bg-white rounded-2xl border border-slate-100 p-5 shadow-sm animate-pulse">
|
|
<div className="w-10 h-10 rounded-xl bg-slate-100 mb-4" />
|
|
<div className="h-7 w-16 bg-slate-100 rounded mb-2" />
|
|
<div className="h-3 w-24 bg-slate-100 rounded" />
|
|
</div>
|
|
);
|
|
|
|
// ─── Grid ──────────────────────────────────────────────────────────────────────
|
|
|
|
export const StatsCards = () => {
|
|
const t = useTranslations('Cabinet');
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['statistics'],
|
|
queryFn: (): Promise<Stats> =>
|
|
apiRequest('GET', links.statistics).then((res) => res.data as Stats),
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<StatCardSkeleton />
|
|
<StatCardSkeleton />
|
|
<StatCardSkeleton />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!data) {
|
|
return (
|
|
<div className="flex items-center justify-center py-10 gap-2 text-slate-400">
|
|
<Loader2 size={18} className="animate-spin" />
|
|
<span className="text-sm">{t('noData')}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<StatCard
|
|
icon={TrendingUp}
|
|
label={t('totalChecks')}
|
|
value={String(data.total_documents)}
|
|
iconColor="text-blue-600"
|
|
iconBg="bg-blue-50"
|
|
/>
|
|
<StatCard
|
|
icon={Calendar}
|
|
label={t('thisMonth')}
|
|
value={String(data.this_month_documents)}
|
|
iconColor="text-emerald-600"
|
|
iconBg="bg-emerald-50"
|
|
/>
|
|
<StatCard
|
|
icon={Wallet}
|
|
label={t('paidAmount')}
|
|
value={`${data.paid_price.toLocaleString('uz-UZ')} UZS`}
|
|
iconColor="text-violet-600"
|
|
iconBg="bg-violet-50"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|