Files
lti-web-client/src/figma-make/components/pages/list-daily-checklist/ListDailyChecklistContent.tsx
T

1094 lines
35 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState } from 'react';
import {
Eye,
CheckCircle,
XCircle,
Search,
Trash2,
Edit,
Loader2,
} 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 { DateRangePicker } from '@/figma-make/components/base/date-range-picker';
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 { toast } from 'sonner';
import { useRouter } from 'next/navigation';
import useSWR from 'swr';
import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import Table from '@/components/Table';
import { DailyChecklist } from '@/types/api/daily-checklist/daily-checklist';
import { cn } from '@/lib/helper';
import { ColumnDef, Row } from '@tanstack/react-table';
import { useSelect } from '@/components/input/SelectInput';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import RequirePermission from '@/components/helper/RequirePermission';
import { DailyChecklistKandangApi } from '@/services/api/daily-checklist/kandang';
import CheckboxInput from '@/components/input/CheckboxInput';
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 } = {
pullet_open: 'Pullet Open',
pullet_close: 'Pullet Close',
produksi_open: 'Produksi Open',
produksi_close: 'Produksi Close',
empty_kandang: 'Kandang Kosong',
};
export function ListDailyChecklistContent() {
const router = useRouter();
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterQueryString,
} = useTableFilter({
initial: {
date_from: '',
date_to: '',
search: '',
kandang_id: '',
status: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
search: 'search',
kandang_id: 'kandang_id',
status: 'status',
date_from: 'date_from',
date_to: 'date_to',
},
persist: true,
storeName: 'list-daily-checklist-content-table',
});
const {
data: checklistListRes,
isLoading: isLoadingChecklistList,
mutate: refreshChecklistList,
} = useSWR(
`${DailyChecklistApi.basePath}${getTableFilterQueryString()}`,
DailyChecklistApi.getAllFetcher,
{
keepPreviousData: true,
}
);
const {
options: kandangOptions,
isLoadingMore: isLoadingMoreKandang,
loadMore: loadMoreKandang,
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
order_by: 'asc',
sort_by: 'name',
});
const checklistList = isResponseSuccess(checklistListRes)
? checklistListRes.data || []
: [];
const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement;
if (target.scrollHeight - target.scrollTop <= target.clientHeight + 10) {
if (!isLoadingMoreKandang) {
loadMoreKandang();
}
}
};
// Modals
const [showApproveModal, setShowApproveModal] = useState(false);
const [showBulkApproveModal, setShowBulkApproveModal] = useState(false);
const [showRejectModal, setShowRejectModal] = useState(false);
const [showBulkRejectModal, setShowBulkRejectModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedItem, setSelectedItem] = useState<DailyChecklist | null>(null);
const [rejectReason, setRejectReason] = useState('');
const [actionLoading, setActionLoading] = useState(false);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const selectedRowIds = Object.keys(rowSelection);
const selectedRowItems = selectedRowIds.map((itemId) =>
checklistList.find((item) => item.id === parseInt(itemId))
);
const tableEnableRowSelectionHandler: (
row: Row<DailyChecklist>
) => boolean = (row) => {
return (
row.original.status !== 'APPROVED' && row.original.status !== 'REJECTED'
);
};
const handleDetail = (item: DailyChecklist) => {
router.push(
`/daily-checklist/list-daily-checklist/detail?checklistId=${item.id}`
);
};
const handleEdit = (item: DailyChecklist) => {
router.push(`/daily-checklist/daily-checklist?checklistId=${item.id}`);
};
const handleApprove = (item: DailyChecklist) => {
setSelectedItem(item);
setShowApproveModal(true);
};
const handleBulkApprove = () => {
setShowBulkApproveModal(true);
};
const handleReject = (item: DailyChecklist) => {
setSelectedItem(item);
setRejectReason('');
setShowRejectModal(true);
};
const handleBulkReject = () => {
setRejectReason('');
setShowBulkRejectModal(true);
};
const handleDelete = (item: DailyChecklist) => {
setSelectedItem(item);
setShowDeleteModal(true);
};
const confirmApprove = async () => {
if (!selectedItem) return;
try {
setActionLoading(true);
const approveRes = await DailyChecklistApi.approve(
String(selectedItem.id)
);
if (isResponseError(approveRes)) {
toast.error('Gagal approve checklist: ' + approveRes.message);
return;
}
refreshChecklistList();
toast.success('Checklist berhasil di-approve');
setShowApproveModal(false);
setSelectedItem(null);
} catch (error) {
console.error('Error approving checklist:', error);
toast.error('Terjadi kesalahan');
} finally {
setActionLoading(false);
}
};
const confirmBulkApprove = async () => {
if (!selectedRowIds.length) return;
try {
setActionLoading(true);
const approveRes = await DailyChecklistApi.bulkApprove(selectedRowIds);
if (isResponseError(approveRes)) {
toast.error('Gagal approve checklist: ' + approveRes.message);
return;
}
refreshChecklistList();
toast.success('Checklist berhasil di-approve');
setShowBulkApproveModal(false);
setRowSelection({});
} catch (error) {
console.error('Error approving checklist:', error);
toast.error('Terjadi kesalahan');
} finally {
setActionLoading(false);
}
};
const confirmReject = async () => {
if (!selectedItem) return;
if (!rejectReason.trim()) {
toast.error('Alasan reject harus diisi');
return;
}
try {
setActionLoading(true);
const rejectRes = await DailyChecklistApi.reject(
String(selectedItem.id),
rejectReason
);
if (isResponseError(rejectRes)) {
toast.error('Gagal reject checklist: ' + rejectRes.message);
return;
}
refreshChecklistList();
toast.success('Checklist berhasil di-reject');
setShowRejectModal(false);
setSelectedItem(null);
setRejectReason('');
} catch (error) {
console.error('Error rejecting checklist:', error);
toast.error('Terjadi kesalahan');
} finally {
setActionLoading(false);
}
};
const confirmBulkReject = async () => {
if (!selectedRowIds.length) return;
if (!rejectReason.trim()) {
toast.error('Alasan reject harus diisi');
return;
}
try {
setActionLoading(true);
const rejectRes = await DailyChecklistApi.bulkReject(
selectedRowIds,
rejectReason
);
if (isResponseError(rejectRes)) {
toast.error('Gagal reject checklist: ' + rejectRes.message);
return;
}
refreshChecklistList();
toast.success('Checklist berhasil di-reject');
setShowBulkRejectModal(false);
setRowSelection({});
setRejectReason('');
} catch (error) {
console.error('Error rejecting checklist:', error);
toast.error('Terjadi kesalahan');
} finally {
setActionLoading(false);
}
};
const confirmDelete = async () => {
if (!selectedItem) return;
try {
setActionLoading(true);
const deleteRes = await DailyChecklistApi.delete(selectedItem.id);
if (isResponseError(deleteRes)) {
toast.error('Gagal hapus checklist: ' + deleteRes.message);
return;
}
refreshChecklistList();
toast.success('Checklist berhasil dihapus');
setShowDeleteModal(false);
setSelectedItem(null);
} catch (error) {
console.error('Error deleting checklist:', error);
toast.error('Terjadi kesalahan');
} finally {
setActionLoading(false);
}
};
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 formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric',
});
};
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 checklistListColumns: ColumnDef<DailyChecklist>[] = [
{
id: 'select',
header: ({ table }) => (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
</div>
),
cell: ({ row }) => {
const isCheckboxDisabled =
!row.getCanSelect() ||
row.original.status === 'APPROVED' ||
row.original.status === 'REJECTED';
return (
<div>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={isCheckboxDisabled}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
</div>
);
},
},
{
accessorKey: 'date',
header: 'Tanggal',
enableSorting: false,
cell: ({ row }) => formatDate(row.original.date),
},
{
accessorKey: 'kandang',
header: 'Kandang',
enableSorting: false,
cell: ({ row }) => row.original.kandang?.name ?? '-',
},
{
accessorKey: 'category',
header: 'Kategori',
enableSorting: false,
cell: ({ row }) =>
CATEGORY_LABELS[row.original.category] || row.original.category,
},
{
accessorKey: 'status',
header: 'Status',
enableSorting: false,
cell: ({ row }) => getStatusBadge(row.original.status),
},
{
accessorKey: 'total_phase',
header: 'Total Phase',
enableSorting: false,
},
{
accessorKey: 'total_activity',
header: 'Total Aktivitas',
enableSorting: false,
},
{
accessorKey: 'progress',
header: 'Progress',
enableSorting: false,
cell: ({ row }) => (
<div className='flex items-center justify-center gap-2'>
<div className='w-24 bg-gray-200 rounded-full h-2'>
<div
className='bg-[#0069e0] h-2 rounded-full transition-all'
style={{ width: `${row.original.progress}%` }}
/>
</div>
<span className='text-sm text-gray-700 font-medium'>
{row.original.progress}%
</span>
</div>
),
},
{
accessorKey: 'updated_at',
header: 'Diperbarui',
enableSorting: false,
cell: ({ row }) => formatDateTime(row.original.updated_at),
},
{
id: 'action',
header: 'Aksi',
accessorKey: 'action',
enableSorting: false,
cell: ({ row }) => (
<div className='flex items-center justify-center gap-2'>
<Button
size='sm'
variant='outline'
onClick={() => handleDetail(row.original)}
className='border-gray-200 text-gray-700 hover:bg-gray-50'
>
<Eye className='w-4 h-4 mr-1' />
Detail
</Button>
{row.original.status === 'DRAFT' && (
<RequirePermission permissions='lti.daily_checklist.create'>
<Button
size='sm'
variant='outline'
onClick={() => handleEdit(row.original)}
className='border-gray-200 text-gray-700 hover:bg-gray-50'
>
<Edit className='w-4 h-4 mr-1' />
Edit
</Button>
</RequirePermission>
)}
{row.original.status === 'SUBMITTED' && (
<RequirePermission permissions='lti.daily_checklist.create'>
<Button
size='sm'
onClick={() => handleApprove(row.original)}
className='bg-green-600 hover:bg-green-700 text-white'
>
<CheckCircle className='w-4 h-4 mr-1' />
Approve
</Button>
<Button
size='sm'
variant='destructive'
onClick={() => handleReject(row.original)}
className='bg-red-600 hover:bg-red-700 text-white'
>
<XCircle className='w-4 h-4 mr-1' />
Reject
</Button>
</RequirePermission>
)}
<RequirePermission permissions='lti.daily_checklist.create'>
<Button
size='sm'
variant='destructive'
onClick={() => handleDelete(row.original)}
className='bg-red-600 hover:bg-red-700 text-white'
>
<Trash2 className='w-4 h-4 mr-1' />
Hapus
</Button>
</RequirePermission>
</div>
),
},
];
return (
<div className='min-h-screen'>
<div className='p-6'>
{/* Page Title */}
<div className='mb-6 flex flex-row justify-between items-center gap-3'>
<div>
<h1 className='text-2xl font-semibold text-gray-900'>
List Daily Checklist
</h1>
<p className='text-sm text-gray-600 mt-1'>
Daftar semua checklist harian
</p>
</div>
<RequirePermission permissions='lti.daily_checklist.create'>
{selectedRowIds.length > 0 && (
<div className='flex flex-row items-center gap-3'>
<Button
size='sm'
onClick={handleBulkApprove}
className='bg-green-600 hover:bg-green-700 text-white'
>
<CheckCircle className='w-4 h-4 mr-1' />
Bulk Approve {`(${selectedRowIds.length}) item`}
</Button>
<Button
size='sm'
variant='destructive'
onClick={handleBulkReject}
className='bg-red-600 hover:bg-red-700 text-white'
>
<XCircle className='w-4 h-4 mr-1' />
Bulk Reject {`(${selectedRowIds.length}) item`}
</Button>
</div>
)}
</RequirePermission>
</div>
{/* Main Card */}
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
<CardContent className='p-6'>
{/* 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={tableFilterState.date_from}
dateTo={tableFilterState.date_to}
onDateChange={(from, to) => {
updateFilter('date_from', from);
updateFilter('date_to', to);
}}
/>
</div>
</div>
<div>
<Label htmlFor='kandang-filter'>Kandang</Label>
<div className='mt-1.5'>
<Select
value={tableFilterState.kandang_id}
onValueChange={(value) => {
updateFilter('kandang_id', value === 'ALL' ? '' : value);
}}
>
<SelectTrigger
id='kandang-filter'
className='border-gray-200'
>
<SelectValue placeholder='Semua Kandang' />
</SelectTrigger>
<SelectContent onScroll={handleKandangScroll}>
<SelectItem value='ALL'>Semua Kandang</SelectItem>
{kandangOptions.map((kandang) => (
<SelectItem
key={kandang.value}
value={String(kandang.value)}
>
{kandang.label}
</SelectItem>
))}
{isLoadingMoreKandang && (
<div className='flex justify-center p-2'>
<Loader2 className='h-4 w-4 animate-spin text-gray-500' />
</div>
)}
</SelectContent>
</Select>
</div>
</div>
<div>
<Label htmlFor='status-filter'>Status</Label>
<div className='mt-1.5'>
<Select
value={tableFilterState.status}
onValueChange={(value) => {
updateFilter('status', value === 'ALL' ? '' : value);
}}
>
<SelectTrigger
id='status-filter'
className='border-gray-200'
>
<SelectValue placeholder='Semua Status' />
</SelectTrigger>
<SelectContent>
{STATUS_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div>
<Label htmlFor='search-text'>Cari</Label>
<div className='relative mt-1.5'>
<DebouncedTextInput
name='search'
placeholder='Kandang / Kategori'
value={tableFilterState.search}
onChange={(e) => updateFilter('search', e.target.value)}
className={{
wrapper: 'w-full border-gray-200',
inputWrapper: 'px-3 py-2 h-fit rounded-md',
input: 'text-sm',
}}
startAdornment={
<Search className='text-gray-400 w-4 h-4' />
}
/>
</div>
</div>
</div>
{/* Table Section */}
<Table<DailyChecklist>
data={checklistList}
columns={checklistListColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={
isResponseSuccess(checklistListRes)
? checklistListRes?.meta?.page
: 0
}
totalItems={
isResponseSuccess(checklistListRes)
? checklistListRes?.meta?.total_results
: 0
}
onPageChange={setPage}
isLoading={isLoadingChecklistList}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
enableRowSelection={tableEnableRowSelectionHandler}
withCheckbox
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(checklistListRes) &&
checklistListRes?.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',
paginationClassName: 'px-4',
}}
/>
</CardContent>
</Card>
</div>
{/* Approve Modal */}
<Dialog open={showApproveModal} onOpenChange={setShowApproveModal}>
<DialogContent className='sm:max-w-md bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle>Approve Checklist</DialogTitle>
<DialogDescription>
Apakah Anda yakin ingin approve checklist ini?
</DialogDescription>
</DialogHeader>
{selectedItem && (
<div className='bg-gray-50 rounded-lg p-4 space-y-2'>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Tanggal:</span>
<span className='font-medium text-gray-900'>
{formatDate(selectedItem.date)}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kandang:</span>
<span className='font-medium text-gray-900'>
{selectedItem.kandang?.name ?? '-'}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kategori:</span>
<span className='font-medium text-gray-900'>
{CATEGORY_LABELS[selectedItem.category] ||
selectedItem.category}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Progress:</span>
<span className='font-medium text-gray-900'>
{selectedItem.progress}%
</span>
</div>
</div>
)}
<DialogFooter className='flex gap-2'>
<Button
variant='outline'
onClick={() => setShowApproveModal(false)}
disabled={actionLoading}
className='border-gray-200'
>
Batal
</Button>
<Button
onClick={confirmApprove}
disabled={actionLoading}
className='bg-green-600 hover:bg-green-700 text-white'
>
{actionLoading ? 'Memproses...' : 'Ya, Approve'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Bulk Approve Modal */}
<Dialog
open={showBulkApproveModal}
onOpenChange={setShowBulkApproveModal}
>
<DialogContent className='sm:max-w-md max-h-[80vh] overflow-y-auto bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle>Approve Checklist</DialogTitle>
<DialogDescription>
Apakah Anda yakin ingin approve {selectedRowIds.length} checklist
ini?
</DialogDescription>
</DialogHeader>
<div className='max-h-[60vh] overflow-y-auto flex flex-col gap-3'>
{selectedRowItems.map((item) => (
<div
key={item?.id ?? 0}
className='bg-gray-50 rounded-lg p-4 space-y-2'
>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Tanggal:</span>
<span className='font-medium text-gray-900'>
{formatDate(item?.date ?? '')}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kandang:</span>
<span className='font-medium text-gray-900'>
{item?.kandang?.name ?? '-'}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kategori:</span>
<span className='font-medium text-gray-900'>
{item?.category
? (CATEGORY_LABELS[item.category] ?? item?.category)
: item?.category}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Progress:</span>
<span className='font-medium text-gray-900'>
{item?.progress}%
</span>
</div>
</div>
))}
</div>
<DialogFooter className='flex gap-2'>
<Button
variant='outline'
onClick={() => setShowBulkApproveModal(false)}
disabled={actionLoading}
className='border-gray-200'
>
Batal
</Button>
<Button
onClick={confirmBulkApprove}
disabled={actionLoading}
className='bg-green-600 hover:bg-green-700 text-white'
>
{actionLoading ? 'Memproses...' : 'Ya, Approve'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Reject Modal */}
<Dialog open={showRejectModal} onOpenChange={setShowRejectModal}>
<DialogContent className='sm:max-w-md bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle>Reject Checklist</DialogTitle>
<DialogDescription>
Berikan alasan reject untuk checklist ini
</DialogDescription>
</DialogHeader>
{selectedItem && (
<div className='bg-gray-50 rounded-lg p-4 space-y-2 mb-4'>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Tanggal:</span>
<span className='font-medium text-gray-900'>
{formatDate(selectedItem.date)}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kandang:</span>
<span className='font-medium text-gray-900'>
{selectedItem.kandang?.name ?? '-'}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kategori:</span>
<span className='font-medium text-gray-900'>
{CATEGORY_LABELS[selectedItem.category] ||
selectedItem.category}
</span>
</div>
</div>
)}
<div>
<Label htmlFor='reject-reason'>
Alasan Reject <span className='text-red-500'>*</span>
</Label>
<Textarea
id='reject-reason'
value={rejectReason}
onChange={(e) => setRejectReason(e.target.value)}
placeholder='Tuliskan alasan reject...'
className='mt-1.5 border-gray-200 min-h-[100px]'
disabled={actionLoading}
/>
</div>
<DialogFooter className='flex gap-2'>
<Button
variant='outline'
onClick={() => setShowRejectModal(false)}
disabled={actionLoading}
className='border-gray-200'
>
Batal
</Button>
<Button
onClick={confirmReject}
disabled={actionLoading}
variant='destructive'
className='bg-red-600 hover:bg-red-700 text-white'
>
{actionLoading ? 'Memproses...' : 'Ya, Reject'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Bulk Reject Modal */}
<Dialog open={showBulkRejectModal} onOpenChange={setShowBulkRejectModal}>
<DialogContent className='sm:max-w-md bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle>Reject Checklist</DialogTitle>
<DialogDescription>
Berikan alasan reject untuk checklist ini
</DialogDescription>
</DialogHeader>
<div className='max-h-[60vh] overflow-y-auto flex flex-col gap-3'>
{selectedRowItems.map((item) => (
<div
key={item?.id ?? 0}
className='bg-gray-50 rounded-lg p-4 space-y-2 mb-4'
>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Tanggal:</span>
<span className='font-medium text-gray-900'>
{formatDate(item?.date ?? '')}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kandang:</span>
<span className='font-medium text-gray-900'>
{item?.kandang?.name ?? '-'}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kategori:</span>
<span className='font-medium text-gray-900'>
{item?.category
? CATEGORY_LABELS[item.category] || item?.category
: item?.category}
</span>
</div>
</div>
))}
</div>
<div>
<Label htmlFor='reject-reason'>
Alasan Reject <span className='text-red-500'>*</span>
</Label>
<Textarea
id='reject-reason'
value={rejectReason}
onChange={(e) => setRejectReason(e.target.value)}
placeholder='Tuliskan alasan reject...'
className='mt-1.5 border-gray-200 min-h-[100px]'
disabled={actionLoading}
/>
</div>
<DialogFooter className='flex gap-2'>
<Button
variant='outline'
onClick={() => setShowBulkRejectModal(false)}
disabled={actionLoading}
className='border-gray-200'
>
Batal
</Button>
<Button
onClick={confirmBulkReject}
disabled={actionLoading}
variant='destructive'
className='bg-red-600 hover:bg-red-700 text-white'
>
{actionLoading ? 'Memproses...' : 'Ya, Reject'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Delete Modal */}
<Dialog open={showDeleteModal} onOpenChange={setShowDeleteModal}>
<DialogContent className='sm:max-w-md bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle className='text-red-600'>
Hapus Checklist
</DialogTitle>
<DialogDescription>
Apakah Anda yakin ingin menghapus checklist ini? Data yang dihapus
tidak dapat dikembalikan.
</DialogDescription>
</DialogHeader>
{selectedItem && (
<>
<div className='bg-red-50 border border-red-200 rounded-lg p-4 space-y-2 mb-2'>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Tanggal:</span>
<span className='font-medium text-gray-900'>
{formatDate(selectedItem.date)}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kandang:</span>
<span className='font-medium text-gray-900'>
{selectedItem.kandang?.name ?? '-'}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Kategori:</span>
<span className='font-medium text-gray-900'>
{CATEGORY_LABELS[selectedItem.category] ||
selectedItem.category}
</span>
</div>
<div className='flex justify-between text-sm'>
<span className='text-gray-600'>Status:</span>
{getStatusBadge(selectedItem.status)}
</div>
</div>
<div className='bg-yellow-50 border border-yellow-200 rounded-lg p-3'>
<p className='text-xs text-yellow-800'>
<strong>Peringatan:</strong> Semua data terkait (phases,
activities, assignments) akan ikut terhapus secara permanen.
</p>
</div>
</>
)}
<DialogFooter className='flex gap-2'>
<Button
variant='outline'
onClick={() => setShowDeleteModal(false)}
disabled={actionLoading}
className='border-gray-200'
>
Batal
</Button>
<Button
onClick={confirmDelete}
disabled={actionLoading}
variant='destructive'
className='bg-red-600 hover:bg-red-700 text-white'
>
{actionLoading ? 'Memproses...' : 'Ya, Hapus'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}