'use client'; import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle, } from '@/figma-make/components/base/card'; import { Label } from '@/figma-make/components/base/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/figma-make/components/base/select'; import { Input } from '@/figma-make/components/base/input'; import { Badge } from '@/figma-make/components/base/badge'; import { Calendar as CalendarIcon, Users, AlertCircle, Info, } from 'lucide-react'; import { DateRangePicker } from '@/figma-make/components/base/date-range-picker'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell, } from 'recharts'; import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase'; import { toast } from 'sonner'; interface EmployeePerformance { employee_id: string; employee_name: string; kandang_id: string; kandang_name: string; total_activities_in_category: number; // Total aktivitas di kategori completed_activities: number; // Aktivitas yang sudah di-check completion_rate: number; last_activity_date: string | null; color: string; // Color based on kandang } interface Kandang { id: string; name: string; } interface Category { id: string; name: string; } interface ChecklistKandang { id: string; date: string; kandang_id: string; category: string; kandang: { id: string; name: string; } | null; } interface AssignmentEmployee { id: string; task_id: string; employee_id: string; checked: boolean; updated_at: string; employee: { id: string; name: string; } | null; } const KANDANG_COLORS = [ '#0069e0', // Blue (primary) '#10B981', // Green '#F59E0B', // Amber '#EF4444', // Red '#8B5CF6', // Violet '#EC4899', // Pink '#14B8A6', // Teal '#F97316', // Orange ]; const CATEGORY_LABELS: { [key: string]: string } = { pullet_open: 'Pullet Open', pullet_close: 'Pullet Close', produksi_open: 'Produksi Open', produksi_close: 'Produksi Close', }; export function Dashboard() { const [loading, setLoading] = useState(false); const [employeePerformance, setEmployeePerformance] = useState< EmployeePerformance[] >([]); // Master data const [kandangList, setKandangList] = useState([]); const [categoryList, setCategoryList] = useState([]); // Filters const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); const [kandangFilter, setKandangFilter] = useState('ALL'); const [categoryFilter, setCategoryFilter] = useState('ALL'); // Color mapping for kandang const [kandangColorMap, setKandangColorMap] = useState<{ [key: string]: string; }>({}); useEffect(() => { fetchMasterData(); }, []); useEffect(() => { // Only fetch when date filters are set if (dateFrom && dateTo) { fetchEmployeePerformance(); } else { setEmployeePerformance([]); } }, [dateFrom, dateTo, kandangFilter, categoryFilter]); const fetchMasterData = async () => { if (!isSupabaseConfigured()) return; try { // Fetch kandang const { data: kandangData, error: kandangError } = await supabase .from('kandang') .select('id, name') .order('name', { ascending: true }); if (kandangError) { console.error('Error fetching kandang:', kandangError); } else { setKandangList(kandangData || []); // Create color mapping const colorMap: { [key: string]: string } = {}; (kandangData || []).forEach((k, index) => { colorMap[k.id] = KANDANG_COLORS[index % KANDANG_COLORS.length]; }); setKandangColorMap(colorMap); } // Set categories from CATEGORY_LABELS (hardcoded list) const categories: Category[] = Object.keys(CATEGORY_LABELS).map((id) => ({ id, name: CATEGORY_LABELS[id], })); setCategoryList(categories); } catch (error) { console.error('Error fetching master data:', error); } }; const fetchEmployeePerformance = async () => { if (!isSupabaseConfigured() || !dateFrom || !dateTo) { return; } try { setLoading(true); // Step 1: Get all checklists in date range + filters let checklistQuery = supabase .from('daily_checklists') .select( ` id, date, kandang_id, category, kandang:kandang_id ( id, name ) ` ) .gte('date', dateFrom) .lte('date', dateTo); if (kandangFilter !== 'ALL') { checklistQuery = checklistQuery.eq('kandang_id', kandangFilter); } if (categoryFilter !== 'ALL') { checklistQuery = checklistQuery.eq('category', categoryFilter); } const { data: checklists, error: checklistError } = await checklistQuery; if (checklistError) { console.error('Error fetching checklists:', checklistError); toast.error('Gagal memuat data checklist'); return; } if (!checklists || checklists.length === 0) { setEmployeePerformance([]); return; } const checklistsData = checklists as unknown as ChecklistKandang[]; // Step 2: Get all tasks from these checklists const checklistIds = checklistsData.map((c) => c.id); const { data: tasks, error: tasksError } = await supabase .from('daily_checklist_activity_tasks') .select('id, checklist_id') .in('checklist_id', checklistIds); if (tasksError) { console.error('Error fetching tasks:', tasksError); return; } if (!tasks || tasks.length === 0) { setEmployeePerformance([]); return; } const taskIds = tasks.map((t) => t.id); // Step 3: Get all assignments for these tasks const { data: assignments, error: assignmentsError } = await supabase .from('daily_checklist_activity_task_assignments') .select( ` id, task_id, employee_id, checked, updated_at, employee:employee_id ( id, name ) ` ) .in('task_id', taskIds); if (assignmentsError) { console.error('Error fetching assignments:', assignmentsError); return; } if (!assignments || assignments.length === 0) { setEmployeePerformance([]); return; } const assignmentsData = assignments as unknown as AssignmentEmployee[]; // Step 4: Calculate total activities in selected category (if filtered) let totalActivitiesInCategory = 0; if (categoryFilter !== 'ALL') { // Get total activities from master data for this category const { data: phases } = await supabase .from('phases') .select('id') .eq('category_id', categoryFilter); if (phases && phases.length > 0) { const phaseIds = phases.map((p) => p.id); const { count } = await supabase .from('activities') .select('*', { count: 'exact', head: true }) .in('phase_id', phaseIds); totalActivitiesInCategory = count || 0; } } // Step 5: Group by employee and calculate performance const employeeMap = new Map< string, { employee_id: string; employee_name: string; kandang_id: string; kandang_name: string; completed_count: number; total_count: number; last_activity_date: string | null; } >(); assignmentsData.forEach((assignment) => { const task = tasks.find((t) => t.id === assignment.task_id); if (!task) return; const checklist = checklistsData.find( (c) => c.id === task.checklist_id ); if (!checklist) return; const employeeId = assignment.employee_id; const employeeName = assignment.employee?.name || 'Unknown'; const kandangId = checklist.kandang_id; const kandangName = checklist.kandang?.name || 'Unknown'; if (!employeeMap.has(employeeId)) { employeeMap.set(employeeId, { employee_id: employeeId, employee_name: employeeName, kandang_id: kandangId, kandang_name: kandangName, completed_count: 0, total_count: 0, last_activity_date: null, }); } const empData = employeeMap.get(employeeId)!; empData.total_count += 1; if (assignment.checked) { empData.completed_count += 1; } // Update last activity date if (assignment.updated_at) { if ( !empData.last_activity_date || assignment.updated_at > empData.last_activity_date ) { empData.last_activity_date = assignment.updated_at; } } }); // Step 6: Convert to array and add calculated fields const performanceData: EmployeePerformance[] = Array.from( employeeMap.values() ).map((emp) => { // Use total activities in category if category is selected, otherwise use employee's assigned count const totalActivities = categoryFilter !== 'ALL' && totalActivitiesInCategory > 0 ? totalActivitiesInCategory : emp.total_count; return { employee_id: emp.employee_id, employee_name: emp.employee_name, kandang_id: emp.kandang_id, kandang_name: emp.kandang_name, total_activities_in_category: totalActivities, completed_activities: emp.completed_count, completion_rate: totalActivities > 0 ? Math.round((emp.completed_count / totalActivities) * 100) : 0, last_activity_date: emp.last_activity_date, color: kandangColorMap[emp.kandang_id] || '#0069e0', }; }); // Sort by employee name performanceData.sort((a, b) => a.employee_name.localeCompare(b.employee_name) ); setEmployeePerformance(performanceData); } catch (error) { console.error('Error fetching employee performance:', error); toast.error('Terjadi kesalahan saat memuat data'); } finally { setLoading(false); } }; const formatDate = (dateString: string | null) => { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric', }); }; const hasFilters = dateFrom && dateTo; // Prepare chart data const chartData = employeePerformance.map((emp) => ({ name: emp.employee_name, completed: emp.completed_activities, remaining: emp.total_activities_in_category - emp.completed_activities, total: emp.total_activities_in_category, color: emp.color, kandang: emp.kandang_name, })); return (
{/* Page Title */}

