feat: integrate Daily Checklist Report to API

This commit is contained in:
ValdiANS
2026-01-12 12:55:31 +07:00
parent a8c12d0c92
commit f46a0610f5
@@ -1,13 +1,9 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useMemo } from 'react';
import { Eye, Download, Search } from 'lucide-react';
import { Card, CardContent } from '@/figma-make/components/base/card'; 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 { Badge } from '@/figma-make/components/base/badge';
import { Input } from '@/figma-make/components/base/input';
import { Label } from '@/figma-make/components/base/label'; import { Label } from '@/figma-make/components/base/label';
import { DateRangePicker } from '@/figma-make/components/base/date-range-picker';
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -16,357 +12,287 @@ import {
SelectValue, SelectValue,
} from '@/figma-make/components/base/select'; } from '@/figma-make/components/base/select';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase';
import { useRouter } from 'next/navigation'; 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 { const MONTH_OPTIONS = [
checklist_id: string; { value: '1', label: 'Januari' },
date: string; { value: '2', label: 'Februari' },
kandang_id: string; { value: '3', label: 'Maret' },
kandang_name: string; { value: '4', label: 'April' },
category: string; { value: '5', label: 'Mei' },
status: string; { value: '6', label: 'Juni' },
progress_percent: number; { value: '7', label: 'Juli' },
total_phases: number; { value: '8', label: 'Agustus' },
total_activities: number; { value: '9', label: 'September' },
total_employees: number; { value: '10', label: 'Oktober' },
updated_at: string; { value: '11', label: 'November' },
} { value: '12', label: 'Desember' },
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 CATEGORY_LABELS: { [key: string]: string } = { const YEAR_OPTIONS = [
pullet_open: 'Pullet Open', { value: '2027', label: '2027' },
pullet_close: 'Pullet Close', { value: '2026', label: '2026' },
produksi_open: 'Produksi Open', { value: '2025', label: '2025' },
produksi_close: 'Produksi Close', { 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() { export function DailyChecklistReportsContent() {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(true);
// Report State const currentMonth = useMemo(() => new Date().getMonth() + 1, []);
const [reportList, setReportList] = useState<SubmissionReportItem[]>([]); const currentYear = useMemo(() => new Date().getFullYear(), []);
const [filteredReportList, setFilteredReportList] = useState<
SubmissionReportItem[]
>([]);
// Master data const {
const [kandangList, setKandangList] = useState<Kandang[]>([]); 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 {
const [statusFilter, setStatusFilter] = useState('ALL'); data: reportResponse,
const [kandangFilter, setKandangFilter] = useState('ALL'); isLoading: isLoadingReport,
const [searchText, setSearchText] = useState(''); mutate: refreshReport,
const [dateFrom, setDateFrom] = useState(''); } = useSWR<
const [dateTo, setDateTo] = useState(''); BaseApiResponse<DailyChecklistReport[] | undefined>,
AxiosError<BaseApiResponse>,
useEffect(() => { SWRHttpKey
fetchKandangList(); >(
fetchReports(); `${DailyChecklistApi.basePath}/report${getTableFilterQueryString()}`,
}, []); httpClientFetcher,
{
useEffect(() => { keepPreviousData: true,
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 fetchReports = async () => {
if (!isSupabaseConfigured()) {
console.warn('Supabase not configured');
setLoading(false);
return;
}
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<string>();
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)
); );
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
AreaApi.basePath,
'id',
'name',
'search',
{
page: '1',
limit: '100',
} }
// ✅ 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<string>();
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)) { const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
uniquePhasesWithChecked.add(task.phase_id); useSelect(LocationApi.basePath, 'id', 'name', 'search', {
} page: '1',
limit: '100',
area_id: tableFilterState.area_id,
});
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<DailyChecklistReport>[] = [];
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 phasesWithCheckedCount = uniquePhasesWithChecked.size; const reportColumns: ColumnDef<DailyChecklistReport>[] = [
const progressPercent = {
totalPhasesInMaster && totalPhasesInMaster > 0 accessorKey: 'area',
? Math.round( header: 'Area',
(phasesWithCheckedCount / totalPhasesInMaster) * 100 enableSorting: false,
) cell: ({ row }) => row.original.area.name,
: 0; },
{
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 }) => (
<span className='text-nowrap'>{row.original.abk.name}</span>
),
},
{
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 }) => (
<span className='text-red-400'>
{row.original.summary.kategori.kurang}
</span>
),
},
{
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 }) => (
<span className='text-green-400'>
{row.original.summary.kategori.baik}
</span>
),
},
],
},
];
return { // const exportToCSV = () => {
checklist_id: checklist.id, // toast.info('Export CSV akan segera tersedia');
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); const monthChangeHandler = (value: string) => updateFilter('bulan', value);
} catch (error) { const yearChangeHandler = (value: string) => updateFilter('tahun', value);
console.error('Error fetching reports:', error);
toast.error('Terjadi kesalahan'); const areaChangeHandler = (value: string) => {
} finally { updateFilter('area_id', value === 'ALL' ? '' : value);
setLoading(false); updateFilter('location_id', '');
} updateFilter('kandang_id', '');
updateFilter('employee_id', '');
}; };
const applyFilters = () => { const locationChangeHandler = (value: string) => {
let filtered = [...reportList]; updateFilter('location_id', value === 'ALL' ? '' : value);
updateFilter('kandang_id', '');
if (statusFilter && statusFilter !== 'ALL') { updateFilter('employee_id', '');
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) => { const kandangChangeHandler = (value: string) => {
switch (status) { updateFilter('kandang_id', value === 'ALL' ? '' : value);
case 'DRAFT': updateFilter('employee_id', '');
return (
<Badge
variant='outline'
className='border-gray-300 text-gray-700 bg-white'
>
Draft
</Badge>
);
case 'SUBMITTED':
return (
<Badge
variant='outline'
className='border-orange-300 text-orange-700 bg-white'
>
Submitted
</Badge>
);
case 'APPROVED':
return (
<Badge
variant='outline'
className='border-green-300 text-green-700 bg-white'
>
Approved
</Badge>
);
case 'REJECTED':
return (
<Badge
variant='outline'
className='border-red-300 text-red-700 bg-white'
>
Rejected
</Badge>
);
default:
return (
<Badge
variant='outline'
className='border-gray-300 text-gray-700 bg-white'
>
{status}
</Badge>
);
}
}; };
const formatDate = (dateString: string) => { const phaseChangeHandler = (value: string) => {
const date = new Date(dateString); updateFilter('phase_id', value);
return date.toLocaleDateString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
});
}; };
const formatDateTime = (dateString: string) => { const employeeChangeHandler = (value: string) => {
const date = new Date(dateString); updateFilter('employee_id', value === 'ALL' ? '' : value);
return date.toLocaleString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const handleViewDetail = (checklistId: string) => {
// Navigate to detail page (same as List Daily Checklist)
router.push(`/list-daily-checklist/detail?checklistId=${checklistId}`);
};
const exportToCSV = () => {
toast.info('Export CSV akan segera tersedia');
}; };
return ( return (
@@ -380,13 +306,13 @@ export function DailyChecklistReportsContent() {
Laporan lengkap checklist harian Laporan lengkap checklist harian
</p> </p>
</div> </div>
<Button {/* <Button
onClick={exportToCSV} onClick={exportToCSV}
className='bg-[#0069e0] hover:bg-[#0058c0] text-white' className='bg-[#0069e0] hover:bg-[#0058c0] text-white'
> >
<Download className='w-4 h-4 mr-2' /> <Download className='w-4 h-4 mr-2' />
Export CSV Export CSV
</Button> </Button> */}
</div> </div>
{/* Main Card */} {/* Main Card */}
@@ -395,22 +321,100 @@ export function DailyChecklistReportsContent() {
{/* Filters Section */} {/* Filters Section */}
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 pb-6 border-b border-gray-200'> <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 pb-6 border-b border-gray-200'>
<div> <div>
<Label>Periode Tanggal</Label> <Label htmlFor='bulan-filter-report'>Bulan</Label>
<div className='mt-1.5'> <Select
<DateRangePicker value={tableFilterState.bulan}
dateFrom={dateFrom} onValueChange={monthChangeHandler}
dateTo={dateTo} >
onDateChange={(from, to) => { <SelectTrigger
setDateFrom(from); id='bulan-filter-report'
setDateTo(to); className='mt-1.5 border-gray-200'
}} >
/> <SelectValue placeholder='Semua Bulan' />
</SelectTrigger>
<SelectContent>
{MONTH_OPTIONS.map((bulan) => (
<SelectItem key={bulan.value} value={String(bulan.value)}>
{bulan.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
<div>
<Label htmlFor='tahun-filter-report'>Tahun</Label>
<Select
value={tableFilterState.tahun}
onValueChange={yearChangeHandler}
>
<SelectTrigger
id='tahun-filter-report'
className='mt-1.5 border-gray-200'
>
<SelectValue placeholder='Semua Tahun' />
</SelectTrigger>
<SelectContent>
{YEAR_OPTIONS.map((tahun) => (
<SelectItem key={tahun.value} value={String(tahun.value)}>
{tahun.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor='area-filter-report'>Area</Label>
<Select
value={tableFilterState.area_id}
onValueChange={areaChangeHandler}
>
<SelectTrigger
id='area-filter-report'
className='mt-1.5 border-gray-200'
>
<SelectValue placeholder='Semua Area' />
</SelectTrigger>
<SelectContent>
<SelectItem value='ALL'>Semua Area</SelectItem>
{areaOptions.map((area) => (
<SelectItem key={area.value} value={String(area.value)}>
{area.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor='location-filter-report'>Lokasi</Label>
<Select
value={tableFilterState.location_id}
onValueChange={locationChangeHandler}
>
<SelectTrigger
id='location-filter-report'
className='mt-1.5 border-gray-200'
>
<SelectValue placeholder='Semua Lokasi' />
</SelectTrigger>
<SelectContent>
<SelectItem value='ALL'>Semua Lokasi</SelectItem>
{locationOptions.map((location) => (
<SelectItem
key={location.value}
value={String(location.value)}
>
{location.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
<div> <div>
<Label htmlFor='kandang-filter-report'>Kandang</Label> <Label htmlFor='kandang-filter-report'>Kandang</Label>
<Select value={kandangFilter} onValueChange={setKandangFilter}> <Select
value={tableFilterState.kandang_id}
onValueChange={kandangChangeHandler}
>
<SelectTrigger <SelectTrigger
id='kandang-filter-report' id='kandang-filter-report'
className='mt-1.5 border-gray-200' className='mt-1.5 border-gray-200'
@@ -419,168 +423,105 @@ export function DailyChecklistReportsContent() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value='ALL'>Semua Kandang</SelectItem> <SelectItem value='ALL'>Semua Kandang</SelectItem>
{kandangList.map((kandang) => ( {kandangOptions.map((kandang) => (
<SelectItem key={kandang.id} value={kandang.id}> <SelectItem
{kandang.name} key={kandang.value}
value={String(kandang.value)}
>
{kandang.label}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div> <div>
<Label htmlFor='status-filter-report'>Status</Label> <Label htmlFor='phase-filter-report'>Phase</Label>
<Select value={statusFilter} onValueChange={setStatusFilter}> <Select
value={tableFilterState.phase_id}
onValueChange={phaseChangeHandler}
>
<SelectTrigger <SelectTrigger
id='status-filter-report' id='phase-filter-report'
className='mt-1.5 border-gray-200' className='mt-1.5 border-gray-200'
> >
<SelectValue placeholder='Semua Status' /> <SelectValue placeholder='Semua Phase' />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{STATUS_OPTIONS.map((option) => ( <SelectItem value='ALL'>Semua Phase</SelectItem>
<SelectItem key={option.value} value={option.value}> {phaseOptions.map((phase) => (
{option.label} <SelectItem key={phase.value} value={String(phase.value)}>
{phase.label}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div> <div>
<Label htmlFor='search-text-report'>Cari</Label> <Label htmlFor='employee-filter-report'>ABK</Label>
<div className='relative mt-1.5'> <Select
<Input value={tableFilterState.employee_id}
id='search-text-report' onValueChange={employeeChangeHandler}
type='text' >
placeholder='Kandang / Kategori...' <SelectTrigger
value={searchText} id='employee-filter-report'
onChange={(e) => setSearchText(e.target.value)} className='mt-1.5 border-gray-200'
className='border-gray-200 pl-9' >
/> <SelectValue placeholder='Semua ABK' />
<Search className='absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400' /> </SelectTrigger>
</div> <SelectContent>
<SelectItem value='ALL'>Semua ABK</SelectItem>
{employeeOptions.map((employee) => (
<SelectItem
key={employee.value}
value={String(employee.value)}
>
{employee.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
</div> </div>
{/* Reports Table */} {/* Reports Table */}
{loading ? ( <Table<DailyChecklistReport>
<div className='text-center py-12 text-gray-500'> data={
Memuat data... isResponseSuccess(reportResponse)
</div> ? reportResponse.data || []
) : filteredReportList.length > 0 ? ( : []
<div className='overflow-x-auto'>
<table className='w-full border border-gray-200 rounded-lg'>
<thead>
<tr className='bg-gray-50 border-b border-gray-200'>
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
Tanggal
</th>
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
Kandang
</th>
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
Kategori
</th>
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
Status
</th>
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
Phase
</th>
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
Aktivitas
</th>
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
ABK
</th>
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
Progress
</th>
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
Updated At
</th>
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
Aksi
</th>
</tr>
</thead>
<tbody>
{filteredReportList.map((item, index) => (
<tr
key={`${item.checklist_id}-${item.date}-${index}`}
className={
index % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'
} }
> columns={reportColumns}
<td className='py-3 px-4 text-sm text-gray-900'> isLoading={isLoadingReport}
{formatDate(item.date)} pageSize={tableFilterState.pageSize}
</td> onPageSizeChange={setPageSize}
<td className='py-3 px-4 text-sm text-gray-900'> rowOptions={[10, 20, 50, 100]}
{item.kandang_name} page={
</td> isResponseSuccess(reportResponse)
<td className='py-3 px-4 text-sm text-gray-900'> ? reportResponse?.meta?.page
{CATEGORY_LABELS[item.category] || item.category} : 0
</td> }
<td className='py-3 px-4'> totalItems={
{getStatusBadge(item.status)} isResponseSuccess(reportResponse)
</td> ? reportResponse?.meta?.total_results
<td className='py-3 px-4 text-center text-sm text-gray-900'> : 0
{item.total_phases} }
</td> onPageChange={setPage}
<td className='py-3 px-4 text-center text-sm text-gray-900'> className={{
{item.total_activities} containerClassName: cn({
</td> 'w-full mb-20':
<td className='py-3 px-4 text-center text-sm text-gray-900'> isResponseSuccess(reportResponse) &&
{item.total_employees} reportResponse?.data?.length === 0,
</td> }),
<td className='py-3 px-4 text-center'> tableWrapperClassName:
<div className='flex items-center justify-center gap-2'> 'overflow-x-auto border border-solid border-base-content/10 rounded-none',
<div className='w-20 bg-gray-200 rounded-full h-2'> headerRowClassName: 'bg-gray-50/50',
<div headerColumnClassName:
className='bg-[#0069e0] h-2 rounded-full transition-all' 'text-left py-3.5 px-6 text-sm font-semibold text-gray-700 border-x border-base-content/10',
style={{ width: `${item.progress_percent}%` }} bodyColumnClassName:
'px-4 py-3 text-base-content border-x border-base-content/10',
paginationClassName: 'px-4',
}}
/> />
</div>
<span className='text-sm text-gray-700 font-medium'>
{item.progress_percent}%
</span>
</div>
</td>
<td className='py-3 px-4 text-sm text-gray-600'>
{formatDateTime(item.updated_at)}
</td>
<td className='py-3 px-4'>
<div className='flex items-center justify-center'>
<Button
size='sm'
variant='outline'
onClick={() =>
handleViewDetail(item.checklist_id)
}
className='border-gray-200 text-gray-700 hover:bg-gray-50'
>
<Eye className='w-4 h-4 mr-1' />
Detail
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className='text-center py-12 text-gray-500'>
{searchText ||
dateFrom ||
dateTo ||
statusFilter !== 'ALL' ||
kandangFilter !== 'ALL'
? 'Tidak ada data yang sesuai dengan filter'
: 'Belum ada data checklist'}
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>