From f46a0610f57477bb681e41f95931e42d087e05ea Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 12 Jan 2026 12:55:31 +0700 Subject: [PATCH] feat: integrate Daily Checklist Report to API --- .../reports/DailyChecklistReportsContent.tsx | 923 ++++++++---------- 1 file changed, 432 insertions(+), 491 deletions(-) diff --git a/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx b/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx index 71156f15..7d6383a0 100644 --- a/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx +++ b/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx @@ -1,13 +1,9 @@ 'use client'; -import { useState, useEffect } from 'react'; -import { Eye, Download, Search } from 'lucide-react'; +import { useMemo } from 'react'; import { Card, CardContent } from '@/figma-make/components/base/card'; -import { Button } from '@/figma-make/components/base/button'; import { Badge } from '@/figma-make/components/base/badge'; -import { Input } from '@/figma-make/components/base/input'; import { Label } from '@/figma-make/components/base/label'; -import { DateRangePicker } from '@/figma-make/components/base/date-range-picker'; import { Select, SelectContent, @@ -16,357 +12,287 @@ import { SelectValue, } from '@/figma-make/components/base/select'; import { toast } from 'sonner'; -import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase'; import { useRouter } from 'next/navigation'; +import { useSelect } from '@/components/input/SelectInput'; +import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data'; +import useSWR from 'swr'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { DailyChecklistReport } 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 Table from '@/components/Table'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { cn } from '@/lib/helper'; +import { ColumnDef } from '@tanstack/react-table'; +import { report } from 'process'; +import { PhaseApi } from '@/services/api/daily-checklist/phase'; +import { EmployeeApi } from '@/services/api/daily-checklist/employee'; -interface SubmissionReportItem { - checklist_id: string; - date: string; - kandang_id: string; - kandang_name: string; - category: string; - status: string; - progress_percent: number; - total_phases: number; - total_activities: number; - total_employees: number; - updated_at: string; -} - -interface Kandang { - id: string; - name: string; -} - -interface ReportQueryResult { - id: string; - date: string; - kandang_id: string; - category: string; - status: string; - updated_at: string; - kandang: { - id: string; - name: string; - } | null; -} - -const STATUS_OPTIONS = [ - { value: 'ALL', label: 'Semua Status' }, - { value: 'DRAFT', label: 'Draft' }, - { value: 'SUBMITTED', label: 'Submitted' }, - { value: 'APPROVED', label: 'Approved' }, - { value: 'REJECTED', label: 'Rejected' }, +const MONTH_OPTIONS = [ + { value: '1', label: 'Januari' }, + { value: '2', label: 'Februari' }, + { value: '3', label: 'Maret' }, + { value: '4', label: 'April' }, + { value: '5', label: 'Mei' }, + { value: '6', label: 'Juni' }, + { value: '7', label: 'Juli' }, + { value: '8', label: 'Agustus' }, + { value: '9', label: 'September' }, + { value: '10', label: 'Oktober' }, + { value: '11', label: 'November' }, + { value: '12', label: 'Desember' }, ]; -const CATEGORY_LABELS: { [key: string]: string } = { - pullet_open: 'Pullet Open', - pullet_close: 'Pullet Close', - produksi_open: 'Produksi Open', - produksi_close: 'Produksi Close', -}; +const YEAR_OPTIONS = [ + { value: '2027', label: '2027' }, + { value: '2026', label: '2026' }, + { value: '2025', label: '2025' }, + { value: '2024', label: '2024' }, + { value: '2023', label: '2023' }, + { value: '2022', label: '2022' }, + { value: '2021', label: '2021' }, + { value: '2020', label: '2020' }, +]; + +// const CATEGORY_LABELS: { [key: string]: string } = { +// pullet_open: 'Pullet Open', +// pullet_close: 'Pullet Close', +// produksi_open: 'Produksi Open', +// produksi_close: 'Produksi Close', +// }; export function DailyChecklistReportsContent() { const router = useRouter(); - const [loading, setLoading] = useState(true); - // Report State - const [reportList, setReportList] = useState([]); - const [filteredReportList, setFilteredReportList] = useState< - SubmissionReportItem[] - >([]); + const currentMonth = useMemo(() => new Date().getMonth() + 1, []); + const currentYear = useMemo(() => new Date().getFullYear(), []); - // Master data - const [kandangList, setKandangList] = useState([]); + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + bulan: currentMonth.toString(), + tahun: currentYear.toString(), + area_id: '', + location_id: '', + kandang_id: '', + employee_id: '', + phase_id: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + bulan: 'bulan', + tahun: 'tahun', + area_id: 'area_id', + location_id: 'location_id', + kandang_id: 'kandang_id', + employee_id: 'employee_id', + phase_id: 'phase_id', + }, + }); - // Filters - const [statusFilter, setStatusFilter] = useState('ALL'); - const [kandangFilter, setKandangFilter] = useState('ALL'); - const [searchText, setSearchText] = useState(''); - const [dateFrom, setDateFrom] = useState(''); - const [dateTo, setDateTo] = useState(''); - - useEffect(() => { - fetchKandangList(); - fetchReports(); - }, []); - - useEffect(() => { - applyFilters(); - }, [reportList, statusFilter, kandangFilter, searchText, dateFrom, dateTo]); - - const fetchKandangList = async () => { - if (!isSupabaseConfigured()) return; - - try { - const { data, error } = await supabase - .from('kandang') - .select('id, name') - .order('name', { ascending: true }); - - if (error) { - console.error('Error fetching kandang:', error); - return; - } - - setKandangList(data || []); - } catch (error) { - console.error('Error fetching kandang:', error); + const { + data: reportResponse, + isLoading: isLoadingReport, + mutate: refreshReport, + } = useSWR< + BaseApiResponse, + AxiosError, + SWRHttpKey + >( + `${DailyChecklistApi.basePath}/report${getTableFilterQueryString()}`, + httpClientFetcher, + { + keepPreviousData: true, } - }; + ); - const fetchReports = async () => { - if (!isSupabaseConfigured()) { - console.warn('Supabase not configured'); - setLoading(false); - return; + const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect( + AreaApi.basePath, + 'id', + 'name', + 'search', + { + page: '1', + limit: '100', } + ); - try { - setLoading(true); - - // Fetch checklists directly from daily_checklists table - const { data: checklists, error } = await supabase - .from('daily_checklists') - .select( - ` - id, - date, - kandang_id, - category, - status, - updated_at, - kandang:kandang_id ( - id, - name - ) - ` - ) - .order('date', { ascending: false }) - .order('updated_at', { ascending: false }); - - if (error) { - console.error('Error fetching reports:', error); - toast.error('Gagal memuat data reports'); - return; - } - - // Enrich data with calculations - const enrichedData = await Promise.all( - ((checklists as unknown as ReportQueryResult[]) || []) - .filter((checklist) => checklist.id) - .map(async (checklist) => { - // Count phases - const { count: phaseCount } = await supabase - .from('daily_checklist_phases') - .select('*', { count: 'exact', head: true }) - .eq('checklist_id', checklist.id); - - // Count activities (tasks) - const { count: activityCount } = await supabase - .from('daily_checklist_activity_tasks') - .select('*', { count: 'exact', head: true }) - .eq('checklist_id', checklist.id); - - // Count unique employees - const { data: tasks } = await supabase - .from('daily_checklist_activity_tasks') - .select('id') - .eq('checklist_id', checklist.id); - - const taskIds = (tasks || []).map((t) => t.id); - let uniqueEmployees = new Set(); - - if (taskIds.length > 0) { - const { data: assignments } = await supabase - .from('daily_checklist_activity_task_assignments') - .select('employee_id') - .in('task_id', taskIds); - - uniqueEmployees = new Set( - (assignments || []).map((a) => a.employee_id) - ); - } - - // ✅ Calculate progress based on phase coverage - const { count: totalPhasesInMaster } = await supabase - .from('phases') - .select('*', { count: 'exact', head: true }) - .eq('category_id', checklist.category); - - const { data: checklistTasks } = await supabase - .from('daily_checklist_activity_tasks') - .select('id, phase_id') - .eq('checklist_id', checklist.id); - - const checklistTaskIds = (checklistTasks || []).map((t) => t.id); - const uniquePhasesWithChecked = new Set(); - - if (checklistTaskIds.length > 0) { - const { data: checkedAssignments } = await supabase - .from('daily_checklist_activity_task_assignments') - .select('task_id') - .in('task_id', checklistTaskIds) - .eq('checked', true); - - if (checkedAssignments && checkedAssignments.length > 0) { - const checkedTaskIds = new Set( - checkedAssignments.map((a) => a.task_id) - ); - checklistTasks?.forEach((task) => { - if (checkedTaskIds.has(task.id)) { - uniquePhasesWithChecked.add(task.phase_id); - } - }); - } - } - - const phasesWithCheckedCount = uniquePhasesWithChecked.size; - const progressPercent = - totalPhasesInMaster && totalPhasesInMaster > 0 - ? Math.round( - (phasesWithCheckedCount / totalPhasesInMaster) * 100 - ) - : 0; - - return { - checklist_id: checklist.id, - date: checklist.date, - kandang_id: checklist.kandang_id, - kandang_name: checklist.kandang?.name || '-', - category: checklist.category, - status: checklist.status, - progress_percent: progressPercent, - total_phases: phaseCount || 0, - total_activities: activityCount || 0, - total_employees: uniqueEmployees.size, - updated_at: checklist.updated_at, - }; - }) - ); - - setReportList(enrichedData); - } catch (error) { - console.error('Error fetching reports:', error); - toast.error('Terjadi kesalahan'); - } finally { - setLoading(false); - } - }; - - const applyFilters = () => { - let filtered = [...reportList]; - - if (statusFilter && statusFilter !== 'ALL') { - filtered = filtered.filter((item) => item.status === statusFilter); - } - - if (kandangFilter && kandangFilter !== 'ALL') { - filtered = filtered.filter((item) => item.kandang_id === kandangFilter); - } - - if (searchText) { - filtered = filtered.filter( - (item) => - item.kandang_name.toLowerCase().includes(searchText.toLowerCase()) || - item.category.toLowerCase().includes(searchText.toLowerCase()) || - (CATEGORY_LABELS[item.category] || '') - .toLowerCase() - .includes(searchText.toLowerCase()) - ); - } - - if (dateFrom) { - filtered = filtered.filter( - (item) => new Date(item.date) >= new Date(dateFrom) - ); - } - if (dateTo) { - filtered = filtered.filter( - (item) => new Date(item.date) <= new Date(dateTo) - ); - } - - setFilteredReportList(filtered); - }; - - const getStatusBadge = (status: string) => { - switch (status) { - case 'DRAFT': - return ( - - Draft - - ); - case 'SUBMITTED': - return ( - - Submitted - - ); - case 'APPROVED': - return ( - - Approved - - ); - case 'REJECTED': - return ( - - Rejected - - ); - default: - return ( - - {status} - - ); - } - }; - - const formatDate = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleDateString('id-ID', { - day: '2-digit', - month: 'short', - year: 'numeric', + const { options: locationOptions, isLoadingOptions: isLoadingLocations } = + useSelect(LocationApi.basePath, 'id', 'name', 'search', { + page: '1', + limit: '100', + area_id: tableFilterState.area_id, }); - }; - const formatDateTime = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleString('id-ID', { - day: '2-digit', - month: 'short', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', + const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } = + useSelect(KandangApi.basePath, 'id', 'name', 'search', { + page: '1', + limit: '100', + area_id: tableFilterState.area_id, + location_id: tableFilterState.location_id, }); + + const { options: phaseOptions, isLoadingOptions: isLoadingPhases } = + useSelect(PhaseApi.basePath, 'id', 'name', 'search', { + page: '1', + limit: '100', + }); + + const { options: employeeOptions, isLoadingOptions: isLoadingEmployees } = + useSelect(EmployeeApi.basePath, 'id', 'name', 'search', { + page: '1', + limit: '500', + kandang_id: tableFilterState.kandang_id, + }); + + const currentMonthMaxDay = new Date( + Number(tableFilterState.tahun), + Number(tableFilterState.bulan), + 0 + ).getDate(); + + const reportDateColumns: ColumnDef[] = []; + + if (isResponseSuccess(reportResponse) && reportResponse.data) { + for (let dateNumber = 1; dateNumber <= currentMonthMaxDay; dateNumber++) { + reportDateColumns.push({ + accessorKey: `daily_activities[${dateNumber}]`, + header: `${dateNumber}`, + enableSorting: false, + cell: ({ row }) => row.original.daily_activities[dateNumber], + }); + } + } + + const reportColumns: ColumnDef[] = [ + { + accessorKey: 'area', + header: 'Area', + enableSorting: false, + cell: ({ row }) => row.original.area.name, + }, + { + accessorKey: 'farm', + header: 'Farm', + enableSorting: false, + cell: ({ row }) => row.original.farm.name, + }, + { + accessorKey: 'kandang', + header: 'Kandang', + enableSorting: false, + cell: ({ row }) => row.original.kandang.name, + }, + { + accessorKey: 'abk', + header: 'ABK', + enableSorting: false, + cell: ({ row }) => ( + {row.original.abk.name} + ), + }, + { + accessorKey: 'phase', + header: 'Phase', + enableSorting: false, + cell: ({ row }) => row.original.phase, + }, + { + header: `Tanggal - ${MONTH_OPTIONS[Number(tableFilterState.bulan) - 1].label} - ${tableFilterState.tahun}`, + columns: reportDateColumns, + }, + { + accessorKey: 'summary.total_checklist', + header: 'Total Checklist', + enableSorting: false, + }, + { + accessorKey: 'summary.jumlah_hari_efektif', + header: 'Jumlah Hari Efektif', + enableSorting: false, + }, + { + accessorKey: 'summary.abk_percentage', + header: 'ABK (%)', + enableSorting: false, + }, + { + accessorKey: 'summary.kandang_percentage', + header: 'Kandang (%)', + enableSorting: false, + }, + { + header: 'Kategori', + columns: [ + { + accessorKey: 'summary.kategori.kurang', + header: 'Kurang', + enableSorting: false, + cell: ({ row }) => ( + + {row.original.summary.kategori.kurang} + + ), + }, + { + accessorKey: 'summary.kategori.cukup', + header: 'Cukup', + enableSorting: false, + cell: ({ row }) => row.original.summary.kategori.cukup, + }, + { + accessorKey: 'summary.kategori.baik', + header: 'Baik', + enableSorting: false, + cell: ({ row }) => ( + + {row.original.summary.kategori.baik} + + ), + }, + ], + }, + ]; + + // const exportToCSV = () => { + // toast.info('Export CSV akan segera tersedia'); + // }; + + const monthChangeHandler = (value: string) => updateFilter('bulan', value); + const yearChangeHandler = (value: string) => updateFilter('tahun', value); + + const areaChangeHandler = (value: string) => { + updateFilter('area_id', value === 'ALL' ? '' : value); + updateFilter('location_id', ''); + updateFilter('kandang_id', ''); + updateFilter('employee_id', ''); }; - const handleViewDetail = (checklistId: string) => { - // Navigate to detail page (same as List Daily Checklist) - router.push(`/list-daily-checklist/detail?checklistId=${checklistId}`); + const locationChangeHandler = (value: string) => { + updateFilter('location_id', value === 'ALL' ? '' : value); + updateFilter('kandang_id', ''); + updateFilter('employee_id', ''); }; - const exportToCSV = () => { - toast.info('Export CSV akan segera tersedia'); + const kandangChangeHandler = (value: string) => { + updateFilter('kandang_id', value === 'ALL' ? '' : value); + updateFilter('employee_id', ''); + }; + + const phaseChangeHandler = (value: string) => { + updateFilter('phase_id', value); + }; + + const employeeChangeHandler = (value: string) => { + updateFilter('employee_id', value === 'ALL' ? '' : value); }; return ( @@ -380,13 +306,13 @@ export function DailyChecklistReportsContent() { Laporan lengkap checklist harian

- + */} {/* Main Card */} @@ -395,22 +321,100 @@ export function DailyChecklistReportsContent() { {/* Filters Section */}
- -
- { - setDateFrom(from); - setDateTo(to); - }} - /> -
+ + +
+
+ + +
+
+ + +
+
+ +
-
- Semua Kandang - {kandangList.map((kandang) => ( - - {kandang.name} + {kandangOptions.map((kandang) => ( + + {kandang.label} ))}
-
- - - + - {STATUS_OPTIONS.map((option) => ( - - {option.label} + Semua Phase + {phaseOptions.map((phase) => ( + + {phase.label} ))}
-
- -
- setSearchText(e.target.value)} - className='border-gray-200 pl-9' - /> - -
+ +
{/* Reports Table */} - {loading ? ( -
- Memuat data... -
- ) : filteredReportList.length > 0 ? ( -
- - - - - - - - - - - - - - - - - {filteredReportList.map((item, index) => ( - - - - - - - - - - - - - ))} - -
- Tanggal - - Kandang - - Kategori - - Status - - Phase - - Aktivitas - - ABK - - Progress - - Updated At - - Aksi -
- {formatDate(item.date)} - - {item.kandang_name} - - {CATEGORY_LABELS[item.category] || item.category} - - {getStatusBadge(item.status)} - - {item.total_phases} - - {item.total_activities} - - {item.total_employees} - -
-
-
-
- - {item.progress_percent}% - -
-
- {formatDateTime(item.updated_at)} - -
- -
-
-
- ) : ( -
- {searchText || - dateFrom || - dateTo || - statusFilter !== 'ALL' || - kandangFilter !== 'ALL' - ? 'Tidak ada data yang sesuai dengan filter' - : 'Belum ada data checklist'} -
- )} + + data={ + isResponseSuccess(reportResponse) + ? reportResponse.data || [] + : [] + } + columns={reportColumns} + isLoading={isLoadingReport} + pageSize={tableFilterState.pageSize} + onPageSizeChange={setPageSize} + rowOptions={[10, 20, 50, 100]} + page={ + isResponseSuccess(reportResponse) + ? reportResponse?.meta?.page + : 0 + } + totalItems={ + isResponseSuccess(reportResponse) + ? reportResponse?.meta?.total_results + : 0 + } + onPageChange={setPage} + className={{ + containerClassName: cn({ + 'w-full mb-20': + isResponseSuccess(reportResponse) && + reportResponse?.data?.length === 0, + }), + tableWrapperClassName: + 'overflow-x-auto border border-solid border-base-content/10 rounded-none', + headerRowClassName: 'bg-gray-50/50', + headerColumnClassName: + 'text-left py-3.5 px-6 text-sm font-semibold text-gray-700 border-x border-base-content/10', + bodyColumnClassName: + 'px-4 py-3 text-base-content border-x border-base-content/10', + paginationClassName: 'px-4', + }} + />