Dashboard

Performance tracking per ABK

{/* Filters Card */} Filter
{ setDateFrom(from); setDateTo(to); }} />
{/* Performance Chart Card */}
Performance Overview - Aktivitas per ABK

Aktivitas yang telah diselesaikan vs total aktivitas di kategori

{!hasFilters ? ( // Empty state - no filters

Pilih Filter Terlebih Dahulu

Silakan pilih{' '} Tanggal Dari dan{' '} Tanggal Sampai untuk melihat performance ABK.

) : loading ? (
Memuat data...
) : employeePerformance.length === 0 ? (

Tidak Ada Data

Tidak ada data aktivitas ABK pada periode yang dipilih.

) : ( { if (name === 'completed') return [value, 'Aktivitas Selesai']; if (name === 'remaining') return [value, 'Aktivitas Tersisa']; return [value, name]; }} /> { if (value === 'completed') return 'Aktivitas Selesai'; if (value === 'remaining') return 'Aktivitas Tersisa'; return value; }} /> {chartData.map((entry, index) => ( ))} {chartData.map((entry, index) => ( ))} )}
{/* Employee Tracking Table */} {hasFilters && employeePerformance.length > 0 && ( Tracking ABK

Detail performance masing-masing ABK

{employeePerformance.map((emp, index) => ( ))}
Nama ABK Kandang Total Aktivitas Aktivitas Selesai Aktivitas Tersisa Completion Rate Last Activity
{emp.employee_name} {emp.kandang_name} {emp.total_activities_in_category} {emp.completed_activities} {emp.total_activities_in_category - emp.completed_activities}
{emp.completion_rate}%
{formatDate(emp.last_activity_date)}
)}
); }