'use client'; import React, { useEffect, useState } from 'react'; import { motion } from 'framer-motion'; import { Grid3X3, BookOpen, Layers, ChevronRight, Search, Filter, ChevronDown, ChevronUp, Tag, BarChart3, TrendingUp, Sprout, Leaf, TreePine, Trees, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useAppStore } from '@/lib/store'; import { t } from '@/lib/i18n'; import { fetchCompetencyTemplates, fetchSubjects, type CompetencyTemplate, type Subject, } from '@/lib/api'; // Subject color coding (deterministic per subject name) const subjectColors: Record = { default: { gradient: 'from-emerald-400 to-teal-500', text: 'text-emerald-700 dark:text-emerald-300', dot: 'bg-emerald-500', bg: 'bg-emerald-100 dark:bg-emerald-900/30' }, }; function subjectMeta(name?: string) { if (!name) return subjectColors.default; const lower = name.toLowerCase(); if (lower.includes('math') || lower.includes('mathemat')) return { gradient: 'from-emerald-400 to-teal-500', text: 'text-emerald-700 dark:text-emerald-300', dot: 'bg-emerald-500', bg: 'bg-emerald-100 dark:bg-emerald-900/30' }; if (lower.includes('deutsch') || lower.includes('german')) return { gradient: 'from-violet-400 to-rose-500', text: 'text-violet-700 dark:text-violet-300', dot: 'bg-violet-500', bg: 'bg-violet-100 dark:bg-violet-900/30' }; if (lower.includes('engl') || lower.includes('english')) return { gradient: 'from-teal-400 to-emerald-500', text: 'text-teal-700 dark:text-teal-300', dot: 'bg-teal-500', bg: 'bg-teal-100 dark:bg-teal-900/30' }; if (lower.includes(' sach') || lower.includes('science')) return { gradient: 'from-amber-400 to-amber-500', text: 'text-amber-700 dark:text-amber-300', dot: 'bg-amber-500', bg: 'bg-amber-100 dark:bg-amber-900/30' }; return subjectColors.default; } function hashStr(s: string): number { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return Math.abs(h); } // Deterministic pseudo distribution across 4 mastery levels function masteryDistribution(compId: string): { level: number; pct: number; color: string }[] { const h = hashStr(compId); const l1 = 5 + (h % 25); const l2 = 15 + ((h >> 3) % 30); const l3 = 25 + ((h >> 6) % 30); const l4 = Math.max(0, 100 - l1 - l2 - l3); return [ { level: 1, pct: l1, color: 'bg-red-400 dark:bg-red-500' }, { level: 2, pct: l2, color: 'bg-amber-400 dark:bg-amber-500' }, { level: 3, pct: l3, color: 'bg-emerald-400 dark:bg-emerald-500' }, { level: 4, pct: l4, color: 'bg-teal-400 dark:bg-teal-500' }, ]; } function masteredPercent(compId: string): number { const dist = masteryDistribution(compId); return dist[2].pct + dist[3].pct; // mastered = level 3+4 } export default function CompetencyGridView() { const currentUser = useAppStore((s) => s.currentUser); const [templates, setTemplates] = useState([]); const [subjects, setSubjects] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(null); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [filterSchoolType, setFilterSchoolType] = useState('all'); const [filterSubject, setFilterSubject] = useState('all'); const [expandedCategories, setExpandedCategories] = useState>(new Set()); useEffect(() => { async function load() { try { const [temps, subs] = await Promise.all([ fetchCompetencyTemplates({ schoolId: currentUser?.schoolId ?? undefined }), fetchSubjects(currentUser?.schoolId ?? undefined), ]); setTemplates(temps); setSubjects(subs); // Expand all categories by default const allCatIds = new Set(); temps.forEach((tmpl) => tmpl.categories.forEach((cat) => allCatIds.add(cat.id))); setExpandedCategories(allCatIds); } catch { // ignore } finally { setLoading(false); } } load(); }, [currentUser?.schoolId]); const schoolTypes = ['ELEMENTARY', 'MIDDLE', 'GYMNASIUM', 'OTHER']; const filteredTemplates = templates.filter((tmpl) => { if (filterSchoolType !== 'all' && tmpl.schoolType !== filterSchoolType) return false; if (filterSubject !== 'all' && tmpl.subjectId !== filterSubject) return false; if (search && !tmpl.name.toLowerCase().includes(search.toLowerCase())) return false; return true; }); const masteryColor = (level: number) => { const colors = ['bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300', 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300', 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-300']; return colors[Math.min(level - 1, 3)] ?? colors[0]; }; const masteryHoverBg = (level: number) => { const colors = ['hover:bg-red-50/80 dark:hover:bg-red-900/10', 'hover:bg-amber-50/80 dark:hover:bg-amber-900/10', 'hover:bg-emerald-50/80 dark:hover:bg-emerald-900/10', 'hover:bg-teal-50/80 dark:hover:bg-teal-900/10']; return colors[Math.min(level - 1, 3)] ?? colors[0]; }; const masteryIcon = (level: number) => { const cls = 'w-3.5 h-3.5 inline-block'; if (level === 1) return ; if (level === 2) return ; if (level === 3) return ; return ; }; const competencyProgressPercent = (tmpl: CompetencyTemplate) => { const totalComps = tmpl.categories.reduce((sum, cat) => sum + cat.competencies.length, 0); if (totalComps === 0) return 0; // Assume some progress based on assignment count const assignedCount = tmpl._count?.classCompetencyAssignments ?? 0; return Math.min(assignedCount * 20, 100); }; const toggleCategory = (catId: string) => { setExpandedCategories((prev) => { const next = new Set(prev); if (next.has(catId)) next.delete(catId); else next.add(catId); return next; }); }; const categoryColors = [ { bg: 'bg-emerald-100 dark:bg-emerald-900/30', text: 'text-emerald-700 dark:text-emerald-300', border: 'border-l-emerald-500', header: 'from-emerald-50 dark:from-emerald-900/20' }, { bg: 'bg-teal-100 dark:bg-teal-900/30', text: 'text-teal-700 dark:text-teal-300', border: 'border-l-teal-500', header: 'from-teal-50 dark:from-teal-900/20' }, { bg: 'bg-amber-100 dark:bg-amber-900/30', text: 'text-amber-700 dark:text-amber-300', border: 'border-l-amber-500', header: 'from-amber-50 dark:from-amber-900/20' }, { bg: 'bg-violet-100 dark:bg-violet-900/30', text: 'text-violet-700 dark:text-violet-300', border: 'border-l-violet-500', header: 'from-violet-50 dark:from-violet-900/20' }, { bg: 'bg-rose-100 dark:bg-rose-900/30', text: 'text-rose-700 dark:text-rose-300', border: 'border-l-rose-500', header: 'from-rose-50 dark:from-rose-900/20' }, { bg: 'bg-sky-100 dark:bg-sky-900/30', text: 'text-sky-700 dark:text-sky-300', border: 'border-l-sky-500', header: 'from-sky-50 dark:from-sky-900/20' }, ]; if (loading) { return (
); } return (
{/* Template list */}
{t('competencies.template_library')}
setSearch(e.target.value)} placeholder={t('action.search')} className="pl-9 border-emerald-200/50 dark:border-emerald-900/30 rounded-xl" />
{filteredTemplates.length === 0 ? (

{t('polish.empty_title_no_data')}

{t('competencies.no_templates')}

) : (
{filteredTemplates.map((tmpl) => { const sMeta = subjectMeta(tmpl.subject?.name); return ( setSelectedTemplate(tmpl)} className={`w-full text-left p-4 rounded-xl transition-all duration-200 border-l-3 ${ selectedTemplate?.id === tmpl.id ? 'bg-emerald-50 dark:bg-emerald-900/20 shadow-md ring-1 ring-emerald-300 dark:ring-emerald-700 border-l-emerald-500' : 'bg-gray-50/80 dark:bg-gray-800/50 hover:bg-emerald-50/50 dark:hover:bg-emerald-900/10 hover:shadow-sm border-l-emerald-400/40' }`} >

{tmpl.name}

{/* Template usage badge */} {(tmpl._count?.classCompetencyAssignments ?? 0) > 0 && ( {tmpl._count?.classCompetencyAssignments}× {t('polish.template_usage')} )} {tmpl.isGlobalTemplate && ( {t('competencies.global_template')} )}

{tmpl.subject?.name ?? '—'} · {t('competencies.grade_range', { min: tmpl.gradeLevelMin, max: tmpl.gradeLevelMax })} · {tmpl.categories.reduce((s, c) => s + c.competencies.length, 0)} {t('polish.competency_count')}

{/* Category dots */} {tmpl.categories.length > 0 && (
{tmpl.categories.map((cat, i) => (
))} {tmpl.categories.length} {t('progress.entries')}
)} {/* Progress indicator */}
{competencyProgressPercent(tmpl)}%
); })}
)} {/* Template details */}
{!selectedTemplate ? ( <>

{t('polish.empty_title_competency_grid')}

{t('polish.empty_subtitle_competency_grid')}

{/* Quick Guide: Overview stats + by-subject breakdown + recent templates */}
{t('competencies.quick_guide_title')} · {t('competencies.quick_guide_subtitle')}
{(() => { const totalCompetencies = templates.reduce( (sum, tmpl) => sum + tmpl.categories.reduce((s, c) => s + c.competencies.length, 0), 0, ); const totalCategories = templates.reduce((sum, tmpl) => sum + tmpl.categories.length, 0); // By subject breakdown const bySubject = new Map(); for (const tmpl of templates) { const subjectName = tmpl.subject?.name ?? t('competencies.no_subject'); const cnt = tmpl.categories.reduce((s, c) => s + c.competencies.length, 0); bySubject.set(subjectName, (bySubject.get(subjectName) ?? 0) + cnt); } const subjectRows = Array.from(bySubject.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5); const maxSubjectCount = Math.max(1, ...subjectRows.map(([, n]) => n)); // Recent templates (sorted by name as a stable fallback — no updatedAt available on this model) const recentTemplates = [...templates] .sort((a, b) => a.name.localeCompare(b.name)) .slice(0, 4); const tileColors = [ { bg: 'from-emerald-50 to-emerald-50/0 dark:from-emerald-900/15 dark:to-emerald-900/0', border: 'border-emerald-100/60 dark:border-emerald-900/30', text: 'text-emerald-700 dark:text-emerald-300', iconBg: 'bg-gradient-to-br from-emerald-400 to-emerald-500' }, { bg: 'from-teal-50 to-teal-50/0 dark:from-teal-900/15 dark:to-teal-900/0', border: 'border-teal-100/60 dark:border-teal-900/30', text: 'text-teal-700 dark:text-teal-300', iconBg: 'bg-gradient-to-br from-teal-400 to-teal-500' }, { bg: 'from-amber-50 to-amber-50/0 dark:from-amber-900/15 dark:to-amber-900/0', border: 'border-amber-100/60 dark:border-amber-900/30', text: 'text-amber-700 dark:text-amber-300', iconBg: 'bg-gradient-to-br from-amber-400 to-amber-500' }, ]; return (
{/* Mini stat tiles */}

{templates.length}

{t('competencies.total_templates')}

{totalCategories}

{t('competencies.total_categories')}

{totalCompetencies}

{t('competencies.total_competencies')}

{/* By subject mini bar chart */}

{t('competencies.by_subject_breakdown')}

{subjectRows.length === 0 ? (

{t('competencies.no_subjects_yet')}

) : (
{subjectRows.map(([subjectName, count], idx) => { const palette = ['bg-emerald-500', 'bg-teal-500', 'bg-amber-500', 'bg-violet-500', 'bg-rose-500']; const textPalette = ['text-emerald-700 dark:text-emerald-300', 'text-teal-700 dark:text-teal-300', 'text-amber-700 dark:text-amber-300', 'text-violet-700 dark:text-violet-300', 'text-rose-700 dark:text-rose-300']; const barColor = palette[idx % palette.length]; const textColor = textPalette[idx % textPalette.length]; const widthPct = Math.round((count / maxSubjectCount) * 100); return (
{subjectName}
{count}
); })}
)}
{/* Recent templates */}

{t('competencies.recent_templates')}

{recentTemplates.length === 0 ? (

{t('competencies.no_recent_templates')}

) : (
{recentTemplates.map((tmpl) => { const meta = subjectMeta(tmpl.subject?.name); return ( ); })}
)}
); })()} ) : ( <> {/* Template info */}
{selectedTemplate.name}

{selectedTemplate.subject?.name ?? '—'} · {t(`school_type.${selectedTemplate.schoolType.toLowerCase()}`)} · {t('competencies.grade_range', { min: selectedTemplate.gradeLevelMin, max: selectedTemplate.gradeLevelMax })} · {t('competencies.version')} {selectedTemplate.version}

{t('competencies.used_in_classes', { count: selectedTemplate._count?.classCompetencyAssignments ?? 0 })}
{selectedTemplate.description && (

{selectedTemplate.description}

)}
{/* Grid structure */}
{t('competencies.grid_structure')}
{selectedTemplate.categories.length === 0 ? (

{t('competencies.no_categories')}

) : (
{selectedTemplate.categories.map((cat, catIdx) => { const colorSet = categoryColors[catIdx % categoryColors.length]; const isExpanded = expandedCategories.has(cat.id); const catColor = cat.color ?? ['#10b981', '#14b8a6', '#f59e0b', '#8b5cf6', '#ef4444', '#0ea5e9'][catIdx % 6]; return (
{/* Category header bar */} {/* Competencies */} {isExpanded && (
{cat.competencies.map((comp) => { const dist = masteryDistribution(comp.id); const mastered = masteredPercent(comp.id); return (

{comp.code} {comp.title}

= 70 ? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-300 border-emerald-200/50 dark:border-emerald-900/30' : mastered >= 40 ? 'bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300 border-amber-200/50 dark:border-amber-900/30' : 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 border-red-200/50 dark:border-red-900/30'}`}> {mastered}% {t('polish.mastered')}
{comp.description && (

{comp.description}

)} {/* Mastery distribution mini bar chart */}
{dist.map((d) => (
))}
{t('polish.mastery_distribution')}
{comp.masteryLevelDefinitions.length > 0 && (
{comp.masteryLevelDefinitions.map((ml) => ( {masteryIcon(ml.levelValue)} {ml.levelValue}: {ml.label} ))}
)}
); })}
)}
); })}
)} )}
); }