add service and sertificate prices ,

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-18 13:50:32 +05:00
parent 75bd3467e9
commit aea8854a13
12 changed files with 112 additions and 66 deletions

View File

@@ -53,7 +53,9 @@
"certificateDescription": "An official certificate will be attached to your originality report.", "certificateDescription": "An official certificate will be attached to your originality report.",
"submitting": "Submitting…", "submitting": "Submitting…",
"submitButton": "Submit for Originality Check", "submitButton": "Submit for Originality Check",
"dismiss": "Dismiss" "dismiss": "Dismiss",
"service_price": "Service price {PLAGIAT_SERVICE_FEE} UZS",
"sertificate_price": "Certificate price {SERTIFICATE_PRICE} UZS"
}, },
"HistoryPage": { "HistoryPage": {
"title": "Check History", "title": "Check History",

View File

@@ -53,7 +53,9 @@
"certificateDescription": "Официальный сертификат будет прикреплен к вашему отчету об оригинальности.", "certificateDescription": "Официальный сертификат будет прикреплен к вашему отчету об оригинальности.",
"submitting": "Отправка…", "submitting": "Отправка…",
"submitButton": "Отправить на проверку оригинальности", "submitButton": "Отправить на проверку оригинальности",
"dismiss": "Закрыть" "dismiss": "Закрыть",
"service_price": "Стоимость услуги {PLAGIAT_SERVICE_FEE} сум",
"sertificate_price": "Стоимость сертификата {SERTIFICATE_PRICE} сум"
}, },
"HistoryPage": { "HistoryPage": {
"title": "История проверок", "title": "История проверок",

View File

@@ -57,6 +57,8 @@ declare const messages: {
submitting: 'Yuborilmoqda…'; submitting: 'Yuborilmoqda…';
submitButton: 'Orijinallik tekshiruvi uchun yuborish'; submitButton: 'Orijinallik tekshiruvi uchun yuborish';
dismiss: 'Yopish'; dismiss: 'Yopish';
service_price: "Xizmat narxi {PLAGIAT_SERVICE_FEE} so'm";
sertificate_price: "Sertifikat narxi {SERTIFICATE_PRICE} so'm";
}; };
HistoryPage: { HistoryPage: {
title: 'Tekshiruv tarixi'; title: 'Tekshiruv tarixi';

View File

@@ -53,7 +53,9 @@
"certificateDescription": "Rasmiy sertifikat sizning orijinallik hisobotingizga ilova qilinadi.", "certificateDescription": "Rasmiy sertifikat sizning orijinallik hisobotingizga ilova qilinadi.",
"submitting": "Yuborilmoqda…", "submitting": "Yuborilmoqda…",
"submitButton": "Orijinallik tekshiruvi uchun yuborish", "submitButton": "Orijinallik tekshiruvi uchun yuborish",
"dismiss": "Yopish" "dismiss": "Yopish",
"service_price": "Xizmat narxi {PLAGIAT_SERVICE_FEE} so'm",
"sertificate_price": "Sertifikat narxi {SERTIFICATE_PRICE} so'm"
}, },
"HistoryPage": { "HistoryPage": {
"title": "Tekshiruv tarixi", "title": "Tekshiruv tarixi",

View File

@@ -143,11 +143,19 @@ api.interceptors.response.use(
}; };
const status = error.response?.status; const status = error.response?.status;
// const responseData = error.response?.data as Record<string, unknown> | undefined;
const requestUrl = originalRequest.url ?? ''; const requestUrl = originalRequest.url ?? '';
const isAuthEndpoint = const isAuthEndpoint =
requestUrl.includes('/users/login/') || requestUrl.includes('/users/login/') ||
requestUrl.includes('/users/register/'); requestUrl.includes('/users/register/');
// 403 with token_not_valid means the token is expired — clear and redirect
if (status === 403) {
TokenStorage.clear();
redirectToMain();
return Promise.reject(error);
}
// For auth endpoints, 401 means wrong credentials — show error, don't refresh // For auth endpoints, 401 means wrong credentials — show error, don't refresh
if (isAuthEndpoint || status !== 401 || originalRequest._retry) { if (isAuthEndpoint || status !== 401 || originalRequest._retry) {
toast.error(extractErrorMessage(error)); toast.error(extractErrorMessage(error));

View File

@@ -1,11 +1,12 @@
'use client'; 'use client';
import React from 'react'; import React, { useState } from 'react';
import { Clock, XCircle, ReceiptText } from 'lucide-react'; import { Clock, XCircle, ReceiptText } from 'lucide-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { apiRequest } from '@/shared/request/apiRequest'; import { apiRequest } from '@/shared/request/apiRequest';
import { links } from '@/shared/request/links'; import { links } from '@/shared/request/links';
import PaymentStatus from '@/widgets/detail/paidStatus'; import PaymentStatus from '@/widgets/detail/paidStatus';
import { Pagination } from '@/widgets/history/ui/pagination';
// import { toast } from 'react-toastify'; // import { toast } from 'react-toastify';
// import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; // import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal';
@@ -36,9 +37,11 @@ function formatPrice(price: string) {
// ─── Component ───────────────────────────────────────────────────────────────── // ─── Component ─────────────────────────────────────────────────────────────────
const PAGE_SIZE = 10;
export function PaymentsTable() { export function PaymentsTable() {
const t = useTranslations('Cabinet'); const t = useTranslations('Cabinet');
// const [isPaymentOpen, setIsPaymentOpen] = useState(false); const [currentPage, setCurrentPage] = useState(1);
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ['pay_history'], queryKey: ['pay_history'],
queryFn: (): Promise<Inspection[]> => queryFn: (): Promise<Inspection[]> =>
@@ -47,6 +50,12 @@ export function PaymentsTable() {
), ),
}); });
const totalPages = Math.ceil((data?.length ?? 0) / PAGE_SIZE);
const pageItems = data?.slice(
(currentPage - 1) * PAGE_SIZE,
currentPage * PAGE_SIZE,
);
// const payment = useMutation({ // const payment = useMutation({
// mutationKey: ['payload'], // mutationKey: ['payload'],
// mutationFn: ({ order_id }: { order_id: number }) => // mutationFn: ({ order_id }: { order_id: number }) =>
@@ -93,6 +102,7 @@ export function PaymentsTable() {
<p className="text-sm">{t('noPayments')}</p> <p className="text-sm">{t('noPayments')}</p>
</div> </div>
) : ( ) : (
<>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
@@ -115,9 +125,7 @@ export function PaymentsTable() {
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-50"> <tbody className="divide-y divide-slate-50">
{data.map((row) => { {pageItems!.map((row) => (
// const service_fee = row.total_price + row.discount;
return (
<tr <tr
key={row.id} key={row.id}
className="hover:bg-slate-50/60 transition-colors" className="hover:bg-slate-50/60 transition-colors"
@@ -155,25 +163,17 @@ export function PaymentsTable() {
</span> </span>
)} )}
</td> </td>
{/* <PaymentModal
isOpen={isPaymentOpen}
onClose={() => setIsPaymentOpen(false)}
price={{
service_fee: Number(service_fee),
discount: Number(row.discount) || 0,
total_price: Number(row.total_price) || 0,
}}
onConfirmPayment={() => {
handleSubmit({ document_id: 0 });
}}
isLoading={payment.isPending}
/> */}
</tr> </tr>
); ))}
})}
</tbody> </tbody>
</table> </table>
</div> </div>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</>
)} )}
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
'use client'; 'use client';
import React from 'react'; import React, { useState } from 'react';
import { Download, CreditCard, Eye } from 'lucide-react'; import { Download, CreditCard, Eye } from 'lucide-react';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
@@ -10,6 +10,7 @@ import { links } from '@/shared/request/links';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import type { SiDocument } from '../../lib/types'; import type { SiDocument } from '../../lib/types';
import { useRouter, useParams } from 'next/navigation'; import { useRouter, useParams } from 'next/navigation';
import { Pagination } from '@/widgets/history/ui/pagination';
// ─── State badge ─────────────────────────────────────────────────────────────── // ─── State badge ───────────────────────────────────────────────────────────────
@@ -180,9 +181,18 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({
// ─── SiTable ─────────────────────────────────────────────────────────────────── // ─── SiTable ───────────────────────────────────────────────────────────────────
const PAGE_SIZE = 10;
export const SiTable: React.FC = () => { export const SiTable: React.FC = () => {
const t = useTranslations('Cabinet'); const t = useTranslations('Cabinet');
const { items, isLoading, isError } = useSiHistory(); const { items, isLoading, isError } = useSiHistory();
const [currentPage, setCurrentPage] = useState(1);
const totalPages = Math.ceil(items.length / PAGE_SIZE);
const pageItems = items.slice(
(currentPage - 1) * PAGE_SIZE,
currentPage * PAGE_SIZE,
);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
@@ -228,12 +238,21 @@ export const SiTable: React.FC = () => {
{!isLoading && !isError && items.length === 0 && <EmptyState />} {!isLoading && !isError && items.length === 0 && <EmptyState />}
{!isLoading && {!isLoading &&
!isError && !isError &&
items.map((item, i) => ( pageItems.map((item, i) => (
<SiRow key={item.id} item={item} index={i + 1} /> <SiRow
key={item.id}
item={item}
index={(currentPage - 1) * PAGE_SIZE + i + 1}
/>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div> </div>
</div> </div>
); );

View File

@@ -36,14 +36,15 @@ export const useHistory = (pageSize = DEFAULT_PAGE_SIZE): UseHistoryReturn => {
useEffect(() => { useEffect(() => {
if (data) { if (data) {
const start = (currentPage - 1) * pageSize;
setState({ setState({
items: data?.results || [], items: (data.results || []).slice(start, start + pageSize),
status: 'success', status: 'success',
error: null, error: null,
}); });
setTotal(data.total || 0); setTotal(data.total || 0);
} }
}, [data]); }, [data, currentPage, pageSize]);
useEffect(() => { useEffect(() => {
refetch(); refetch();

View File

@@ -9,7 +9,7 @@ import { useRouter } from '@/shared/config/i18n/navigation';
const PlagiarismLanding = () => { const PlagiarismLanding = () => {
const route = useRouter(); const route = useRouter();
useEffect(() => { useEffect(() => {
const data = localStorage.getItem('user'); const data = localStorage.getItem('access_token');
if (data) { if (data) {
route.push('/plagiat'); route.push('/plagiat');

View File

@@ -13,6 +13,7 @@ import { usePlagiarismForm } from '../lib/usePlagiraism';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal';
import DocumentsTypes from './documentsType'; import DocumentsTypes from './documentsType';
import { PLAGIAT_SERVICE_FEE } from '@/shared/lib/metadata';
export const inputCls = ` export const inputCls = `
w-full px-3.5 py-3.5 text-[14px] text-slate-800 w-full px-3.5 py-3.5 text-[14px] text-slate-800
@@ -113,7 +114,7 @@ export function PlagiarismCheckForm() {
)} )}
{/* left part */} {/* left part */}
<div className="flex flex-col gap-9 md:max-w-[50%] w-full"> <div className="flex flex-col gap-6 md:max-w-[50%] w-full">
{/* Topic */} {/* Topic */}
<FieldWrapper <FieldWrapper
label={t('documentTopic')} label={t('documentTopic')}
@@ -131,6 +132,9 @@ export function PlagiarismCheckForm() {
maxLength={200} maxLength={200}
disabled={isLoading} disabled={isLoading}
/> />
<p className="text-sm text-stone-500 ml-2 ">
{t('service_price', { PLAGIAT_SERVICE_FEE })}
</p>
</FieldWrapper> </FieldWrapper>
{/* Sender Full Name (read-only) */} {/* Sender Full Name (read-only) */}

View File

@@ -1,3 +1,5 @@
import { SERTIFICATE_PRICE } from '@/shared/lib/metadata';
import { useTranslations } from 'next-intl';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
// ─── FieldWrapper ──────────────────────────────────────────────────────────── // ─── FieldWrapper ────────────────────────────────────────────────────────────
@@ -219,6 +221,7 @@ export function CertificateCheckbox({
title = 'Return result with certificate', title = 'Return result with certificate',
description = 'An official certificate will be attached to your originality report.', description = 'An official certificate will be attached to your originality report.',
}: CertificateCheckboxProps) { }: CertificateCheckboxProps) {
const t = useTranslations('PlagiarismCheck');
return ( return (
<label <label
className={` className={`
@@ -260,6 +263,9 @@ export function CertificateCheckbox({
</div> </div>
<div> <div>
<p className="text-sm font-semibold text-stone-800">{title}</p> <p className="text-sm font-semibold text-stone-800">{title}</p>
<p className="text-sm text-stone-500">
{t('sertificate_price', { SERTIFICATE_PRICE })}
</p>
<p className="text-xs text-stone-500 mt-0.5">{description}</p> <p className="text-xs text-stone-500 mt-0.5">{description}</p>
</div> </div>
</label> </label>