'use client'; import * as React from 'react'; import { useState, useEffect, useRef } from 'react'; import { Plus, X, Save, Send, Info, FilePlus, ListChecks, Loader2, } from 'lucide-react'; import { Card, CardContent } from '@/figma-make/components/base/card'; import { Button } from '@/figma-make/components/base/button'; import { Label } from '@/figma-make/components/base/label'; import { Input } from '@/figma-make/components/base/input'; import { Badge } from '@/figma-make/components/base/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/figma-make/components/base/select'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/figma-make/components/base/dialog'; import { DatePicker } from '@/figma-make/components/base/date-picker'; import { toast } from 'sonner'; import { useSelect } from '@/components/input/SelectInput'; import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import useSWR from 'swr'; import { BaseApiResponse, Document } from '@/types/api/api-general'; import { AxiosError } from 'axios'; import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; import { PhaseApi } from '@/services/api/daily-checklist/phase'; import { EmployeeApi } from '@/services/api/daily-checklist/employee'; import { Employee } from '@/types/api/daily-checklist/employee'; import { PhaseActivityApi } from '@/services/api/daily-checklist/phase-activity'; import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity'; import DebouncedTextArea from '@/components/input/DebouncedTextArea'; import DropFileInput from '@/components/input/DropFileInput'; import Link from 'next/link'; import { useRouter, useSearchParams, usePathname } from 'next/navigation'; import { Icon } from '@iconify/react'; import { DailyChecklistKandangApi } from '@/services/api/daily-checklist/kandang'; const CATEGORIES = [ { value: 'pullet_open', label: 'Pullet Open' }, { value: 'pullet_close', label: 'Pullet Close' }, { value: 'produksi_open', label: 'Produksi Open' }, { value: 'produksi_close', label: 'Produksi Close' }, { value: 'empty_kandang', label: 'Kandang Kosong' }, ]; const CATEGORY_LABELS: { [key: string]: string } = { pullet_open: 'Pullet Open', pullet_close: 'Pullet Close', produksi_open: 'Produksi Open', produksi_close: 'Produksi Close', empty_kandang: 'Kandang Kosong', }; 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', }; interface Phase { id: string; name: 'string'; category: string; } export function DailyChecklistContent() { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); // If checklistId is in URL, we are in edit mode const checklistIdFromUrl = searchParams.get('checklistId'); const [kandangId, setKandangId] = useState( searchParams.get('kandang_id') || '' ); const [date, setDate] = useState(() => { const paramDate = searchParams.get('date'); if (paramDate) return paramDate; const today = new Date(); return today.toISOString().split('T')[0]; }); const [selectedCategory, setSelectedCategory] = useState( searchParams.get('category') || '' ); // Initialize emptyKandang from URL so it stays consistent with selectedCategory const [emptyKandang, setEmptyKandang] = useState( searchParams.get('category') === 'empty_kandang' ); const isKandangEmpty = selectedCategory === 'empty_kandang'; const { options: kandangOptions, isLoadingMore: isLoadingMoreKandang, loadMore: loadMoreKandang, } = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name'); const { data: phases } = useSWR< BaseApiResponse, AxiosError, SWRHttpKey >(`${PhaseApi.basePath}?page=1&limit=100`, httpClientFetcher, { keepPreviousData: true, }); const { data: employeesRes } = useSWR( `${EmployeeApi.basePath}?page=1&limit=500&kandang_id=${kandangId}&is_active=true`, EmployeeApi.getAllFetcher, { keepPreviousData: true } ); const allPhases = isResponseSuccess(phases) ? phases.data || [] : []; const employees = isResponseSuccess(employeesRes) ? employeesRes.data || [] : []; const [selectedPhaseIds, setSelectedPhaseIds] = useState([]); const [selectedEmployees, setSelectedEmployees] = useState< { id: number; name: string }[] >([]); const sortedSelectedEmployees = selectedEmployees.toSorted((a, b) => a.name.localeCompare(b.name) ); const [dailyChecklistId, setDailyChecklistId] = useState(null); const [checklistStatus, setChecklistStatus] = useState('DRAFT'); // Activities grouped by phase_id const [activitiesByPhase, setActivitiesByPhase] = useState<{ [phaseId: string]: PhaseActivity[]; }>({}); // Task IDs mapped by phase_activity_id const [taskIdsByPhaseActivityId, setTaskIdsByPhaseActivityId] = useState<{ [phaseActivityId: string]: string; }>({}); // Assignments keyed by phaseActivityId (works for both create and edit modes) const [assignments, setAssignments] = useState<{ [phaseActivityId: string]: { [employeeId: string]: { checked: boolean; note: string }; }; }>({}); const [showAbkModal, setShowAbkModal] = useState(false); const [showPhaseModal, setShowPhaseModal] = useState(false); const [tempSelectedEmployees, setTempSelectedEmployees] = useState< { id: number; name: string }[] >([]); const [tempSelectedPhaseIds, setTempSelectedPhaseIds] = useState( [] ); const [searchAbk, setSearchAbk] = useState(''); const [searchPhase, setSearchPhase] = useState(''); const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); const [isLoadingDraft, setIsLoadingDraft] = useState(false); const [initialLoading, setInitialLoading] = useState(!!checklistIdFromUrl); const [emptyKandangEndDate, setEmptyKandangEndDate] = useState(''); const [emptyKandangEndDateError, setEmptyKandangEndDateError] = useState(''); const [preloadedKandang, setPreloadedKandang] = useState<{ id: string; name: string; } | null>(null); const [existingDocuments, setExistingDocuments] = useState([]); const [documents, setDocuments] = useState([]); const [deletedDocumentIds, setDeletedDocumentIds] = useState([]); // Tracks the last checklistId we loaded to prevent re-loading after URL update const loadedChecklistIdRef = useRef(null); // Prevents the kandang-change effect from clearing employees/assignments during initial edit load const skipKandangClearRef = useRef(false); // Mirror of server-side phases/employees; used to diff on save in edit mode const serverPhaseIdsRef = useRef([]); const serverEmployeeIdsRef = useRef([]); const isChecklistStatusDraft = checklistStatus === 'DRAFT'; const isEditMode = dailyChecklistId !== null; // Load checklist data when checklistId is in URL (edit mode) useEffect(() => { if (!checklistIdFromUrl) { setInitialLoading(false); return; } // Skip if we already loaded this checklist (prevents reload after URL update on save) if (loadedChecklistIdRef.current === checklistIdFromUrl) return; loadedChecklistIdRef.current = checklistIdFromUrl; const loadChecklist = async () => { setInitialLoading(true); try { const checklistData = await DailyChecklistApi.getOneDailyChecklist(checklistIdFromUrl); if (isResponseError(checklistData) || !checklistData?.data) { toast.error('Gagal memuat checklist'); router.push('/daily-checklist'); return; } const data = checklistData.data; // Pre-fill form fields const rawDate = data.date || ''; setDate(rawDate.length > 10 ? rawDate.slice(0, 10) : rawDate); skipKandangClearRef.current = true; const loadedKandangId = String(data.kandang?.id || ''); setKandangId(loadedKandangId); if (data.kandang?.name) { setPreloadedKandang({ id: loadedKandangId, name: data.kandang.name }); } const isEmptyKandang = !!data.empty_kandang || data.category === 'empty_kandang'; setEmptyKandang(isEmptyKandang); setSelectedCategory(isEmptyKandang ? 'empty_kandang' : data.category); if ( isEmptyKandang && data.empty_kandang && data.empty_kandang.end_date ) { const rawEnd = data.empty_kandang.end_date; setEmptyKandangEndDate( rawEnd.length > 10 ? rawEnd.slice(0, 10) : rawEnd ); } setDailyChecklistId(String(data.id)); setChecklistStatus(data.status); // Pre-fill phases const phaseIds = (data.phases || []).map((p) => String(p.phase_id)); setSelectedPhaseIds(phaseIds); serverPhaseIdsRef.current = phaseIds; // Pre-fill employees const loadedEmployees = data.assigned_employees || []; setSelectedEmployees(loadedEmployees); serverEmployeeIdsRef.current = loadedEmployees.map((e) => String(e.id)); // Pre-fill documents setExistingDocuments(data.document_urls || []); // Build task map and assignments (keyed by phaseActivityId) const taskMap: { [phaseActivityId: string]: string } = {}; const assignmentMap: { [phaseActivityId: string]: { [employeeId: string]: { checked: boolean; note: string }; }; } = {}; (data.tasks || []).forEach((task) => { const paId = String(task.phase_activity_id); taskMap[paId] = String(task.id); if (!assignmentMap[paId]) assignmentMap[paId] = {}; task.assignments.forEach((assignment) => { assignmentMap[paId][String(assignment.employee.id)] = { checked: assignment.checked, note: assignment.note || '', }; }); }); setTaskIdsByPhaseActivityId(taskMap); setAssignments(assignmentMap); } catch (error) { console.error('Error loading checklist:', error); toast.error('Terjadi kesalahan saat memuat checklist'); } finally { setInitialLoading(false); } }; loadChecklist(); }, [checklistIdFromUrl, router]); // Load activities whenever selected phases change (read-only, safe to call anytime) useEffect(() => { const loadActivities = async () => { if (selectedPhaseIds.length === 0) { setActivitiesByPhase({}); return; } try { const activitiesRes = await PhaseActivityApi.getAll({ phase_ids: selectedPhaseIds.join(','), limit: '100', }); if (isResponseError(activitiesRes)) { toast.error('Gagal memuat aktivitas'); return; } const activities = activitiesRes?.data || []; const grouped: { [phaseId: string]: PhaseActivity[] } = {}; activities.forEach((act) => { if (!grouped[act.phase_id]) grouped[act.phase_id] = []; grouped[act.phase_id].push(act); }); setActivitiesByPhase(grouped); } catch (error) { console.error('Error loading activities:', error); } }; loadActivities(); }, [selectedPhaseIds]); // // Sync form state to URL query params // useEffect(() => { // const params = new URLSearchParams(searchParams.toString()); // let pendingUpdate = false; // if (date) { // if (params.get('date') !== date) { // params.set('date', date); // pendingUpdate = true; // } // } else if (params.has('date')) { // params.delete('date'); // pendingUpdate = true; // } // if (kandangId) { // if (params.get('kandang_id') !== kandangId) { // params.set('kandang_id', kandangId); // pendingUpdate = true; // } // } else if (params.has('kandang_id')) { // params.delete('kandang_id'); // pendingUpdate = true; // } // if (selectedCategory) { // if (params.get('category') !== selectedCategory) { // params.set('category', selectedCategory); // pendingUpdate = true; // } // } else if (params.has('category')) { // params.delete('category'); // pendingUpdate = true; // } // if (pendingUpdate) { // router.replace(`${pathname}?${params.toString()}`); // } // }, [date, kandangId, selectedCategory, pathname, router, searchParams]); // Clear employees and assignments when kandang changes (skip during initial edit load) useEffect(() => { if (skipKandangClearRef.current) { skipKandangClearRef.current = false; return; } setSelectedEmployees([]); setAssignments({}); }, [kandangId]); const handleKandangScroll = (e: React.UIEvent) => { const target = e.target as HTMLDivElement; if (target.scrollHeight - target.scrollTop <= target.clientHeight + 10) { if (!isLoadingMoreKandang) loadMoreKandang(); } }; const formatDateForDisplay = (dateStr: string) => { if (!dateStr) return 'Pilih tanggal'; const [year, month, day] = dateStr.split('-'); const d = new Date(parseInt(year), parseInt(month) - 1, parseInt(day)); return d.toLocaleDateString('id-ID', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }); }; const formatDate = (dateString: string) => { const d = new Date(dateString); return d.toLocaleDateString('id-ID', { day: '2-digit', month: 'long', year: 'numeric', }); }; const isMobileDevice = () => { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ); }; const getStatusMessage = () => { switch (checklistStatus) { case 'DRAFT': return 'Checklist harian perlu disubmit'; case 'SUBMITTED': return 'Checklist harian menunggu persetujuan'; case 'APPROVED': return 'Checklist harian telah disetujui'; case 'REJECTED': return 'Checklist harian telah ditolak'; default: return ''; } }; const getStatusBadgeClass = () => { switch (checklistStatus) { case 'SUBMITTED': return 'border-blue-300 text-blue-700 bg-white'; case 'APPROVED': return 'border-green-300 text-green-700 bg-white'; case 'REJECTED': return 'border-red-300 text-red-700 bg-white'; default: return 'border-blue-300 text-blue-700 bg-white'; } }; // Persist all local state to the server (shared by save draft and submit flows) const persistChecklistData = async (currentChecklistId: string) => { if (!isKandangEmpty) { // Set phases if (selectedPhaseIds.length > 0) { const setPhaseRes = await DailyChecklistApi.setDailyChecklistPhase( currentChecklistId, selectedPhaseIds ); if (isResponseError(setPhaseRes)) { toast.error('Gagal menyimpan fase'); return false; } } // Set employees if (selectedEmployees.length > 0) { const setEmployeesRes = await DailyChecklistApi.setDailyChecklistEmployees( currentChecklistId, selectedEmployees.map((e) => String(e.id)) ); if (isResponseError(setEmployeesRes)) { toast.error('Gagal menyimpan ABK'); return false; } } // Fetch tasks to get IDs, then save non-default assignments if (selectedPhaseIds.length > 0 && selectedEmployees.length > 0) { const checklistData = await DailyChecklistApi.getOneDailyChecklist(currentChecklistId); if (!isResponseError(checklistData) && checklistData?.data?.tasks) { const newTaskMap: { [phaseActivityId: string]: string } = {}; checklistData.data.tasks.forEach((task) => { newTaskMap[String(task.phase_activity_id)] = String(task.id); }); setTaskIdsByPhaseActivityId(newTaskMap); const assignmentPromises: Promise[] = []; Object.keys(assignments).forEach((phaseActivityId) => { const taskId = newTaskMap[phaseActivityId]; if (!taskId) return; Object.keys(assignments[phaseActivityId]).forEach((employeeId) => { const { checked, note } = assignments[phaseActivityId][employeeId]; if (checked || (note && note !== '')) { assignmentPromises.push( DailyChecklistApi.checkOrUncheckAssignment({ task_id: Number(taskId), employee_id: Number(employeeId), checked, note: note || null, }) ); } }); }); await Promise.all(assignmentPromises); } } } return true; }; // Ensure a checklist record exists; returns the ID or null on failure const ensureChecklist = async (): Promise => { if (dailyChecklistId) return dailyChecklistId; const createRes = await DailyChecklistApi.create({ date, kandang_id: Number(kandangId), category: emptyKandang ? 'empty_kandang' : selectedCategory, status: 'DRAFT', empty_kandang: emptyKandang, empty_kandang_end_date: emptyKandang ? emptyKandangEndDate || null : null, }); if (isResponseError(createRes) || !createRes?.data?.id) { toast.error( 'Gagal membuat checklist: ' + (isResponseError(createRes) ? createRes.message : 'unknown error') ); return null; } const newId = String(createRes.data.id); setDailyChecklistId(newId); return newId; }; // Phase selection modal const handleAddPhase = () => { if (!selectedCategory) { toast.error('Pilih kategori terlebih dahulu'); return; } setTempSelectedPhaseIds([...selectedPhaseIds]); setSearchPhase(''); setShowPhaseModal(true); }; const toggleTempPhase = (phaseId: string) => { if (tempSelectedPhaseIds.includes(phaseId)) { setTempSelectedPhaseIds( tempSelectedPhaseIds.filter((id) => id !== phaseId) ); } else { setTempSelectedPhaseIds([...tempSelectedPhaseIds, phaseId]); } }; // Phase selection — local state only; persisted on Simpan Draft / Submit const applyPhaseSelection = () => { if (!tempSelectedPhaseIds.length) { toast.error('Pilih minimal satu fase'); return; } setSelectedPhaseIds([...tempSelectedPhaseIds]); setShowPhaseModal(false); setSearchPhase(''); }; // ABK selection modal const handleAddAbk = () => { if (!kandangId) { toast.error('Pilih kandang terlebih dahulu'); return; } setTempSelectedEmployees([...selectedEmployees]); setSearchAbk(''); setShowAbkModal(true); }; const toggleTempEmployee = (employee: Employee) => { const isSelected = tempSelectedEmployees.find((e) => e.id === employee.id); if (isSelected) { setTempSelectedEmployees( tempSelectedEmployees.filter((e) => e.id !== employee.id) ); } else { setTempSelectedEmployees([...tempSelectedEmployees, employee]); } }; // ABK selection — local state only; persisted on Simpan Draft / Submit const applyAbkSelection = () => { const removedEmployees = selectedEmployees.filter( (emp) => !tempSelectedEmployees.find((temp) => temp.id === emp.id) ); if (removedEmployees.length > 0) { const newAssignments = { ...assignments }; Object.keys(newAssignments).forEach((paId) => { removedEmployees.forEach((emp) => { delete newAssignments[paId][String(emp.id)]; }); }); setAssignments(newAssignments); } setSelectedEmployees([...tempSelectedEmployees]); setShowAbkModal(false); setSearchAbk(''); }; // Remove ABK — local state only; persisted on Simpan Draft / Submit const handleRemoveAbk = (employeeId: string) => { const newAssignments = { ...assignments }; Object.keys(newAssignments).forEach((paId) => { delete newAssignments[paId][employeeId]; }); setAssignments(newAssignments); setSelectedEmployees( selectedEmployees.filter((e) => String(e.id) !== employeeId) ); }; // Checkbox change — local state only; persisted on Simpan Draft / Submit const handleCheckboxChange = ( activityId: string, employeeId: string, checked: boolean ) => { if (!isChecklistStatusDraft) return; setAssignments((prev) => ({ ...prev, [activityId]: { ...prev[activityId], [employeeId]: { checked, note: prev[activityId]?.[employeeId]?.note || '', }, }, })); }; // Note change — local state only; persisted on Simpan Draft / Submit const handleNoteChange = ( activityId: string, employeeId: string, note: string ) => { if (!isChecklistStatusDraft) return; setAssignments((prev) => ({ ...prev, [activityId]: { ...prev[activityId], [employeeId]: { checked: prev[activityId]?.[employeeId]?.checked || false, note, }, }, })); }; // Persist phases, employees, and assignments to the server (edit mode) const persistEditModeData = async (currentChecklistId: string) => { const localPhaseIds = selectedPhaseIds; const localEmployeeIds = selectedEmployees.map((e) => String(e.id)); // Phases — sync if changed const prevPhaseIds = serverPhaseIdsRef.current; const phasesChanged = localPhaseIds.length !== prevPhaseIds.length || localPhaseIds.some((id) => !prevPhaseIds.includes(id)); if (phasesChanged && localPhaseIds.length > 0) { const res = await DailyChecklistApi.setDailyChecklistPhase( currentChecklistId, localPhaseIds ); if (isResponseError(res)) { toast.error('Gagal menyimpan fase'); return false; } } // Employees — diff vs last known server state const prevEmployeeIds = serverEmployeeIdsRef.current; const removedIds = prevEmployeeIds.filter( (id) => !localEmployeeIds.includes(id) ); const addedIds = localEmployeeIds.filter( (id) => !prevEmployeeIds.includes(id) ); for (const empId of removedIds) { const res = await DailyChecklistApi.removeEmployeeAssignment( currentChecklistId, empId ); if (isResponseError(res)) { toast.error('Gagal menghapus ABK'); return false; } } if (addedIds.length > 0) { const res = await DailyChecklistApi.setDailyChecklistEmployees( currentChecklistId, addedIds ); if (isResponseError(res)) { toast.error('Gagal menyimpan ABK'); return false; } } // Re-fetch to get up-to-date task IDs whenever phases or employees changed // (server regenerates tasks when phases/assignments change) let currentTaskMap = taskIdsByPhaseActivityId; if (phasesChanged || removedIds.length > 0 || addedIds.length > 0) { const refreshed = await DailyChecklistApi.getOneDailyChecklist(currentChecklistId); if (!isResponseError(refreshed) && refreshed?.data?.tasks) { const newTaskMap: { [phaseActivityId: string]: string } = {}; refreshed.data.tasks.forEach((task) => { newTaskMap[String(task.phase_activity_id)] = String(task.id); }); setTaskIdsByPhaseActivityId(newTaskMap); currentTaskMap = newTaskMap; } } // Assignments — use the freshest task map const assignmentPromises: Promise[] = []; Object.keys(assignments).forEach((phaseActivityId) => { const taskId = currentTaskMap[phaseActivityId]; if (!taskId) return; Object.keys(assignments[phaseActivityId]).forEach((employeeId) => { const { checked, note } = assignments[phaseActivityId][employeeId]; assignmentPromises.push( DailyChecklistApi.checkOrUncheckAssignment({ task_id: Number(taskId), employee_id: Number(employeeId), checked, note: note || null, }) ); }); }); await Promise.all(assignmentPromises); // Update server-state mirrors serverPhaseIdsRef.current = localPhaseIds; serverEmployeeIdsRef.current = localEmployeeIds; return true; }; const handleNewChecklist = () => { const today = new Date().toISOString().split('T')[0]; setDate(today); setKandangId(''); setSelectedCategory(''); setEmptyKandang(false); setEmptyKandangEndDate(''); setSelectedPhaseIds([]); setSelectedEmployees([]); setDailyChecklistId(null); setChecklistStatus('DRAFT'); setActivitiesByPhase({}); setTaskIdsByPhaseActivityId({}); setAssignments({}); setExistingDocuments([]); setDocuments([]); setDeletedDocumentIds([]); loadedChecklistIdRef.current = null; const params = new URLSearchParams(searchParams.toString()); params.delete('checklistId'); router.replace(`${pathname}?${params.toString()}`); }; const handleSaveDraft = async () => { if (!date || !kandangId || (!emptyKandang && !selectedCategory)) { toast.error('Lengkapi tanggal, kandang, dan kategori terlebih dahulu'); return; } if (emptyKandang && !emptyKandangEndDate) { setEmptyKandangEndDateError('Tanggal akhir kandang kosong wajib diisi'); return; } setIsLoadingDraft(true); try { const currentChecklistId = await ensureChecklist(); if (!currentChecklistId) return; if (isEditMode) { // Update base fields in edit mode const updateRes = await DailyChecklistApi.update( Number(currentChecklistId), { date, kandang_id: Number(kandangId), category: emptyKandang ? 'empty_kandang' : selectedCategory, status: 'DRAFT', empty_kandang: emptyKandang, empty_kandang_end_date: emptyKandang ? emptyKandangEndDate || null : null, } ); if (isResponseError(updateRes)) { toast.error('Gagal memperbarui checklist'); return; } const ok = await persistEditModeData(currentChecklistId); if (!ok) return; } else { // In create mode, persist all local state to the server const ok = await persistChecklistData(currentChecklistId); if (!ok) return; } const uploadRes = await DailyChecklistApi.uploadImage( Number(currentChecklistId), 'DRAFT', documents, deletedDocumentIds ); if (isResponseError(uploadRes)) { toast.error('Gagal menyimpan dokumen'); return; } // After first save, push checklistId into URL so refresh keeps edit mode if (!isEditMode) { loadedChecklistIdRef.current = currentChecklistId; const params = new URLSearchParams(searchParams.toString()); params.set('checklistId', currentChecklistId); router.replace(`${pathname}?${params.toString()}`); } // Refresh existing documents list const refreshed = await DailyChecklistApi.getOneDailyChecklist(currentChecklistId); if (!isResponseError(refreshed) && refreshed?.data) { setExistingDocuments(refreshed.data.document_urls || []); } setDocuments([]); setDeletedDocumentIds([]); toast.success('Draft tersimpan!'); } catch (error) { console.error('Error saving draft:', error); toast.error('Terjadi kesalahan'); } finally { setIsLoadingDraft(false); } }; const handleSubmit = async () => { if (!date || !kandangId || (!emptyKandang && !selectedCategory)) { toast.error('Lengkapi tanggal, kandang, dan kategori terlebih dahulu'); return; } if (emptyKandang && !emptyKandangEndDate) { setEmptyKandangEndDateError('Tanggal akhir kandang kosong wajib diisi'); return; } if (!isKandangEmpty) { if (selectedEmployees.length === 0) { toast.error('Pilih minimal 1 ABK'); return; } if (selectedPhaseIds.length === 0) { toast.error('Pilih minimal 1 fase'); return; } } setIsLoadingSubmit(true); try { const currentChecklistId = await ensureChecklist(); if (!currentChecklistId) return; if (isEditMode) { // Update base fields in edit mode before submitting const updateRes = await DailyChecklistApi.update( Number(currentChecklistId), { date, kandang_id: Number(kandangId), category: emptyKandang ? 'empty_kandang' : selectedCategory, status: 'DRAFT', empty_kandang: emptyKandang, empty_kandang_end_date: emptyKandang ? emptyKandangEndDate || null : null, } ); if (isResponseError(updateRes)) { toast.error('Gagal memperbarui checklist'); return; } const ok = await persistEditModeData(currentChecklistId); if (!ok) return; } else { // In create mode, persist all local state first const ok = await persistChecklistData(currentChecklistId); if (!ok) return; } const submitRes = await DailyChecklistApi.submit( currentChecklistId, documents, deletedDocumentIds ); if (isResponseError(submitRes)) { toast.error('Gagal submit checklist'); return; } setChecklistStatus('SUBMITTED'); // Push checklistId into URL if (!isEditMode) { loadedChecklistIdRef.current = currentChecklistId; const params = new URLSearchParams(searchParams.toString()); params.set('checklistId', currentChecklistId); router.replace(`${pathname}?${params.toString()}`); } const shareToWhatsApp = () => { const kandangName = kandangOptions.find((k) => String(k.value) === kandangId)?.label || kandangId; const statusMsg = getStatusMessage(); const category = selectedCategory || ''; const message = encodeURIComponent( `Daily Checklist\n\nTanggal: ${formatDate(date)}\nKandang: ${kandangName}\nKategori: ${CATEGORY_LABELS[category] || category}\nStatus: SUBMITTED${statusMsg ? ` - ${statusMsg}` : ''}\n\nLihat detail lengkap: ${window.location.href}` ); const isMobile = isMobileDevice(); const whatsappUrl = isMobile ? `https://wa.me/?text=${message}` : `https://web.whatsapp.com/send?text=${message}`; window.open(whatsappUrl, '_blank'); }; toast.success('Checklist berhasil disubmit untuk approval', { action: { label: 'Bagikan ke WhatsApp', onClick: shareToWhatsApp, }, description: ( ), }); } catch (error) { console.error('Error submitting:', error); toast.error('Terjadi kesalahan'); } finally { setIsLoadingSubmit(false); } }; // Filter helpers const filteredEmployees = employees.filter((emp) => emp.name.toLowerCase().includes(searchAbk.toLowerCase()) ); const availablePhases = allPhases.filter( (p) => p.category === selectedCategory ); const filteredPhases = availablePhases.filter((phase) => phase.name.toLowerCase().includes(searchPhase.toLowerCase()) ); const isAllAbkSelected = tempSelectedEmployees.length === filteredEmployees.length && filteredEmployees.length > 0 && tempSelectedEmployees.every((t) => filteredEmployees.some((f) => f.id === t.id) ); const isAllPhasesSelected = tempSelectedPhaseIds.length === filteredPhases.length && filteredPhases.length > 0 && tempSelectedPhaseIds.every((id) => filteredPhases.some((p) => String(p.id) === String(id)) ); const toggleSelectAllAbk = () => { if (isAllAbkSelected) { setTempSelectedEmployees([]); } else { setTempSelectedEmployees([...filteredEmployees]); } }; // Group activities by Phase → TimeType const groupActivitiesByPhase = () => { const grouped: { [phaseId: string]: { phase: Phase; timeGroups: { [timeType: string]: PhaseActivity[] }; }; } = {}; const selectedPhasesData = allPhases.filter((p) => selectedPhaseIds.includes(String(p.id)) ); selectedPhasesData.forEach((phase) => { const phaseActivities = activitiesByPhase[phase.id] || []; if (!grouped[phase.id]) { grouped[phase.id] = { phase, timeGroups: {} }; } phaseActivities.forEach((activity) => { const timeType = activity.time_type || 'Umum'; if (!grouped[phase.id].timeGroups[timeType]) { grouped[phase.id].timeGroups[timeType] = []; } grouped[phase.id].timeGroups[timeType].push(activity); }); }); return grouped; }; // Visibility helpers const hasFormBase = !!(date && kandangId); const hasCategory = !!(isKandangEmpty || selectedCategory); const canShowPhaseSection = !isKandangEmpty && hasFormBase && !!selectedCategory; const canShowAbkSection = canShowPhaseSection && selectedPhaseIds.length > 0; const canShowTable = canShowAbkSection && selectedEmployees.length > 0; const canShowActions = isChecklistStatusDraft && hasFormBase && hasCategory && (isKandangEmpty || (selectedPhaseIds.length > 0 && selectedEmployees.length > 0)); if (initialLoading) { return (

