dahsboard connected to backend
This commit is contained in:
@@ -13,4 +13,5 @@ export const links = {
|
|||||||
si_create: '/shared/ai_document/create/',
|
si_create: '/shared/ai_document/create/',
|
||||||
document_types: '/shared/document_types/',
|
document_types: '/shared/document_types/',
|
||||||
pay_history: '/shared/orders/all/',
|
pay_history: '/shared/orders/all/',
|
||||||
|
statistics: '/shared/statistics/',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TrendingUp, Calendar, Tag, Wallet } from 'lucide-react';
|
import { TrendingUp, Calendar, Wallet, Loader2 } from 'lucide-react';
|
||||||
import type { CabinetStats } from '../../lib/types';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
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 ──────────────────────────────────────────────────────────
|
// ─── Single stat card ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -33,46 +43,62 @@ const StatCard: React.FC<StatCardProps> = ({
|
|||||||
</div>
|
</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 ──────────────────────────────────────────────────────────────────────
|
// ─── Grid ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
interface StatsCardsProps {
|
export const StatsCards = () => {
|
||||||
stats: CabinetStats;
|
const { data, isLoading } = useQuery({
|
||||||
}
|
queryKey: ['statistics'],
|
||||||
|
queryFn: (): Promise<Stats> =>
|
||||||
|
apiRequest('GET', links.statistics).then((res) => res.data as Stats),
|
||||||
|
});
|
||||||
|
|
||||||
export const StatsCards: React.FC<StatsCardsProps> = ({ stats }) => {
|
if (isLoading) {
|
||||||
const discountPct = Math.round(
|
return (
|
||||||
(stats.discountUsed / stats.discountTotal) * 100,
|
<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">Ma'lumot topilmadi</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={TrendingUp}
|
icon={TrendingUp}
|
||||||
label="Jami tekshiruvlar"
|
label="Jami tekshiruvlar"
|
||||||
value={String(stats.total)}
|
value={String(data.total_documents)}
|
||||||
iconColor="text-blue-600"
|
iconColor="text-blue-600"
|
||||||
iconBg="bg-blue-50"
|
iconBg="bg-blue-50"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={Calendar}
|
icon={Calendar}
|
||||||
label="Bu oy"
|
label="Bu oy"
|
||||||
value={String(stats.thisMonth)}
|
value={String(data.this_month_documents)}
|
||||||
sub={`${stats.discountUsed}/${stats.discountTotal} ta hujjat`}
|
|
||||||
iconColor="text-emerald-600"
|
iconColor="text-emerald-600"
|
||||||
iconBg="bg-emerald-50"
|
iconBg="bg-emerald-50"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
|
||||||
icon={Tag}
|
|
||||||
label="Chegirma holati"
|
|
||||||
value={`${discountPct}%`}
|
|
||||||
sub={`${stats.discountUsed}/${stats.discountTotal} ta ishlatilgan`}
|
|
||||||
iconColor="text-amber-600"
|
|
||||||
iconBg="bg-amber-50"
|
|
||||||
/>
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={Wallet}
|
icon={Wallet}
|
||||||
label="Balans"
|
label="To'langan summa"
|
||||||
value={`${stats.balance.toLocaleString()} ${stats.currency}`}
|
value={`${data.paid_price.toLocaleString('uz-UZ')} UZS`}
|
||||||
iconColor="text-violet-600"
|
iconColor="text-violet-600"
|
||||||
iconBg="bg-violet-50"
|
iconBg="bg-violet-50"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import React from 'react';
|
|||||||
import { CtaCards } from './CtaCards';
|
import { CtaCards } from './CtaCards';
|
||||||
import { StatsCards } from './StatsCards';
|
import { StatsCards } from './StatsCards';
|
||||||
import { ModulesSection } from './ModulesSection';
|
import { ModulesSection } from './ModulesSection';
|
||||||
import type { CabinetStats } from '../../lib/types';
|
|
||||||
|
|
||||||
interface DashboardProps {
|
interface DashboardProps {
|
||||||
stats: CabinetStats;
|
|
||||||
userName: string;
|
userName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Dashboard: React.FC<DashboardProps> = ({ stats, userName }) => (
|
export const Dashboard: React.FC<DashboardProps> = ({ userName }) => (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-slate-900">
|
<h2 className="text-xl font-bold text-slate-900">
|
||||||
@@ -20,7 +18,7 @@ export const Dashboard: React.FC<DashboardProps> = ({ stats, userName }) => (
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatsCards stats={stats} />
|
<StatsCards />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-widest mb-3">
|
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-widest mb-3">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const ProfileSection = dynamic(
|
|||||||
function SectionContent({ section }: { section: CabinetSection }) {
|
function SectionContent({ section }: { section: CabinetSection }) {
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case 'dashboard':
|
case 'dashboard':
|
||||||
return <Dashboard stats={MOCK_STATS} userName={MOCK_USER.name} />;
|
return <Dashboard userName={MOCK_USER.name} />;
|
||||||
case 'plagiat':
|
case 'plagiat':
|
||||||
return <PlagiatTable />;
|
return <PlagiatTable />;
|
||||||
case 'si':
|
case 'si':
|
||||||
|
|||||||
Reference in New Issue
Block a user