feat: desktop image thumbnails, gallery lightbox portal, client-side compression, clickable table rows, fix header gap

This commit is contained in:
egecankomur
2026-05-12 04:49:36 +03:00
parent 3cce632eb3
commit 3554b39800
134 changed files with 7736 additions and 1913 deletions
+134
View File
@@ -0,0 +1,134 @@
"use client";
import { useState, useEffect } from "react";
import { CheckCircle, Circle } from '@/lib/icons';
import { cn } from "@/lib/utils";
import { ACADEMY_MODULES } from "@/lib/academy/tours";
import { getCompletedModules, resetProgress } from "@/lib/academy/progress";
import { AcademyTourButton } from "./academy-tour-button";
import { Button } from "@/components/ui/button";
export function AcademyClient() {
const [completed, setCompleted] = useState<string[]>([]);
useEffect(() => {
setCompleted(getCompletedModules());
}, []);
function handleComplete() {
setCompleted(getCompletedModules());
}
function handleReset() {
resetProgress();
setCompleted([]);
}
const percent = Math.round((completed.length / ACADEMY_MODULES.length) * 100);
const allDone = completed.length === ACADEMY_MODULES.length;
return (
<div className="space-y-6">
{/* Progress header */}
<div className="bg-card border rounded-xl p-5 space-y-3">
<div className="flex items-center justify-between">
<div>
<p className="font-semibold text-sm">Genel İlerleme</p>
<p className="text-muted-foreground text-xs mt-0.5">
{completed.length} / {ACADEMY_MODULES.length} modül tamamlandı
</p>
</div>
<span className={cn(
"text-2xl font-bold",
allDone ? "text-green-600" : "text-primary"
)}>
%{percent}
</span>
</div>
{/* Progress bar */}
<div className="h-2 bg-muted rounded-full overflow-hidden">
<div
className={cn(
"h-full rounded-full transition-all duration-500",
allDone ? "bg-green-500" : "bg-primary"
)}
style={{ width: `${percent}%` }}
/>
</div>
{allDone && (
<p className="text-green-600 text-sm font-medium">
🎉 Tüm modülleri tamamladınız! Artık KovakEmlak CRM&apos;i tam verimle kullanabilirsiniz.
</p>
)}
</div>
{/* Module grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{ACADEMY_MODULES.map((mod) => {
const isDone = completed.includes(mod.id);
return (
<div
key={mod.id}
className={cn(
"bg-card border rounded-xl p-5 flex flex-col gap-3 transition-colors",
isDone && "border-green-200 bg-green-50/40 dark:bg-green-950/20 dark:border-green-900"
)}
>
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-2.5">
<span className="text-2xl">{mod.icon}</span>
<div>
<p className="font-semibold text-sm leading-tight">{mod.title}</p>
<p className="text-muted-foreground text-xs mt-0.5">
{mod.steps.length} adım
</p>
</div>
</div>
{isDone ? (
<CheckCircle className="size-5 text-green-500 shrink-0 mt-0.5" />
) : (
<Circle className="size-5 text-muted-foreground/40 shrink-0 mt-0.5" />
)}
</div>
<p className="text-muted-foreground text-xs leading-relaxed flex-1">
{mod.description}
</p>
{/* Step previews */}
<div className="space-y-1">
{mod.steps.slice(0, 3).map((step, i) => (
<div key={i} className="flex items-center gap-2 text-xs text-muted-foreground">
<span className={cn(
"size-4 rounded-full flex items-center justify-center text-[10px] font-medium shrink-0",
isDone
? "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"
: "bg-muted text-muted-foreground"
)}>
{i + 1}
</span>
<span className="truncate">{step.title}</span>
</div>
))}
</div>
<AcademyTourButton
module={mod}
onComplete={handleComplete}
variant={isDone ? "ghost" : "outline"}
/>
</div>
);
})}
</div>
{completed.length > 0 && (
<div className="flex justify-end">
<Button variant="ghost" size="sm" onClick={handleReset} className="text-muted-foreground text-xs">
İlerlemeyi sıfırla
</Button>
</div>
)}
</div>
);
}