Daily Checklist

Checklist Harian Aktivitas Kandang

Memuat data...
); } return (
{/* Page Title */}

Daily Checklist

{isChecklistStatusDraft && ( Mode Edit )} {checklistStatus !== 'DRAFT' && ( {checklistStatus} )}
{isEditMode && ( )}

Checklist Harian Aktivitas Kandang

{/* Main Card */} {/* Form Section */}
{emptyKandang && (
{ setEmptyKandangEndDate(val); if (val) setEmptyKandangEndDateError(''); }} disabled={!isChecklistStatusDraft} placeholder='Pilih tanggal' formatDisplay={formatDateForDisplay} hasError={!!emptyKandangEndDateError} /> {emptyKandangEndDateError && (

{emptyKandangEndDateError}

)}
)}
{/* Phase Selection Section */} {canShowPhaseSection && (
{isChecklistStatusDraft && (
)} {selectedPhaseIds.length > 0 ? (
{allPhases .filter((p) => selectedPhaseIds.includes(String(p.id))) .map((phase) => ( {phase.name} ))}
) : (

Belum ada fase dipilih

)}
)} {/* ABK Assignment Section */} {canShowAbkSection && (
{isChecklistStatusDraft && (
)} {selectedEmployees.length > 0 ? (
{selectedEmployees.map((emp) => ( {emp.name} {isChecklistStatusDraft && ( )} ))}
) : (

Belum ada ABK dipilih

)}
)} {/* Activity Checklist Table */} {!isKandangEmpty && ( <> {canShowTable ? (

Checklist Aktivitas

{Object.keys(activitiesByPhase).length > 0 ? (
{sortedSelectedEmployees.map((emp) => ( ))} {Object.keys(groupActivitiesByPhase()).flatMap( (phaseId) => { const phaseData = groupActivitiesByPhase()[phaseId]; const { phase, timeGroups } = phaseData; const timeTypes = Object.keys(timeGroups).sort( (a, b) => TIME_TYPE_ORDER.indexOf(a) - TIME_TYPE_ORDER.indexOf(b) ); const totalActivities = timeTypes.reduce( (sum, tt) => sum + timeGroups[tt].length, 0 ); const rows = []; // Phase header row rows.push( ); timeTypes.forEach((timeType) => { const activities = timeGroups[timeType]; const hasMultiple = timeTypes.length > 1; if (hasMultiple) { rows.push( ); } activities .slice() .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base', }) ) .forEach((activity, index) => { const activityId = String(activity.id); const indentClass = hasMultiple ? 'pl-12' : 'pl-8'; rows.push( {sortedSelectedEmployees.map( (emp) => ( ) )} ); }); }); return rows; } )}
Aktivitas {emp.name} Catatan
{phase.name} {totalActivities} aktivitas
{TIME_TYPE_LABELS[timeType]} ( {activities.length} aktivitas)

{activity.name}

{activity.description && (

{activity.description}

)}
handleCheckboxChange( activityId, String(emp.id), e.target.checked ) } disabled={ !isChecklistStatusDraft } className='checkbox-clean' /> 0 ? assignments[activityId]?.[ String( selectedEmployees[0].id ) ]?.note || '' : '' } onChange={(e) => { if ( selectedEmployees.length > 0 ) { handleNoteChange( activityId, String( selectedEmployees[0].id ), e.target.value ); } }} disabled={!isChecklistStatusDraft} />
) : (

Tidak Ada Aktivitas

Tidak ada aktivitas untuk fase yang dipilih. Silakan tambahkan aktivitas di Master Aktivitas.

)}
) : (
{!hasFormBase || !selectedCategory ? (

Mulai Checklist Baru

Pilih tanggal, kandang, dan kategori untuk memulai checklist harian Anda.

) : selectedPhaseIds.length === 0 ? (

Pilih Fase / Tahap

Klik tombol {'"'}Pilih Fase{'"'} untuk memilih tahap aktivitas yang akan dikerjakan.

) : (

Pilih ABK

Klik tombol {'"'}Tambah ABK{'"'} untuk memilih pekerja yang akan ditugaskan.

)}
)} )} {/* Documents */} {!isKandangEmpty && canShowTable && ( <> {existingDocuments.length > 0 && (

Dokumen yang telah diupload

{existingDocuments.map( (existingDocument, existingDocumentIdx) => (
{existingDocument.name}{' '} {isChecklistStatusDraft && ( )}
) )}
)} {isChecklistStatusDraft && ( setDocuments(files)} onDelete={(deletedFileIdx: number) => { const next = [...documents]; next.splice(deletedFileIdx, 1); setDocuments(next); }} disabled={!isChecklistStatusDraft} className={{ wrapper: 'mt-6', inputWrapper: 'flex items-center', label: 'font-semibold text-gray-900', }} maxSize={5242880} bottomLabel='Ukuran file maksimal 5MB' /> )} )} {/* Documents for empty kandang */} {isKandangEmpty && isChecklistStatusDraft && ( <> {existingDocuments.length > 0 && (

Dokumen yang telah diupload

{existingDocuments.map( (existingDocument, existingDocumentIdx) => (
{existingDocument.name}{' '}
) )}
)} setDocuments(files)} onDelete={(deletedFileIdx: number) => { const next = [...documents]; next.splice(deletedFileIdx, 1); setDocuments(next); }} disabled={false} className={{ wrapper: 'mt-6', inputWrapper: 'flex items-center', label: 'font-semibold text-gray-900', }} maxSize={5242880} bottomLabel='Ukuran file maksimal 5MB' /> )} {/* Action Buttons */} {canShowActions && (
)}
{/* Phase Selection Modal */} setShowPhaseModal(false)} > Pilih Fase / Tahap Pilih satu atau lebih fase yang akan dikerjakan
setSearchPhase(e.target.value)} className='border-gray-200' />
{availablePhases.length > 0 && (
)}
{filteredPhases.length > 0 ? (
{filteredPhases.map((phase) => { const isChecked = tempSelectedPhaseIds.includes( String(phase.id) ); return ( ); })}
) : (

{searchPhase ? 'Tidak ada fase ditemukan' : 'Tidak ada fase tersedia'}

)}
Terpilih: {tempSelectedPhaseIds.length} Fase
{/* ABK Selection Modal */} setShowAbkModal(false)}> Pilih ABK Pilih satu atau lebih ABK yang ditugaskan
setSearchAbk(e.target.value)} className='border-gray-200' />
{filteredEmployees.length > 0 && (
)}
{filteredEmployees.length > 0 ? (
{filteredEmployees.map((emp) => { const isChecked = tempSelectedEmployees.find( (e) => e.id === emp.id ); const kandang = emp.kandangs .map((empKandang) => { if (String(empKandang.id) === kandangId) { return `${empKandang.name}`; } return empKandang.name; }) .join(', '); return ( ); })}
) : (

{searchAbk ? 'Tidak ada ABK ditemukan' : 'Tidak ada ABK tersedia'}

)}
Terpilih: {tempSelectedEmployees.length} ABK
); }