'use client'; import { useState, useEffect } from 'react'; import * as React from 'react'; import { ArrowLeft, CheckCircle, XCircle, AlertCircle } from 'lucide-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 { Label } from '@/figma-make/components/base/label'; import { Textarea } from '@/figma-make/components/base/textarea'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/figma-make/components/base/dialog'; import { toast } from 'sonner'; import { useRouter, useSearchParams } from 'next/navigation'; import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist'; import { isResponseError } from '@/lib/api-helper'; import Link from 'next/link'; import { Icon } from '@iconify/react'; import { Document } from '@/types/api/api-general'; interface ChecklistDetailRow { checklist_id: string; date: string; kandang_name: string; category: string; status: string; reject_reason: string | null; phase_id: string; phase_name: string; activity_id: string; activity_name: string; activity_description: string | null; time_type: string; employee_id: string; employee_name: string; checked: boolean; note: string | null; } interface ChecklistHeader { date: string; kandang_name: string; category: string; status: string; reject_reason: string | null; progress_percent: number; total_phases: number; total_activities: number; } interface PhaseGroup { phase: { id: string; name: string; }; timeGroups: { [timeType: string]: { activities: { id: string; name: string; description: string | null; employees: { id: string; name: string; checked: boolean; note: string | null; }[]; }[]; }; }; } interface ChecklistData { id: number; date: string; kandang_id: string; category: string; status: string; reject_reason: string | null; kandang: { id: number; name: string; }; } interface AssignmentQueryResult { task_id: number; employee_id: string; checked: boolean; note: string | null; employees: { id: number; name: string; } | null; } const CATEGORY_LABELS: { [key: string]: string } = { pullet_open: 'Pullet Open', pullet_close: 'Pullet Close', produksi_open: 'Produksi Open', produksi_close: 'Produksi Close', }; const TIME_TYPE_ORDER = ['Umum', 'Pagi', 'Siang', 'Sore', 'Malam']; const TIME_TYPE_LABELS: { [key: string]: string } = { Umum: 'Umum', Pagi: 'Pagi', Siang: 'Siang', Sore: 'Sore', Malam: 'Malam', }; export function DetailDailyChecklistContent() { const router = useRouter(); const searchParams = useSearchParams(); const checklistId = searchParams.get('checklistId'); const [loading, setLoading] = useState(true); const [header, setHeader] = useState(null); const [detailRows, setDetailRows] = useState([]); const [phaseGroups, setPhaseGroups] = useState([]); const [employees, setEmployees] = useState<{ id: string; name: string }[]>( [] ); const [documents, setDocuments] = useState([]); // Modals const [showApproveModal, setShowApproveModal] = useState(false); const [showRejectModal, setShowRejectModal] = useState(false); const [rejectReason, setRejectReason] = useState(''); const [actionLoading, setActionLoading] = useState(false); useEffect(() => { if (checklistId) { fetchChecklistDetail(); } }, [checklistId]); const fetchChecklistDetail = async () => { if (!checklistId) { console.warn('checklistId missing'); setLoading(false); return; } try { setLoading(true); const checklistDataRes = await DailyChecklistApi.getOneDailyChecklist(checklistId); if (isResponseError(checklistDataRes)) { console.error('Error fetching checklist:', checklistDataRes.message); toast.error('Data checklist tidak ditemukan'); router.push('/daily-checklist/list-daily-checklist'); return; } const rawDetailChecklist = checklistDataRes?.data; setDocuments(rawDetailChecklist?.document_urls || []); const checklistData = { id: rawDetailChecklist?.id, date: rawDetailChecklist?.date, kandang_id: rawDetailChecklist?.kandang.id, category: rawDetailChecklist?.category, status: rawDetailChecklist?.status, reject_reason: rawDetailChecklist?.reject_reason, kandang: rawDetailChecklist?.kandang, }; const tasks = rawDetailChecklist?.tasks; const castedChecklistData = checklistData as unknown as ChecklistData | null; if (!tasks || tasks.length === 0) { toast.info('Checklist belum memiliki aktivitas'); setHeader({ date: castedChecklistData?.date || '-', kandang_name: castedChecklistData?.kandang?.name || '-', category: castedChecklistData?.category || '-', status: castedChecklistData?.status || '-', reject_reason: castedChecklistData?.reject_reason || '-', progress_percent: 0, total_phases: 0, total_activities: 0, }); setLoading(false); return; } const assignments: { task_id: number; checked: boolean; note: string | null; employee: { id: number; name: string; }; }[] = []; tasks.forEach((task) => { task.assignments.forEach((assignment) => { assignments.push({ task_id: task.id, checked: assignment.checked, note: assignment.note, employee: assignment.employee, }); }); }); // ✅ Build detail rows from tasks and assignments const detailRows: ChecklistDetailRow[] = []; tasks.forEach((task) => { const taskAssignments = assignments.filter( (a) => a.task_id === task.id ); taskAssignments.forEach((assignment) => { detailRows.push({ checklist_id: checklistId, date: castedChecklistData?.date || '-', kandang_name: castedChecklistData?.kandang?.name || '-', category: castedChecklistData?.category || '-', status: castedChecklistData?.status || '-', reject_reason: castedChecklistData?.reject_reason || '-', phase_id: String(task.phase_id), phase_name: task.phase?.name || '-', activity_id: String(task.phase_activity_id), activity_name: task.phase_activity?.name || '-', activity_description: task.phase_activity?.description || null, time_type: task.time_type, employee_id: String(assignment.employee.id), employee_name: assignment.employee?.name || '-', checked: assignment.checked, note: assignment.note, }); }); }); if (detailRows.length === 0) { toast.info('Checklist belum memiliki assignment ABK'); setHeader({ date: castedChecklistData?.date || '-', kandang_name: castedChecklistData?.kandang?.name || '-', category: castedChecklistData?.category || '-', status: castedChecklistData?.status || '-', reject_reason: castedChecklistData?.reject_reason || '-', progress_percent: 0, total_phases: new Set(tasks.map((t) => t.phase_id)).size, total_activities: tasks.length, }); setLoading(false); return; } setDetailRows(detailRows); // Extract unique employees const uniqueEmployees = Array.from( new Map( detailRows.map((row) => [ row.employee_id, { id: row.employee_id, name: row.employee_name }, ]) ).values() ); uniqueEmployees.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base', }) ); setEmployees(uniqueEmployees); // Group data by Phase → Time Type → Activity groupDetailData(detailRows); // Calculate progress const totalCheckboxes = detailRows.length; const checkedCount = detailRows.filter((row) => row.checked).length; const progressPercent = totalCheckboxes > 0 ? Math.round((checkedCount / totalCheckboxes) * 100) : 0; const uniquePhases = new Set(detailRows.map((row) => row.phase_id)); const uniqueActivities = new Set( detailRows.map((row) => row.activity_id) ); setHeader({ date: castedChecklistData?.date || '-', kandang_name: castedChecklistData?.kandang?.name || '-', category: castedChecklistData?.category || '-', status: castedChecklistData?.status || '-', reject_reason: castedChecklistData?.reject_reason || '-', progress_percent: progressPercent, total_phases: uniquePhases.size, total_activities: uniqueActivities.size, }); } catch (error) { console.error('Error fetching checklist detail:', error); toast.error('Terjadi kesalahan'); router.push('/daily-checklist/list-daily-checklist'); } finally { setLoading(false); } }; const groupDetailData = (rows: ChecklistDetailRow[]) => { // Group by phase_id const phaseMap = new Map< string, { phase: { id: string; name: string }; activities: Map< string, { id: string; name: string; description: string | null; time_type: string; employees: Map< string, { id: string; name: string; checked: boolean; note: string | null; } >; } >; } >(); rows.forEach((row) => { if (!phaseMap.has(row.phase_id)) { phaseMap.set(row.phase_id, { phase: { id: row.phase_id, name: row.phase_name }, activities: new Map(), }); } const phaseData = phaseMap.get(row.phase_id)!; if (!phaseData.activities.has(row.activity_id)) { phaseData.activities.set(row.activity_id, { id: row.activity_id, name: row.activity_name, description: row.activity_description, time_type: row.time_type, employees: new Map(), }); } const activityData = phaseData.activities.get(row.activity_id)!; activityData.employees.set(row.employee_id, { id: row.employee_id, name: row.employee_name, checked: row.checked, note: row.note, }); }); // Convert to array and group by time_type const grouped: PhaseGroup[] = []; phaseMap.forEach((phaseData, phaseId) => { const timeGroups: { [timeType: string]: { activities: { id: string; name: string; description: string | null; employees: { id: string; name: string; checked: boolean; note: string | null; }[]; }[]; }; } = {}; phaseData.activities.forEach((activityData) => { const timeType = activityData.time_type || 'Umum'; if (!timeGroups[timeType]) { timeGroups[timeType] = { activities: [] }; } timeGroups[timeType].activities.push({ id: activityData.id, name: activityData.name, description: activityData.description, employees: Array.from(activityData.employees.values()), }); }); grouped.push({ phase: phaseData.phase, timeGroups, }); }); setPhaseGroups(grouped); }; const handleApprove = () => { setShowApproveModal(true); }; const handleReject = () => { setRejectReason(''); setShowRejectModal(true); }; const confirmApprove = async () => { if (!checklistId) return; try { setActionLoading(true); const approveRes = await DailyChecklistApi.approve(String(checklistId)); if (isResponseError(approveRes)) { toast.error('Gagal approve checklist: ' + approveRes.message); return; } toast.success('Checklist berhasil di-approve'); setShowApproveModal(false); await fetchChecklistDetail(); } catch (error) { console.error('Error approving checklist:', error); toast.error('Terjadi kesalahan'); } finally { setActionLoading(false); } }; const confirmReject = async () => { if (!checklistId) return; if (!rejectReason.trim()) { toast.error('Alasan reject harus diisi'); return; } try { setActionLoading(true); const rejectRes = await DailyChecklistApi.reject( String(checklistId), rejectReason ); if (isResponseError(rejectRes)) { toast.error('Gagal reject checklist: ' + rejectRes.message); return; } toast.success('Checklist berhasil di-reject'); setShowRejectModal(false); setRejectReason(''); await fetchChecklistDetail(); } catch (error) { console.error('Error rejecting checklist:', error); toast.error('Terjadi kesalahan'); } finally { setActionLoading(false); } }; 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: 'long', year: 'numeric', }); }; if (loading) { return (

