changed
This commit is contained in:
49
src/widgets/cabinet/ui/dashboard/CtaCards.tsx
Normal file
49
src/widgets/cabinet/ui/dashboard/CtaCards.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { FileSearch, BrainCircuit, ArrowRight } from 'lucide-react';
|
||||
import type { CabinetSection } from '../../lib/types';
|
||||
|
||||
interface CtaCardsProps {
|
||||
onNavigate: (section: CabinetSection) => void;
|
||||
}
|
||||
|
||||
export const CtaCards: React.FC<CtaCardsProps> = ({ onNavigate }) => (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{/* Plagiat */}
|
||||
<button
|
||||
onClick={() => onNavigate('plagiat')}
|
||||
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-blue-500 to-blue-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
||||
>
|
||||
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
|
||||
<FileSearch size={72} className="text-white" />
|
||||
</div>
|
||||
<FileSearch size={26} className="text-white mb-4" />
|
||||
<h3 className="text-white font-semibold text-base mb-1">
|
||||
Plagiat tekshiruvi
|
||||
</h3>
|
||||
<p className="text-blue-100 text-sm mb-4 leading-relaxed">
|
||||
Hujjatingizni originallik uchun tekshiring
|
||||
</p>
|
||||
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
||||
Yuborish <ArrowRight size={12} />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* SI */}
|
||||
<button
|
||||
onClick={() => onNavigate('si')}
|
||||
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-violet-500 to-violet-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
||||
>
|
||||
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
|
||||
<BrainCircuit size={72} className="text-white" />
|
||||
</div>
|
||||
<BrainCircuit size={26} className="text-white mb-4" />
|
||||
<h3 className="text-white font-semibold text-base mb-1">SI detektor</h3>
|
||||
<p className="text-violet-100 text-sm mb-4 leading-relaxed">
|
||||
Matnni sun'iy intellekt uchun tekshiring
|
||||
</p>
|
||||
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
||||
Yuborish <ArrowRight size={12} />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
128
src/widgets/cabinet/ui/dashboard/ModulesSection.tsx
Normal file
128
src/widgets/cabinet/ui/dashboard/ModulesSection.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
MODULE_CATEGORIES,
|
||||
MODULE_STATS,
|
||||
TAG_STYLES,
|
||||
type ModuleTag,
|
||||
} from '../../lib/modules';
|
||||
|
||||
// ─── Module stats mini-cards ───────────────────────────────────────────────────
|
||||
|
||||
const ModuleStats: React.FC = () => (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ value: MODULE_STATS.total, label: 'Jami modullar' },
|
||||
{ value: MODULE_STATS.freeInternet, label: 'Bepul internet manbalari' },
|
||||
{ value: MODULE_STATS.aiModules, label: 'AI tahlil modullari' },
|
||||
{ value: MODULE_STATS.categories, label: 'Kategoriya' },
|
||||
].map(({ value, label }) => (
|
||||
<div
|
||||
key={label}
|
||||
className="bg-slate-50 border border-slate-100 rounded-xl px-4 py-3"
|
||||
>
|
||||
<p className="text-2xl font-bold text-slate-900 tabular-nums leading-none">
|
||||
{value}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 mt-1">{label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
// ─── Tag badge ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const Tag: React.FC<{ tag: ModuleTag }> = ({ tag }) => (
|
||||
<span
|
||||
className={`inline-block text-[10px] font-semibold px-2 py-0.5 rounded-full ${TAG_STYLES[tag]}`}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
);
|
||||
|
||||
// ─── Category divider ──────────────────────────────────────────────────────────
|
||||
|
||||
const CategoryHeader: React.FC<{ label: string }> = ({ label }) => (
|
||||
<div className="flex items-center gap-3 my-5">
|
||||
<div className="flex-1 h-px bg-slate-100" />
|
||||
<span className="text-[11px] font-semibold tracking-widest text-slate-400 uppercase whitespace-nowrap">
|
||||
{label}
|
||||
</span>
|
||||
<div className="flex-1 h-px bg-slate-100" />
|
||||
</div>
|
||||
);
|
||||
|
||||
// ─── Single module card ────────────────────────────────────────────────────────
|
||||
|
||||
interface ModuleCardProps {
|
||||
name: string;
|
||||
desc: string;
|
||||
tags: ModuleTag[];
|
||||
index: number;
|
||||
}
|
||||
|
||||
const ModuleCard: React.FC<ModuleCardProps> = ({ name, desc, tags, index }) => (
|
||||
<div className="bg-white border border-slate-100 rounded-xl p-4 flex items-start gap-3 hover:border-slate-200 hover:shadow-sm transition-all duration-150">
|
||||
{/* Index number */}
|
||||
<span className="w-6 h-6 rounded-full bg-slate-100 flex items-center justify-center text-[10px] font-semibold text-slate-500 shrink-0 mt-0.5">
|
||||
{index}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold text-slate-800 leading-snug mb-1 truncate">
|
||||
{name}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 leading-relaxed mb-2.5">{desc}</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{tags.map((tag) => (
|
||||
<Tag key={tag} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// ─── Modules section ───────────────────────────────────────────────────────────
|
||||
|
||||
export const ModulesSection: React.FC = () => {
|
||||
// running counter across all categories
|
||||
let counter = 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-slate-800">
|
||||
Tekshiruv modullari
|
||||
</h3>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Plagiat aniqlashda foydalaniladigan barcha manbalar
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-slate-400 bg-slate-100 px-2.5 py-1 rounded-lg font-medium">
|
||||
{MODULE_STATS.total} ta modul
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ModuleStats />
|
||||
|
||||
{MODULE_CATEGORIES.map((cat) => (
|
||||
<div key={cat.label}>
|
||||
<CategoryHeader label={cat.label} />
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3">
|
||||
{cat.modules.map((mod) => {
|
||||
counter += 1;
|
||||
return (
|
||||
<ModuleCard
|
||||
key={mod.name}
|
||||
name={mod.name}
|
||||
desc={mod.desc}
|
||||
tags={mod.tags}
|
||||
index={counter}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
81
src/widgets/cabinet/ui/dashboard/StatsCards.tsx
Normal file
81
src/widgets/cabinet/ui/dashboard/StatsCards.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { TrendingUp, Calendar, Tag, Wallet } from 'lucide-react';
|
||||
import type { CabinetStats } from '../../lib/types';
|
||||
|
||||
// ─── Single stat card ──────────────────────────────────────────────────────────
|
||||
|
||||
interface StatCardProps {
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
value: string;
|
||||
sub?: string;
|
||||
iconColor: string;
|
||||
iconBg: string;
|
||||
}
|
||||
|
||||
const StatCard: React.FC<StatCardProps> = ({
|
||||
icon: Icon,
|
||||
label,
|
||||
value,
|
||||
sub,
|
||||
iconColor,
|
||||
iconBg,
|
||||
}) => (
|
||||
<div className="bg-white rounded-2xl border border-slate-100 p-5 shadow-sm hover:shadow-md transition-shadow duration-200">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-xl flex items-center justify-center mb-4 ${iconBg}`}
|
||||
>
|
||||
<Icon size={18} className={iconColor} />
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900 tabular-nums">{value}</p>
|
||||
<p className="text-xs text-slate-500 mt-0.5">{label}</p>
|
||||
{sub && <p className="text-[11px] text-slate-400 mt-1">{sub}</p>}
|
||||
</div>
|
||||
);
|
||||
|
||||
// ─── Grid ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface StatsCardsProps {
|
||||
stats: CabinetStats;
|
||||
}
|
||||
|
||||
export const StatsCards: React.FC<StatsCardsProps> = ({ stats }) => {
|
||||
const discountPct = Math.round(
|
||||
(stats.discountUsed / stats.discountTotal) * 100,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
icon={TrendingUp}
|
||||
label="Jami tekshiruvlar"
|
||||
value={String(stats.total)}
|
||||
iconColor="text-blue-600"
|
||||
iconBg="bg-blue-50"
|
||||
/>
|
||||
<StatCard
|
||||
icon={Calendar}
|
||||
label="Bu oy"
|
||||
value={String(stats.thisMonth)}
|
||||
sub={`${stats.discountUsed}/${stats.discountTotal} ta hujjat`}
|
||||
iconColor="text-emerald-600"
|
||||
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
|
||||
icon={Wallet}
|
||||
label="Balans"
|
||||
value={`${stats.balance.toLocaleString()} ${stats.currency}`}
|
||||
iconColor="text-violet-600"
|
||||
iconBg="bg-violet-50"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
41
src/widgets/cabinet/ui/dashboard/index.tsx
Normal file
41
src/widgets/cabinet/ui/dashboard/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { CtaCards } from './CtaCards';
|
||||
import { StatsCards } from './StatsCards';
|
||||
import { ModulesSection } from './ModulesSection';
|
||||
import type { CabinetSection, CabinetStats } from '../../lib/types';
|
||||
|
||||
interface DashboardProps {
|
||||
stats: CabinetStats;
|
||||
onNavigate: (section: CabinetSection) => void;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export const Dashboard: React.FC<DashboardProps> = ({
|
||||
stats,
|
||||
onNavigate,
|
||||
userName,
|
||||
}) => (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-slate-900">
|
||||
Xush kelibsiz, {userName} 👋
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500 mt-0.5">
|
||||
Shaxsiy kabinetingizga xush kelibsiz
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<StatsCards stats={stats} />
|
||||
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-widest mb-3">
|
||||
Tezkor harakatlar
|
||||
</h3>
|
||||
<CtaCards onNavigate={onNavigate} />
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100 pt-6">
|
||||
<ModulesSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
Reference in New Issue
Block a user