"use client"; import { useEffect, useRef, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Activity, BookOpen, CalendarPlus, Eraser, Pencil, Trophy, Users, } from "lucide-react"; import { http } from "@/lib/api"; import type { AdminStats } from "@/lib/types"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; const SKETCH_KEY = "pargar.admin.statsSketch"; const NOTES_KEY = "pargar.admin.statsNotes"; /** Ink is always stored black; CSS invert makes it white in dark mode. */ const SKETCH_INK = "#000000"; export function StatsPanel() { const { data, isLoading } = useQuery({ queryKey: ["admin", "stats"], queryFn: () => http.get<{ stats: AdminStats }>("/api/admin/stats"), }); const [notes, setNotes] = useState(""); const canvasRef = useRef(null); const drawing = useRef(false); const [ink, setInk] = useState(true); useEffect(() => { setNotes(localStorage.getItem(NOTES_KEY) ?? ""); }, []); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; const dpr = window.devicePixelRatio || 1; const w = canvas.clientWidth; const h = 220; if (w <= 0) return; canvas.width = w * dpr; canvas.height = h * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.lineWidth = 2.5; ctx.strokeStyle = SKETCH_INK; const saved = localStorage.getItem(SKETCH_KEY); if (saved) { const img = new Image(); img.onload = () => ctx.drawImage(img, 0, 0, w, h); img.src = saved; } }, [isLoading]); const persistCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; try { localStorage.setItem(SKETCH_KEY, canvas.toDataURL("image/png")); } catch { /* ignore quota */ } }; const pos = (e: React.PointerEvent) => { const rect = canvasRef.current!.getBoundingClientRect(); return { x: e.clientX - rect.left, y: e.clientY - rect.top }; }; const onDown = (e: React.PointerEvent) => { const canvas = canvasRef.current; const ctx = canvas?.getContext("2d"); if (!canvas || !ctx) return; drawing.current = true; canvas.setPointerCapture(e.pointerId); const p = pos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); if (ink) { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = SKETCH_INK; ctx.lineWidth = 2.5; } else { ctx.globalCompositeOperation = "destination-out"; ctx.strokeStyle = "rgba(0,0,0,1)"; ctx.lineWidth = 16; } }; const onMove = (e: React.PointerEvent) => { if (!drawing.current) return; const ctx = canvasRef.current?.getContext("2d"); if (!ctx) return; const p = pos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); ctx.beginPath(); ctx.moveTo(p.x, p.y); }; const onUp = () => { if (!drawing.current) return; drawing.current = false; persistCanvas(); }; const clearSketch = () => { const canvas = canvasRef.current; const ctx = canvas?.getContext("2d"); if (!canvas || !ctx) return; ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.restore(); localStorage.removeItem(SKETCH_KEY); }; if (isLoading) { return (
{[0, 1, 2, 3, 4].map((i) => (
))}
); } const s = data?.stats; const cards = [ { label: "هنرجویان", value: s?.users ?? 0, icon: Users, klass: "text-primary bg-primary/10", hint: "هنرجویان با حساب فعال" }, { label: "درس‌ها", value: s?.lessons ?? 0, icon: BookOpen, klass: "text-secondary bg-secondary/10", hint: "درس‌های ثبت‌شده در درخت" }, { label: "سوالات", value: s?.questions ?? 0, icon: Activity, klass: "text-accent bg-accent/10", hint: "بانک سوالات آزمون" }, { label: "رویدادها", value: s?.events ?? 0, icon: CalendarPlus, klass: "text-gold bg-gold/10", hint: "جلسات و کارگاه‌ها" }, { label: "تکمیل‌ها", value: s?.completions ?? 0, icon: Trophy, klass: "text-success bg-success/10", hint: "قبولی آزمون درس‌ها" }, ]; return (
{cards.map((c) => (

{c.value}

{c.label}

{c.hint}

))}
بوم یادداشت دستی

اسکچ و ایده‌های جلسه را اینجا بکشید؛ روی همین مرورگر ذخیره می‌شود.

یادداشت متنی

چک‌لیست یا نکات عملیاتی برای تیم منتور.