Detail Daily Checklist

Memuat data...
); } if (!header) { return null; } const isReadOnly = header.status === 'APPROVED' || header.status === 'REJECTED'; return (
{/* Page Title with Back Button */}

Detail Daily Checklist

Lihat detail checklist harian

{header.status === 'SUBMITTED' && (
)}
{/* Header Info Card */}

{formatDate(header.date)}

{header.kandang_name}

{CATEGORY_LABELS[header.category] || header.category}

{getStatusBadge(header.status)}

{header.total_phases} fase

{header.total_activities} aktivitas

{header.progress_percent}%
{/* Reject Reason if rejected */} {header.status === 'REJECTED' && header.reject_reason && (

{header.reject_reason}

)} {/* Activity Checklist Table */}

Checklist Aktivitas

{phaseGroups.length > 0 ? (
{employees.map((emp) => ( ))} {phaseGroups.flatMap((phaseGroup) => { const timeTypes = Object.keys(phaseGroup.timeGroups).sort( (a, b) => TIME_TYPE_ORDER.indexOf(a) - TIME_TYPE_ORDER.indexOf(b) ); const totalActivities = timeTypes.reduce( (sum, timeType) => sum + phaseGroup.timeGroups[timeType].activities.length, 0 ); const rows = []; // PHASE Header - BLUE rows.push( ); // TIME_TYPE sub-headers and activities timeTypes.forEach((timeType) => { const timeGroup = phaseGroup.timeGroups[timeType]; const hasMultipleTimeTypes = timeTypes.length > 1; // TIME Header (optional) - GRAY SOFT if (hasMultipleTimeTypes) { rows.push( ); } // ACTIVITY rows const activities = timeGroup.activities; activities.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base', }) ); activities.forEach((activity, index) => { const indentClass = hasMultipleTimeTypes ? 'pl-12' : 'pl-8'; console.log({ activity, }); rows.push( {employees.map((emp) => { const empData = activity.employees.find( (e) => e.id === emp.id ); return ( ); })} ); }); }); return rows; })}
Aktivitas {emp.name} Catatan
{phaseGroup.phase.name} {totalActivities} aktivitas
{TIME_TYPE_LABELS[timeType]} ( {timeGroup.activities.length} aktivitas)

{activity.name}

{activity.description && (

{activity.description}

)}
{activity.employees.length > 0 && activity.employees[ activity.employees.length - 1 ].note ? (

{ activity.employees[ activity.employees.length - 1 ].note }

) : (

Tidak ada catatan

)}
) : (
Tidak ada data aktivitas
)} {documents.length > 0 && (

Dokumen yang telah diupload

    {documents.map((existingDocument, existingDocumentIdx) => (
  • {existingDocument.name}{' '}
  • ))}
)}
{/* Approve Modal */} Approve Checklist Apakah Anda yakin ingin approve checklist ini?
Tanggal: {formatDate(header.date)}
Kandang: {header.kandang_name}
Kategori: {CATEGORY_LABELS[header.category] || header.category}
Progress: {header.progress_percent}%
{/* Reject Modal */} Reject Checklist Berikan alasan reject untuk checklist ini
Tanggal: {formatDate(header.date)}
Kandang: {header.kandang_name}
Kategori: {CATEGORY_LABELS[header.category] || header.category}