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
-
Export CSV
-
+ */}
{/* Main Card */}
@@ -395,22 +321,100 @@ export function DailyChecklistReportsContent() {
{/* Filters Section */}
-
Periode Tanggal
-
- {
- setDateFrom(from);
- setDateTo(to);
- }}
- />
-
+
Bulan
+
+
+
+
+
+ {MONTH_OPTIONS.map((bulan) => (
+
+ {bulan.label}
+
+ ))}
+
+
+
+
+ Tahun
+
+
+
+
+
+ {YEAR_OPTIONS.map((tahun) => (
+
+ {tahun.label}
+
+ ))}
+
+
+
+
+ Area
+
+
+
+
+
+ Semua Area
+ {areaOptions.map((area) => (
+
+ {area.label}
+
+ ))}
+
+
+
+
+ Lokasi
+
+
+
+
+
+ Semua Lokasi
+ {locationOptions.map((location) => (
+
+ {location.label}
+
+ ))}
+
+
-
Kandang
-
+
Semua Kandang
- {kandangList.map((kandang) => (
-
- {kandang.name}
+ {kandangOptions.map((kandang) => (
+
+ {kandang.label}
))}
-
- Status
-
+ Phase
+
-
+
- {STATUS_OPTIONS.map((option) => (
-
- {option.label}
+ Semua Phase
+ {phaseOptions.map((phase) => (
+
+ {phase.label}
))}
-
-
Cari
-
- setSearchText(e.target.value)}
- className='border-gray-200 pl-9'
- />
-
-
+
ABK
+
+
+
+
+
+ Semua ABK
+ {employeeOptions.map((employee) => (
+
+ {employee.label}
+
+ ))}
+
+
{/* Reports Table */}
- {loading ? (
-
- Memuat data...
-
- ) : filteredReportList.length > 0 ? (
-
-
-
-
-
- Tanggal
-
-
- Kandang
-
-
- Kategori
-
-
- Status
-
-
- Phase
-
-
- Aktivitas
-
-
- ABK
-
-
- Progress
-
-
- Updated At
-
-
- Aksi
-
-
-
-
- {filteredReportList.map((item, index) => (
-
-
- {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)}
-
-
-
-
- handleViewDetail(item.checklist_id)
- }
- className='border-gray-200 text-gray-700 hover:bg-gray-50'
- >
-
- Detail
-
-
-
-
- ))}
-
-
-
- ) : (
-
- {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',
+ }}
+ />