mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
feat: integrate Daily Checklist Report to API
This commit is contained in:
@@ -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<SubmissionReportItem[]>([]);
|
||||
const [filteredReportList, setFilteredReportList] = useState<
|
||||
SubmissionReportItem[]
|
||||
>([]);
|
||||
const currentMonth = useMemo(() => new Date().getMonth() + 1, []);
|
||||
const currentYear = useMemo(() => new Date().getFullYear(), []);
|
||||
|
||||
// Master data
|
||||
const [kandangList, setKandangList] = useState<Kandang[]>([]);
|
||||
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;
|
||||
const {
|
||||
data: reportResponse,
|
||||
isLoading: isLoadingReport,
|
||||
mutate: refreshReport,
|
||||
} = useSWR<
|
||||
BaseApiResponse<DailyChecklistReport[] | undefined>,
|
||||
AxiosError<BaseApiResponse>,
|
||||
SWRHttpKey
|
||||
>(
|
||||
`${DailyChecklistApi.basePath}/report${getTableFilterQueryString()}`,
|
||||
httpClientFetcher,
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
|
||||
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)) {
|
||||
uniquePhasesWithChecked.add(task.phase_id);
|
||||
}
|
||||
|
||||
const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
|
||||
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 progressPercent =
|
||||
totalPhasesInMaster && totalPhasesInMaster > 0
|
||||
? Math.round(
|
||||
(phasesWithCheckedCount / totalPhasesInMaster) * 100
|
||||
)
|
||||
: 0;
|
||||
const reportColumns: ColumnDef<DailyChecklistReport>[] = [
|
||||
{
|
||||
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 }) => (
|
||||
<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 {
|
||||
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,
|
||||
};
|
||||
})
|
||||
);
|
||||
// const exportToCSV = () => {
|
||||
// toast.info('Export CSV akan segera tersedia');
|
||||
// };
|
||||
|
||||
setReportList(enrichedData);
|
||||
} catch (error) {
|
||||
console.error('Error fetching reports:', error);
|
||||
toast.error('Terjadi kesalahan');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
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 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 locationChangeHandler = (value: string) => {
|
||||
updateFilter('location_id', value === 'ALL' ? '' : value);
|
||||
updateFilter('kandang_id', '');
|
||||
updateFilter('employee_id', '');
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case 'DRAFT':
|
||||
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 kandangChangeHandler = (value: string) => {
|
||||
updateFilter('kandang_id', value === 'ALL' ? '' : value);
|
||||
updateFilter('employee_id', '');
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
});
|
||||
const phaseChangeHandler = (value: string) => {
|
||||
updateFilter('phase_id', value);
|
||||
};
|
||||
|
||||
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 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');
|
||||
const employeeChangeHandler = (value: string) => {
|
||||
updateFilter('employee_id', value === 'ALL' ? '' : value);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -380,13 +306,13 @@ export function DailyChecklistReportsContent() {
|
||||
Laporan lengkap checklist harian
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
onClick={exportToCSV}
|
||||
className='bg-[#0069e0] hover:bg-[#0058c0] text-white'
|
||||
>
|
||||
<Download className='w-4 h-4 mr-2' />
|
||||
Export CSV
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
|
||||
{/* Main Card */}
|
||||
@@ -395,22 +321,100 @@ export function DailyChecklistReportsContent() {
|
||||
{/* 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>
|
||||
<Label>Periode Tanggal</Label>
|
||||
<div className='mt-1.5'>
|
||||
<DateRangePicker
|
||||
dateFrom={dateFrom}
|
||||
dateTo={dateTo}
|
||||
onDateChange={(from, to) => {
|
||||
setDateFrom(from);
|
||||
setDateTo(to);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor='bulan-filter-report'>Bulan</Label>
|
||||
<Select
|
||||
value={tableFilterState.bulan}
|
||||
onValueChange={monthChangeHandler}
|
||||
>
|
||||
<SelectTrigger
|
||||
id='bulan-filter-report'
|
||||
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>
|
||||
<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>
|
||||
<Label htmlFor='kandang-filter-report'>Kandang</Label>
|
||||
<Select value={kandangFilter} onValueChange={setKandangFilter}>
|
||||
<Select
|
||||
value={tableFilterState.kandang_id}
|
||||
onValueChange={kandangChangeHandler}
|
||||
>
|
||||
<SelectTrigger
|
||||
id='kandang-filter-report'
|
||||
className='mt-1.5 border-gray-200'
|
||||
@@ -419,168 +423,105 @@ export function DailyChecklistReportsContent() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='ALL'>Semua Kandang</SelectItem>
|
||||
{kandangList.map((kandang) => (
|
||||
<SelectItem key={kandang.id} value={kandang.id}>
|
||||
{kandang.name}
|
||||
{kandangOptions.map((kandang) => (
|
||||
<SelectItem
|
||||
key={kandang.value}
|
||||
value={String(kandang.value)}
|
||||
>
|
||||
{kandang.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor='status-filter-report'>Status</Label>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<Label htmlFor='phase-filter-report'>Phase</Label>
|
||||
<Select
|
||||
value={tableFilterState.phase_id}
|
||||
onValueChange={phaseChangeHandler}
|
||||
>
|
||||
<SelectTrigger
|
||||
id='status-filter-report'
|
||||
id='phase-filter-report'
|
||||
className='mt-1.5 border-gray-200'
|
||||
>
|
||||
<SelectValue placeholder='Semua Status' />
|
||||
<SelectValue placeholder='Semua Phase' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{STATUS_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
<SelectItem value='ALL'>Semua Phase</SelectItem>
|
||||
{phaseOptions.map((phase) => (
|
||||
<SelectItem key={phase.value} value={String(phase.value)}>
|
||||
{phase.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor='search-text-report'>Cari</Label>
|
||||
<div className='relative mt-1.5'>
|
||||
<Input
|
||||
id='search-text-report'
|
||||
type='text'
|
||||
placeholder='Kandang / Kategori...'
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
className='border-gray-200 pl-9'
|
||||
/>
|
||||
<Search className='absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400' />
|
||||
</div>
|
||||
<Label htmlFor='employee-filter-report'>ABK</Label>
|
||||
<Select
|
||||
value={tableFilterState.employee_id}
|
||||
onValueChange={employeeChangeHandler}
|
||||
>
|
||||
<SelectTrigger
|
||||
id='employee-filter-report'
|
||||
className='mt-1.5 border-gray-200'
|
||||
>
|
||||
<SelectValue placeholder='Semua ABK' />
|
||||
</SelectTrigger>
|
||||
<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>
|
||||
|
||||
{/* Reports Table */}
|
||||
{loading ? (
|
||||
<div className='text-center py-12 text-gray-500'>
|
||||
Memuat data...
|
||||
</div>
|
||||
) : 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'
|
||||
<Table<DailyChecklistReport>
|
||||
data={
|
||||
isResponseSuccess(reportResponse)
|
||||
? reportResponse.data || []
|
||||
: []
|
||||
}
|
||||
>
|
||||
<td className='py-3 px-4 text-sm text-gray-900'>
|
||||
{formatDate(item.date)}
|
||||
</td>
|
||||
<td className='py-3 px-4 text-sm text-gray-900'>
|
||||
{item.kandang_name}
|
||||
</td>
|
||||
<td className='py-3 px-4 text-sm text-gray-900'>
|
||||
{CATEGORY_LABELS[item.category] || item.category}
|
||||
</td>
|
||||
<td className='py-3 px-4'>
|
||||
{getStatusBadge(item.status)}
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center text-sm text-gray-900'>
|
||||
{item.total_phases}
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center text-sm text-gray-900'>
|
||||
{item.total_activities}
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center text-sm text-gray-900'>
|
||||
{item.total_employees}
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center'>
|
||||
<div className='flex items-center justify-center gap-2'>
|
||||
<div className='w-20 bg-gray-200 rounded-full h-2'>
|
||||
<div
|
||||
className='bg-[#0069e0] h-2 rounded-full transition-all'
|
||||
style={{ width: `${item.progress_percent}%` }}
|
||||
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',
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user