history compolated

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-02 15:57:28 +05:00
parent dc653652c7
commit 10cf895262
11 changed files with 134 additions and 157 deletions

View File

@@ -208,5 +208,7 @@
"total": "Total",
"connecting": "Connecting to Payme…",
"payButton": "Pay with Payme"
}
},
"unknownUser": "Username not found",
"file": "File"
}

View File

@@ -208,5 +208,7 @@
"total": "Итого",
"connecting": "Подключение к Payme…",
"payButton": "Оплатить через Payme"
}
},
"unknownUser": "Имя пользователя не найдено",
"file":"Файл"
}

View File

@@ -212,5 +212,7 @@ declare const messages: {
connecting: 'Paymega ulanmoqda…';
payButton: "Payme orqali to'lash";
};
unknownUser: 'Foydalanuvchi topilmadi';
file: 'Fayl';
};
export default messages;

View File

@@ -208,5 +208,7 @@
"total": "Jami",
"connecting": "Paymega ulanmoqda…",
"payButton": "Payme orqali to'lash"
}
},
"unknownUser":"Foydalanuvchi topilmadi",
"file":"Fayl"
}

View File

@@ -6,7 +6,6 @@ export const TABLE_COLUMNS = [
{ key: 'senderFullName', labelKey: 'sender' },
{ key: 'fileName', labelKey: 'file' },
{ key: 'date', labelKey: 'date' },
{ key: 'paymentAmount', labelKey: 'amount' },
{ key: 'result', labelKey: 'result' },
{ key: 'actions', labelKey: 'actions' },
] as const;
@@ -38,10 +37,3 @@ export const RESULT_CONFIG: Record<
// ─── Pagination ────────────────────────────────────────────────────────────────
export const DEFAULT_PAGE_SIZE = 10;
// ─── API ───────────────────────────────────────────────────────────────────────
export const API_ENDPOINTS = {
HISTORY: '/api/plagiarism/history',
DETAIL: (id: string) => `/api/plagiarism/${id}`,
} as const;

View File

@@ -1,71 +1,69 @@
import { PlagiarismCheck } from './types';
/**
* Default mock items used to preview the history table design.
* Replace with real API data in production via useHistory() hook.
*/
export const DEFAULT_HISTORY_ITEMS: PlagiarismCheck[] = [
{
id: '1',
senderFullName: 'Alijon Toshmatov',
fileName: 'thesis-final-v3.pdf',
date: '2024-03-15T10:30:00Z',
paymentAmount: 45000,
currency: 'UZS',
result: 'clean',
},
{
id: '2',
senderFullName: 'Malika Yusupova',
fileName: 'research-paper-2024.docx',
date: '2024-03-14T09:15:00Z',
paymentAmount: 60000,
currency: 'UZS',
result: 'plagiarism_found',
},
{
id: '3',
senderFullName: 'Bobur Rahimov',
fileName: 'coursework-economics.pdf',
date: '2024-03-13T14:45:00Z',
paymentAmount: 45000,
currency: 'UZS',
result: 'pending',
},
{
id: '4',
senderFullName: 'Zulfiya Nazarova',
fileName: 'dissertation-chapter1.pdf',
date: '2024-03-12T11:20:00Z',
paymentAmount: 60000,
currency: 'UZS',
result: 'clean',
},
{
id: '5',
senderFullName: 'Jasur Mirzayev',
fileName: 'lab-report-chemistry.docx',
date: '2024-03-11T16:10:00Z',
paymentAmount: 45000,
currency: 'UZS',
result: 'failed',
},
{
id: '6',
senderFullName: 'Nilufar Karimova',
fileName: 'bachelor-thesis-law.pdf',
date: '2024-03-10T08:55:00Z',
paymentAmount: 60000,
currency: 'UZS',
result: 'plagiarism_found',
},
{
id: '7',
senderFullName: 'Dilnoza Ergasheva',
fileName: 'essay-history-uzbekistan.pdf',
date: '2024-03-09T13:40:00Z',
paymentAmount: 45000,
currency: 'UZS',
result: 'clean',
},
];
// export const DEFAULT_HISTORY_ITEMS: PlagiarismCheck[] = [
// {
// id: '1',
// senderFullName: 'Alijon Toshmatov',
// fileName: 'thesis-final-v3.pdf',
// date: '2024-03-15T10:30:00Z',
// paymentAmount: 45000,
// currency: 'UZS',
// result: 'clean',
// },
// {
// id: '2',
// senderFullName: 'Malika Yusupova',
// fileName: 'research-paper-2024.docx',
// date: '2024-03-14T09:15:00Z',
// paymentAmount: 60000,
// currency: 'UZS',
// result: 'plagiarism_found',
// },
// {
// id: '3',
// senderFullName: 'Bobur Rahimov',
// fileName: 'coursework-economics.pdf',
// date: '2024-03-13T14:45:00Z',
// paymentAmount: 45000,
// currency: 'UZS',
// result: 'pending',
// },
// {
// id: '4',
// senderFullName: 'Zulfiya Nazarova',
// fileName: 'dissertation-chapter1.pdf',
// date: '2024-03-12T11:20:00Z',
// paymentAmount: 60000,
// currency: 'UZS',
// result: 'clean',
// },
// {
// id: '5',
// senderFullName: 'Jasur Mirzayev',
// fileName: 'lab-report-chemistry.docx',
// date: '2024-03-11T16:10:00Z',
// paymentAmount: 45000,
// currency: 'UZS',
// result: 'failed',
// },
// {
// id: '6',
// senderFullName: 'Nilufar Karimova',
// fileName: 'bachelor-thesis-law.pdf',
// date: '2024-03-10T08:55:00Z',
// paymentAmount: 60000,
// currency: 'UZS',
// result: 'plagiarism_found',
// },
// {
// id: '7',
// senderFullName: 'Dilnoza Ergasheva',
// fileName: 'essay-history-uzbekistan.pdf',
// date: '2024-03-09T13:40:00Z',
// paymentAmount: 45000,
// currency: 'UZS',
// result: 'clean',
// },
// ];

