detail page

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-02 13:53:15 +05:00
parent a9be6ba9f5
commit dc653652c7
14 changed files with 560 additions and 79 deletions

View File

@@ -0,0 +1,460 @@
'use client';
import { useState } from 'react';
interface Source {
url: string;
title: string;
matchPercentage: number;
matchedWords: number;
}
interface CheckResult {
ai: number;
plagiarism: number;
originality: number;
citation: number;
checkedWords: number;
uniqueWords: number;
lexicalUniqueness: number;
sentences: number;
avgWordsPerSentence: number;
lines: number;
sources: Source[];
}
interface Certificate {
id: string;
issuedAt: string;
expiresAt: string;
verificationCode: string;
issuerName: string;
downloadUrl: string;
}
interface PlagiatResultProps {
name: string;
initials: string;
email: string;
location: string;
fileName: string;
checkedAt: string;
result: CheckResult;
certificate: Certificate;
}
// Blue palette
const blue = {
50: '#E6F1FB',
100: '#B5D4F4',
200: '#85B7EB',
400: '#378ADD',
600: '#185FA5',
800: '#0C447C',
900: '#042C53',
};
const mockData: PlagiatResultProps = {
name: 'Sokhibjon Orzikulov',
initials: 'SO',
email: 'sakhib@orzklv.uz',
location: 'Tashkent, Uzbekistan',
fileName: 'resume_sokhibjon.pdf',
checkedAt: '2026-04-02',
result: {
ai: 86,
plagiarism: 92,
originality: 5,
citation: 3,
checkedWords: 1477,
uniqueWords: 593,
lexicalUniqueness: 40,
sentences: 105,
avgWordsPerSentence: 14.1,
lines: 191,
sources: [
{
url: 'https://arxiv.org/abs/1706.03762',
title: 'arxiv.org — Attention Is All You Need',
matchPercentage: 9,
matchedWords: 937,
},
{
url: 'https://en.wikipedia.org/wiki/Machine_learning',
title: 'Wikipedia — Machine Learning',
matchPercentage: 7,
matchedWords: 730,
},
{
url: 'https://towardsdatascience.com/introduction-to-neural-networks',
title: 'Towards Data Science — Introduction to Neural Networks',
matchPercentage: 6,
matchedWords: 625,
},
],
},
certificate: {
id: 'cert-9001',
issuedAt: '2026-03-30',
expiresAt: '2027-03-30',
verificationCode: 'PLAG-9001-VERIFY',
issuerName: 'Global Plagiarism Checker',
downloadUrl: '/certificates/cert-9001.pdf',
},
};
// ─── Sub-components ───────────────────────────────────────────────────────────
function CircleGauge({ value }: { value: number }) {
const r = 50;
const circ = 2 * Math.PI * r;
const offset = circ - (value / 100) * circ;
return (
<svg
width="130"
height="130"
viewBox="0 0 130 130"
aria-label={`${value}% plagiat`}
>
<circle
cx="65"
cy="65"
r={r}
fill="none"
stroke={blue[100]}
strokeWidth="14"
/>
<circle
cx="65"
cy="65"
r={r}
fill="none"
stroke={blue[900]}
strokeWidth="14"
strokeDasharray={circ}
strokeDashoffset={offset}
strokeLinecap="round"
transform="rotate(-90 65 65)"
style={{ transition: 'stroke-dashoffset 0.8s ease' }}
/>
<text
x="65"
y="60"
textAnchor="middle"
fontSize="26"
fontWeight="500"
fill={blue[900]}
>
{value}%
</text>
<text x="65" y="78" textAnchor="middle" fontSize="11" fill={blue[400]}>
plagiat
</text>
</svg>
);
}
function BarRow({
label,
value,
color,
}: {
label: string;
value: number;
color: string;
}) {
return (
<div className="mb-2.5">
<div className="flex justify-between items-center text-sm mb-1">
<span className="flex items-center gap-1.5 text-slate-500">
<span
className="inline-block w-2.5 h-2.5 rounded-full"
style={{ background: color }}
/>
{label}
</span>
<span className="font-medium" style={{ color: blue[900] }}>
{value}%
</span>
</div>
<div
className="h-1.5 rounded-full overflow-hidden"
style={{ background: blue[100] }}
>
<div
className="h-full rounded-full"
style={{
width: `${value}%`,
background: color,
transition: 'width 0.8s ease',
}}
/>
</div>
</div>
);
}
function MetricCard({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-lg p-3.5" style={{ background: blue[50] }}>
<p className="text-xs mb-1" style={{ color: blue[600] }}>
{label}
</p>
<p className="text-xl font-medium" style={{ color: blue[900] }}>
{value}
</p>
</div>
);
}
function SourceItem({ source, index }: { source: Source; index: number }) {
const colors = [blue[900], blue[600], blue[400]];
const color = colors[index] ?? blue[400];
return (
<div
className="rounded-lg p-3"
style={{ border: `0.5px solid ${blue[100]}` }}
>
<div className="flex items-center justify-between mb-1">
<span
className="text-sm font-medium truncate max-w-[72%]"
style={{ color: blue[900] }}
>
{source.title}
</span>
<span className="text-sm font-medium" style={{ color }}>
{source.matchPercentage}%
</span>
</div>
<p className="text-[11px] mb-1.5 truncate" style={{ color: blue[400] }}>
{source.url}
</p>
<div
className="h-1.5 rounded-full overflow-hidden"
style={{ background: blue[100] }}
>
<div
className="h-full rounded-full"
style={{
width: `${source.matchPercentage}%`,
background: color,
transition: 'width 0.8s ease',
}}
/>
</div>
</div>
);
}
// ─── Main component ───────────────────────────────────────────────────────────
export default function PlagiatResult({
data = mockData,
}: {
data?: PlagiatResultProps;
}) {
const [downloading, setDownloading] = useState(false);
const { result, certificate } = data;
const handleDownload = () => {
setDownloading(true);
setTimeout(() => setDownloading(false), 1500);
};
const divider = (
<hr
style={{
borderColor: blue[100],
borderTopWidth: '0.5px',
borderStyle: 'solid',
margin: '1.25rem 0',
}}
/>
);
return (
<div className="min-h-screen flex items-start justify-center p-6 bg-slate-50">
<div
className="w-full max-w-2xl rounded-xl p-5"
style={{ background: '#ffffff', border: `0.5px solid ${blue[100]}` }}
>
{/* ── Header ── */}
<div className="flex items-center gap-3 mb-5">
<div
className="w-11 h-11 rounded-full flex items-center justify-center text-sm font-medium shrink-0"
style={{ background: blue[50], color: blue[600] }}
>
{data.initials}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span
className="text-[15px] font-medium"
style={{ color: blue[900] }}
>
{data.name}
</span>
<span
className="text-xs px-2.5 py-0.5 rounded-md font-medium"
style={{ background: blue[100], color: blue[800] }}
>
{result.plagiarism}% plagiat
</span>
</div>
<p
className="text-[13px] mt-0.5 truncate"
style={{ color: blue[400] }}
>
{data.fileName} · {data.email} · {data.location}
</p>
</div>
<div className="text-right shrink-0">
<p className="text-[11px]" style={{ color: blue[400] }}>
Tekshirilgan
</p>
<p className="text-[12px] mt-0.5" style={{ color: blue[600] }}>
{data.checkedAt}
</p>
</div>
</div>
{divider}
{/* ── Top metrics ── */}
<div className="grid grid-cols-4 gap-2.5 mb-5">
<MetricCard
label="Plagiat darajasi"
value={`${result.plagiarism}%`}
/>
<MetricCard label="AI yozgan" value={`${result.ai}%`} />
<MetricCard label="Originallik" value={`${result.originality}%`} />
<MetricCard label="Iqtibos" value={`${result.citation}%`} />
</div>
{/* ── Gauge + bars ── */}
<div className="flex items-center gap-5 mb-5">
<CircleGauge value={result.plagiarism} />
<div className="flex-1">
<BarRow
label="Plagiat"
value={result.plagiarism}
color={blue[900]}
/>
<BarRow
label="AI generatsiya"
value={result.ai}
color={blue[600]}
/>
<BarRow
label="Original"
value={result.originality}
color={blue[400]}
/>
<BarRow label="Iqtibos" value={result.citation} color={blue[200]} />
</div>
</div>
{divider}
{/* ── Text analysis ── */}
<p
className="text-[13px] font-medium uppercase tracking-wider mb-3"
style={{ color: blue[400] }}
>
Matn tahlili
</p>
<div className="grid grid-cols-3 gap-2 mb-5">
<MetricCard
label="Jami so'z"
value={result.checkedWords.toLocaleString()}
/>
<MetricCard
label="Unikal so'z"
value={result.uniqueWords.toLocaleString()}
/>
<MetricCard
label="Leksik unikalligi"
value={`${result.lexicalUniqueness}%`}
/>
<MetricCard label="Jumlalar" value={String(result.sentences)} />
<MetricCard
label="O'rt. so'z/juml."
value={String(result.avgWordsPerSentence)}
/>
<MetricCard label="Qatorlar" value={String(result.lines)} />
</div>
{divider}
{/* ── Sources ── */}
<p
className="text-[13px] font-medium uppercase tracking-wider mb-3"
style={{ color: blue[400] }}
>
Manba yo&lsquo;nalishlari
</p>
<div className="flex flex-col gap-2.5 mb-5">
{result.sources.map((source, i) => (
<SourceItem key={source.url} source={source} index={i} />
))}
</div>
{divider}
{/* ── Certificate ── */}
<p
className="text-[13px] font-medium uppercase tracking-wider mb-3"
style={{ color: blue[400] }}
>
Sertifikat
</p>
<div
className="flex items-center gap-3 rounded-lg p-3.5"
style={{ background: blue[50], border: `0.5px solid ${blue[100]}` }}
>
<div
className="w-9 h-9 rounded-lg flex items-center justify-center shrink-0"
style={{ background: blue[600] }}
>
<svg
width="16"
height="16"
viewBox="0 0 18 18"
fill="none"
aria-hidden="true"
>
<path
d="M9 1L11.2 6.5H17L12.4 10L14.1 16L9 12.8L3.9 16L5.6 10L1 6.5H6.8L9 1Z"
fill="#E6F1FB"
/>
</svg>
</div>
<div className="flex-1 min-w-0">
<p className="text-[13px] font-medium" style={{ color: blue[900] }}>
{certificate.issuerName} sertifikati
</p>
<p
className="text-[11px] mt-0.5 font-mono truncate"
style={{ color: blue[600] }}
>
{certificate.verificationCode} · Amal qilish:{' '}
{certificate.expiresAt}
</p>
</div>
<button
onClick={handleDownload}
className="text-xs px-3 py-1.5 rounded-md shrink-0 transition-colors cursor-pointer"
style={{
border: `0.5px solid ${blue[400]}`,
color: blue[600],
background: downloading ? blue[100] : 'transparent',
}}
>
{downloading ? 'Yuklanmoqda...' : 'Yuklab olish ↗'}
</button>
</div>
</div>
</div>
);
}