diff --git a/src/shared/request/apiRequest.ts b/src/shared/request/apiRequest.ts index 2e55df5..129da6e 100644 --- a/src/shared/request/apiRequest.ts +++ b/src/shared/request/apiRequest.ts @@ -9,7 +9,7 @@ import { getRouteLang } from './getLanguage'; // ─── Constants ───────────────────────────────────────────────────────────────── // const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; -const baseUrl = 'https://api.anti-plagiat.uz/api/v1'; +const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1'; const DEFAULT_LOCALE = 'uz'; // fallback locale for redirect // ─── Token helpers ───────────────────────────────────────────────────────────── diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts index f9a684e..921667d 100644 --- a/src/shared/request/links.ts +++ b/src/shared/request/links.ts @@ -7,4 +7,8 @@ export const links = { payment: (order_id: number) => `/users/payme/link/${order_id}/`, sertifikat: (document_id: number) => `/shared/certificate/${document_id}/pdf/`, + si: '/shared/ai_document/list/', + si_id: (si_id: number) => `/shared/ai_document/list/${si_id}/`, + si_payment: (document_id: number) => `/shared/ai_document/pay/${document_id}`, + si_create: '/shared/ai_document/create/', }; diff --git a/src/widgets/cabinet/lib/hooks/useSiHistory.ts b/src/widgets/cabinet/lib/hooks/useSiHistory.ts new file mode 100644 index 0000000..9ce2dcb --- /dev/null +++ b/src/widgets/cabinet/lib/hooks/useSiHistory.ts @@ -0,0 +1,20 @@ +'use client'; +import { useQuery } from '@tanstack/react-query'; +import { apiRequest } from '@/shared/request/apiRequest'; +import { links } from '@/shared/request/links'; +import type { SiDocument } from '../types'; + +export const useSiHistory = () => { + const { data, isLoading, isError, refetch } = useQuery({ + queryKey: ['si-history'], + queryFn: () => apiRequest('GET', links.si), + select: (res) => res.data, + }); + + return { + items: data ?? [], + isLoading, + isError, + refetch, + }; +}; diff --git a/src/widgets/cabinet/lib/types.ts b/src/widgets/cabinet/lib/types.ts index 13f8b5b..f69beaa 100644 --- a/src/widgets/cabinet/lib/types.ts +++ b/src/widgets/cabinet/lib/types.ts @@ -45,6 +45,21 @@ export interface Payment { status: 'paid' | 'pending' | 'failed'; } +// ─── SI Document (API) ───────────────────────────────────────────────────────── + +export interface SiDocument { + id: number; + title: string; + file: string; + created_at: string; + updated_at: string; + result: null | unknown; + state: 'paid' | 'unpaid'; + ai_order_id: string; + total_words: number; + si_percantage: number | null; +} + export interface CabinetStats { total: number; thisMonth: number; diff --git a/src/widgets/cabinet/ui/index.tsx b/src/widgets/cabinet/ui/index.tsx index ff62ab3..1d51bd2 100644 --- a/src/widgets/cabinet/ui/index.tsx +++ b/src/widgets/cabinet/ui/index.tsx @@ -6,7 +6,7 @@ import { Sidebar } from './Sidebar'; import { CabinetNav } from './CabinetNav'; import { Dashboard } from './dashboard'; import { useCabinet } from '../lib/hooks/useCabinet'; -import { MOCK_USER, MOCK_STATS, MOCK_SI, MOCK_PAYMENTS } from '../lib/mock'; +import { MOCK_USER, MOCK_STATS, MOCK_PAYMENTS } from '../lib/mock'; import type { CabinetSection } from '../lib/types'; // ─── Lazy sections (separate JS chunks) ─────────────────────────────────────── @@ -49,7 +49,7 @@ function SectionContent({ section }: { section: CabinetSection }) { case 'plagiat': return ; case 'si': - return ; + return ; case 'payments': return ; case 'profile': diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index 0f5e840..94b7b07 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -1,22 +1,37 @@ +'use client'; import React from 'react'; -import { FileText, Clock, CheckCircle, XCircle } from 'lucide-react'; -import type { SiCheck } from '../../lib/types'; +import { Download, CreditCard } from 'lucide-react'; +import { useMutation } from '@tanstack/react-query'; +import { useSiHistory } from '../../lib/hooks/useSiHistory'; +import { formatDate } from '@/widgets/history/lib/utils'; +import { apiRequest } from '@/shared/request/apiRequest'; +import { links } from '@/shared/request/links'; +import { toast } from 'react-toastify'; +import type { SiDocument } from '../../lib/types'; -// ─── Helpers ─────────────────────────────────────────────────────────────────── +// ─── State badge ─────────────────────────────────────────────────────────────── -const STATUS_MAP = { - completed: { - label: 'Yakunlandi', - icon: CheckCircle, - cls: 'text-emerald-600 bg-emerald-50', - }, - pending: { - label: 'Kutilmoqda', - icon: Clock, - cls: 'text-amber-600 bg-amber-50', - }, - failed: { label: 'Xato', icon: XCircle, cls: 'text-red-600 bg-red-50' }, -} as const; +const StateBadge: React.FC<{ state: 'paid' | 'unpaid' }> = ({ state }) => { + const isPaid = state === 'paid'; + return ( + + + {isPaid ? "To'langan" : "To'lanmagan"} + + ); +}; + +// ─── SI% badge ───────────────────────────────────────────────────────────────── const SiPercentBadge: React.FC<{ value: number }> = ({ value }) => { const cls = @@ -34,95 +49,187 @@ const SiPercentBadge: React.FC<{ value: number }> = ({ value }) => { ); }; -// ─── Component ───────────────────────────────────────────────────────────────── +// ─── Skeleton ────────────────────────────────────────────────────────────────── -interface SiTableProps { - data: SiCheck[]; -} +const SkeletonRow = () => ( + + {Array.from({ length: 7 }).map((_, i) => ( + +
+ + ))} + +); -export const SiTable: React.FC = ({ data }) => ( -
-
-

SI detektor

-

- {data.length} ta tekshiruv -

-
+// ─── Empty / Error states ────────────────────────────────────────────────────── -
-
- - - - {['#', 'Fayl', "So'z", 'SI%', 'Sana', 'Holat', 'Hisobot'].map( - (h) => ( +const EmptyState = () => ( + + + +); + +const ErrorState = () => ( + + + +); + +// ─── Row ─────────────────────────────────────────────────────────────────────── + +const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ + item, + index, +}) => { + const pay = useMutation({ + mutationKey: ['si-payment', item.id], + mutationFn: () => + apiRequest<{ payment_link: string }>('POST', links.si_payment(item.id)), + onSuccess: (res) => { + window.open(res.data.payment_link, '_self'); + }, + onError: (err) => { + toast.error(err instanceof Error ? err.message : 'Xatolik yuz berdi'); + }, + }); + + return ( + + {/* # */} + + + {/* Sarlavha */} + + + {/* Fayl */} + + + {/* So'z */} + + + {/* SI% */} + + + {/* Sana */} + + + {/* Holat */} + + + {/* Amal */} + + + ); +}; + +// ─── SiTable ─────────────────────────────────────────────────────────────────── + +export const SiTable: React.FC = () => { + const { items, isLoading, isError } = useSiHistory(); + + return ( +
+
+
+

SI detektor

+

+ {isLoading ? '...' : `${items.length} ta tekshiruv`} +

+
+
+ +
+
+
+ Hozircha SI tekshiruvlar yo'q +
+ Ma'lumotlarni yuklashda xatolik yuz berdi +
+ {String(index).padStart(2, '0')} + + + {item.title || '—'} + + + {item.file ? ( + + + Fayl + + ) : ( + + )} + + {item.total_words > 0 ? item.total_words.toLocaleString() : '—'} + + {item.si_percantage != null && item.result != null ? ( + + ) : ( + + )} + + {formatDate(item.created_at)} + + + + {item.state === 'unpaid' ? ( + + ) : ( + + )} +
+ + + {[ + '#', + 'Sarlavha', + 'Fayl', + "So'z", + 'SI%', + 'Sana', + 'Holat', + 'Amal', + ].map((h) => ( - ), - )} - - - - {data.map((row) => { - const s = STATUS_MAP[row.status]; - const Icon = s.icon; - return ( - - - - - - - - - - ); - })} - -
{h}
- {String(row.id).padStart(2, '0')} - - - {row.file} - - - {row.words.toLocaleString()} - - {row.status === 'pending' ? ( - - ) : ( - - )} - - {row.date} - - - - {s.label} - - - {row.reportUrl ? ( - - - - ) : ( - - )} -
+ ))} + + + + {isLoading && + Array.from({ length: 5 }).map((_, i) => ( + + ))} + {isError && } + {!isLoading && !isError && items.length === 0 && } + {!isLoading && + !isError && + items.map((item, i) => ( + + ))} + + +
-
-); + ); +}; diff --git a/src/widgets/history/ui/historyPage.tsx b/src/widgets/history/ui/historyPage.tsx index 6423a44..b486613 100644 --- a/src/widgets/history/ui/historyPage.tsx +++ b/src/widgets/history/ui/historyPage.tsx @@ -4,18 +4,35 @@ import { useTranslations } from 'next-intl'; import { useHistory } from '../lib/useHistory'; import { HistoryTable } from './historyTable'; import { Pagination } from './pagination'; +import Link from 'next/link'; +import { Plus } from 'lucide-react'; +import { usePathname } from '@/shared/config/i18n/navigation'; // ─── Page Header ─────────────────────────────────────────────────────────────── const PageHeader: React.FC = () => { const t = useTranslations('HistoryPage'); - + const pathname = usePathname(); return ( -
-

- {t('title')} -

-

{t('description')}

+
+
+

+ {t('title')} +

+

{t('description')}

+
+ + +

+ Plagiat tekshiruvi +

+
); };