View File

@@ -2,17 +2,18 @@
export type CheckResult = 'clean' | 'plagiarism_found' | 'pending' | 'failed';
export interface PlagiarismCheck {
id: string;
senderFullName: string;
fileName: string;
date: string; // ISO 8601
paymentAmount: number;
currency: string;
result: CheckResult;
export interface DocumentData {
id: number;
title: string;
text: string;
file: string;
certificate: boolean;
created_at: string;
updated_at: string;
results: [];
}
export interface PlagiarismCheckDetail extends PlagiarismCheck {
export interface PlagiarismCheckDetail extends DocumentData {
topic: string;
plagiarismPercentage?: number;
reportUrl?: string;
@@ -22,7 +23,7 @@ export interface PlagiarismCheckDetail extends PlagiarismCheck {
// ─── API Response Types ────────────────────────────────────────────────────────
export interface HistoryApiResponse {
items: PlagiarismCheck[];
items: DocumentData[];
total: number;
page: number;
pageSize: number;
@@ -31,12 +32,12 @@ export interface HistoryApiResponse {
// ─── Component Props ───────────────────────────────────────────────────────────
export interface HistoryTableProps {
items: PlagiarismCheck[];
items: DocumentData[];
isLoading: boolean;
}
export interface HistoryTableRowProps {
item: PlagiarismCheck;
item: DocumentData;
}
export interface ResultBadgeProps {
@@ -56,7 +57,7 @@ export interface SkeletonRowProps {
export type FetchStatus = 'idle' | 'loading' | 'success' | 'error';
export interface HistoryState {
items: PlagiarismCheck[];
items: DocumentData[];
status: FetchStatus;
error: string | null;
}

View File

@@ -1,11 +1,11 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { DEFAULT_PAGE_SIZE } from './constants';
import { HistoryState } from './types';
import { DEFAULT_HISTORY_ITEMS } from './mock';
import { DocumentData, HistoryState } from './types';
import { useQuery } from '@tanstack/react-query';
import { links } from '@/shared/request/links';
import { apiRequest } from '@/shared/request/apiRequest';
import { useUserPlagiatStore } from '@/shared/zustand/user';
interface UseHistoryReturn extends HistoryState {
refetch: () => void;
@@ -16,22 +16,21 @@ interface UseHistoryReturn extends HistoryState {
export const useHistory = (pageSize = DEFAULT_PAGE_SIZE): UseHistoryReturn => {
const [state, setState] = useState<HistoryState>({
items: DEFAULT_HISTORY_ITEMS,
items: [],
status: 'idle',
error: null,
});
const [currentPage, setCurrentPage] = useState(1);
const [total, setTotal] = useState(0);
const user = useUserPlagiatStore((state) => state.user);
const { data, refetch } = useQuery({
queryKey: ['history'],
queryFn: () => apiRequest('GET', links.history),
select: (response) => {
const { results, total } = response.data as {
results: [];
total: number;
};
return { results, total };
console.log(response);
const results = response?.data as DocumentData[];
return { results, total: results.length };
},
});
@@ -42,13 +41,13 @@ export const useHistory = (pageSize = DEFAULT_PAGE_SIZE): UseHistoryReturn => {
status: 'success',
error: null,
});
setTotal(data?.total || 0);
setTotal(data.total || 0);
}
}, [data]);
useEffect(() => {
refetch();
}, [currentPage]);
}, [currentPage, user]);
const goToPage = useCallback((page: number) => {
setCurrentPage(page);

View File

@@ -1,43 +1,3 @@
// ─── API Functions ─────────────────────────────────────────────────────────────
import { API_ENDPOINTS } from './constants';
import { HistoryApiResponse, PlagiarismCheckDetail } from './types';
/**
* Fetches the paginated list of plagiarism checks for the current user.
*/
export const fetchPlagiarismHistory = async (
page = 1,
pageSize = 10,
): Promise<HistoryApiResponse> => {
const url = new URL(API_ENDPOINTS.HISTORY, window.location.origin);
url.searchParams.set('page', String(page));
url.searchParams.set('pageSize', String(pageSize));
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`Failed to load history (${response.status})`);
}
return response.json() as Promise<HistoryApiResponse>;
};
/**
* Fetches the full detail for a single plagiarism check.
*/
export const fetchPlagiarismDetail = async (
id: string,
): Promise<PlagiarismCheckDetail> => {
const response = await fetch(API_ENDPOINTS.DETAIL(id));
if (!response.ok) {
throw new Error(`Failed to load record (${response.status})`);
}
return response.json() as Promise<PlagiarismCheckDetail>;
};
// ─── Formatting Utilities ──────────────────────────────────────────────────────
/**

View File

@@ -2,26 +2,37 @@
import React from 'react';
import { useTranslations } from 'next-intl';
import { HistoryTableRowProps } from '../lib/types';
import { formatDate, truncateFileName } from '../lib/utils';
import { formatDate } from '../lib/utils';
import { ResultBadge } from './resultBadge';
import { useRouter } from '@/shared/config/i18n/navigation';
import { useUserPlagiatStore } from '@/shared/zustand/user';
export const HistoryTableRow: React.FC<HistoryTableRowProps> = ({ item }) => {
const router = useRouter();
const t = useTranslations('HistoryPage');
const tUnknown = useTranslations();
const user = useUserPlagiatStore((state) => state.user);
const userName = user
? `${user.name} ${user.surname}`
: tUnknown('unknownUser');
return (
<tr className="border-b border-slate-100 hover:bg-slate-50/70 transition-colors duration-100 group">
{/* Sender */}
<td className="px-4 py-3.5">
<span className="text-sm font-medium text-slate-800 whitespace-nowrap">
{item.senderFullName}
{userName}
</span>
</td>
{/* File Name */}
<td className="px-4 py-3.5">
<div className="flex items-center gap-2">
<a
href={item.file}
target="_blank"
className="flex items-center gap-2 underline"
>
<svg
width="14"
height="14"
@@ -33,39 +44,36 @@ export const HistoryTableRow: React.FC<HistoryTableRowProps> = ({ item }) => {
>
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span
className="text-sm text-slate-600 font-mono"
title={item.fileName}
>
{truncateFileName(item.fileName)}
<span className="text-sm text-slate-600 font-mono" title={item.file}>
{tUnknown('file')}
</span>
</div>
</a>
</td>
{/* Date */}
<td className="px-4 py-3.5">
<span className="text-sm text-slate-500 whitespace-nowrap">
{formatDate(item.date)}
{formatDate(item.created_at)}
</span>
</td>
{/* Amount */}
<td className="px-4 py-3.5">
{/* <td className="px-4 py-3.5">
<span className="text-sm font-medium text-slate-700 whitespace-nowrap tabular-nums">
{item.paymentAmount}
{item.} UZS
</span>
</td>
</td> */}
{/* Result */}
<td className="px-4 py-3.5">
<ResultBadge result={item.result} />
<ResultBadge result={'clean'} />
</td>
{/* View Button */}
<td className="px-4 py-3.5 text-right">
<button
onClick={() => router.push(`/${item.id}`)}
aria-label={t('viewDetails', { sender: item.senderFullName })}
aria-label={t('viewDetails', { sender: item.title })}
className="
inline-flex items-center gap-1.5 px-3 py-1.5
text-xs font-medium text-slate-600

View File

@@ -13,6 +13,7 @@ import { ChangeLang } from './ChangeLang';
import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth';
import { useTranslations } from 'next-intl';
import { useUserPlagiatStore } from '@/shared/zustand/user';
import { LogOut } from 'lucide-react';
function AuthButtons() {
const t = useTranslations('Navbar');
@@ -22,13 +23,19 @@ function AuthButtons() {
signup: { title: t('signup'), url: '#' },
};
const userItem = [{ title: t('logout'), url: '#' }];
const userItem = [{ title: t('logout'), url: '/', icon: LogOut }];
const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
const toggleRegisterModal = useRegisterModal(
(state) => state.toggleRegisterModal,
);
const user = useUserPlagiatStore((state) => state.user);
const clearUser = useUserPlagiatStore((state) => state.clearUser);
const clearTokens = () => {
localStorage.removeItem('access');
localStorage.removeItem('refresh');
localStorage.removeItem('user');
};
console.log('Current user:', user);
if (user) {
@@ -48,6 +55,10 @@ function AuthButtons() {
asChild
key={subItem.title}
className="w-80"
onClick={() => {
clearTokens();
clearUser();
}}
>
<SubMenuLink item={subItem} />
</NavigationMenuLink>