diff --git a/src/figma-make/components/pages/dashboard/Dashboard.tsx b/src/figma-make/components/pages/dashboard/Dashboard.tsx index 5d866ffc..f6d12d79 100644 --- a/src/figma-make/components/pages/dashboard/Dashboard.tsx +++ b/src/figma-make/components/pages/dashboard/Dashboard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Card, CardContent, @@ -15,7 +15,6 @@ import { 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, @@ -35,53 +34,17 @@ import { 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; -} +import useSWR from 'swr'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { DailyChecklistSummary } from '@/types/api/daily-checklist/daily-checklist'; +import { AxiosError } from 'axios'; +import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; +import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist'; +import { KandangApi } from '@/services/api/master-data'; +import { useSelect } from '@/components/input/SelectInput'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { formatDate } from '@/lib/helper'; const KANDANG_COLORS = [ '#0069e0', // Blue (primary) @@ -102,312 +65,65 @@ const CATEGORY_LABELS: { [key: string]: string } = { }; 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([]); + const { + data: summaryResponse, + isLoading: isLoadingSummary, + mutate: refreshSummary, + } = useSWR< + BaseApiResponse, + AxiosError, + SWRHttpKey + >( + dateFrom && dateTo + ? `${DailyChecklistApi.basePath}/summary?date_from=${dateFrom}&date_to=${dateTo}&kandang_id=${kandangFilter === 'ALL' ? '' : kandangFilter}&category=${categoryFilter === 'ALL' ? '' : categoryFilter}` + : '', + httpClientFetcher, + { + keepPreviousData: true, } - }, [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 { options: kandangOptions, isLoadingOptions: isLoadingKandangs } = + useSelect(KandangApi.basePath, 'id', 'name', 'search', { + page: '1', + limit: '100', }); - }; - const hasFilters = dateFrom && dateTo; + const kandangColorMap: { [key: string]: string } = {}; + (kandangOptions || []).forEach((k, index) => { + kandangColorMap[k.value] = KANDANG_COLORS[index % KANDANG_COLORS.length]; + }); - // Prepare chart data - const chartData = employeePerformance.map((emp) => ({ + const employeePerformance = isResponseSuccess(summaryResponse) + ? summaryResponse.data?.tracking_abk.map((abk) => { + return { + ...abk, + color: kandangColorMap[abk.kandang_id] || '#0069e0', + }; + }) + : []; + + 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, + completed: emp.activity_done, + remaining: emp.activity_left, + total: emp.total_activity, color: emp.color, kandang: emp.kandang_name, })); + const hasFilters = dateFrom && dateTo; + + if (summaryResponse && isResponseError(summaryResponse)) { + toast.error('Gagal memuat data: ' + summaryResponse.message); + } + return (
@@ -457,9 +173,12 @@ export function Dashboard() { Semua Kandang - {kandangList.map((kandang) => ( - - {kandang.name} + {kandangOptions.map((kandang) => ( + + {kandang.label} ))} @@ -482,9 +201,9 @@ export function Dashboard() { Semua Kategori - {categoryList.map((category) => ( - - {category.name} + {Object.keys(CATEGORY_LABELS).map((category) => ( + + {CATEGORY_LABELS[category]} ))} @@ -523,11 +242,11 @@ export function Dashboard() { melihat performance ABK.

- ) : loading ? ( + ) : isLoadingSummary ? (
Memuat data...
- ) : employeePerformance.length === 0 ? ( + ) : employeePerformance && employeePerformance.length === 0 ? (

@@ -582,7 +301,7 @@ export function Dashboard() { fill='#10B981' radius={[0, 0, 0, 0]} > - {chartData.map((entry, index) => ( + {chartData?.map((entry, index) => ( - {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) => ( - - - - - - - - + {hasFilters && + employeePerformance && + employeePerformance.length > 0 && ( + + + Tracking ABK +

+ Detail performance masing-masing ABK +

+
+ +
+
- 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)} -
+ + + + + + + + + - ))} - -
+ Nama ABK + + Kandang + + Total Aktivitas + + Aktivitas Selesai + + Aktivitas Tersisa + + Completion Rate + + Last Activity +
-
-
-
- )} + + + {employeePerformance?.map((emp, index) => ( + + + {emp.employee_name} + + + + {emp.kandang_name} + + + + {emp.total_activity} + + + {emp.activity_done} + + + {emp.activity_left} + + +
+
+
+
+ + {emp.completion_rate}% + +
+ + + {formatDate(emp.last_activity, 'DD MMM YYYY')} + + + ))} + + +
+ + + )}

);