refactor(FE): refactor chickin views and adjust approval logic in project flocks

This commit is contained in:
randy-ar
2025-12-06 00:15:30 +07:00
parent 885e4250fd
commit 85fdb4f7dd
8 changed files with 440 additions and 307 deletions
@@ -44,7 +44,7 @@ export default function AddChickinKandang() {
return ( return (
<> <>
<section className='w-full p-4'> <section className='size-full'>
{isLoading && <span className='loading loading-spinner loading-xl' />} {isLoading && <span className='loading loading-spinner loading-xl' />}
{!isLoading && {!isLoading &&
isResponseSuccess(projectFlockKandang) && isResponseSuccess(projectFlockKandang) &&
+20 -10
View File
@@ -1,5 +1,6 @@
'use client'; 'use client';
import Button from '@/components/Button';
import Tooltip from '@/components/Tooltip'; import Tooltip from '@/components/Tooltip';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -11,12 +12,14 @@ type FloatingActionsButtonProps = {
label?: string; label?: string;
onClick?: () => void; onClick?: () => void;
hidden?: boolean; hidden?: boolean;
disabled?: boolean;
}[]; }[];
approvals: { approvals: {
action: 'APPROVED' | 'REJECTED'; action: 'APPROVED' | 'REJECTED';
icon: string; icon: string;
label?: string; label?: string;
onClick?: () => void; onClick?: () => void;
disabled?: boolean;
}[]; }[];
selectedRowIds: number[]; selectedRowIds: number[];
onClose: () => void; onClose: () => void;
@@ -69,10 +72,12 @@ const FloatingActionsButton = ({
.filter((action) => !action.hidden) .filter((action) => !action.hidden)
.map((action, index) => { .map((action, index) => {
return ( return (
<button <Button
key={index} key={index}
onClick={action.onClick} onClick={action.onClick}
className='text-white hover:text-gray-400 tooltip tooltip-bottom' className='text-white hover:text-gray-400 tooltip tooltip-bottom p-0'
variant='link'
disabled={action.disabled}
> >
<Tooltip content={action.label || action.action}> <Tooltip content={action.label || action.action}>
<Icon <Icon
@@ -82,21 +87,22 @@ const FloatingActionsButton = ({
className={`text-${getActionColor(action.action)} font-thin`} className={`text-${getActionColor(action.action)} font-thin`}
/> />
</Tooltip> </Tooltip>
</button> </Button>
); );
})} })}
<div className='border-[0.5px] border-white/30 h-full'></div> <div className='border-[0.5px] border-white/30 h-full'></div>
{/* Tombol Close */} {/* Tombol Close */}
<button <Button
onClick={onClose} onClick={onClose}
className='text-white hover:text-gray-400' className='text-white hover:text-gray-400 p-0'
variant='link'
> >
<Tooltip content='Close'> <Tooltip content='Close'>
<Icon icon='mdi:close' width={20} height={20} /> <Icon icon='mdi:close' width={20} height={20} />
</Tooltip> </Tooltip>
</button> </Button>
</div> </div>
</div> </div>
</div> </div>
@@ -104,14 +110,18 @@ const FloatingActionsButton = ({
{/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */} {/* === BARIS BAWAH: Approval Buttons (Approve/Reject) === */}
<div className={`grid grid-cols-${approvals.length} gap-3`}> <div className={`grid grid-cols-${approvals.length} gap-3`}>
{approvals.map((approval, index) => ( {approvals.map((approval, index) => (
<button <Button
key={index} key={index}
onClick={approval.onClick} onClick={approval.onClick}
className={cn( className={cn(
'btn btn-lg w-full', 'btn btn-lg w-full',
'bg-white/20 hover:bg-white/40 border-white/30 hover:border-white/50', 'bg-white/20 border-white/30',
'text-white/50 hover:text-white/100 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200' 'text-white/50 font-semibold flex items-center gap-2 rounded-lg transition-all duration-200',
approval.disabled
? 'cursor-not-allowed'
: 'hover:text-white/100 hover:bg-white/40 hover:border-white/50'
)} )}
disabled={approval.disabled}
> >
<Icon <Icon
icon={approval.icon} icon={approval.icon}
@@ -120,7 +130,7 @@ const FloatingActionsButton = ({
className={`text-${getApprovalColor(approval.action)}`} className={`text-${getApprovalColor(approval.action)}`}
/> />
{approval.label || approval.action} {approval.label || approval.action}
</button> </Button>
))} ))}
</div> </div>
</div> </div>
@@ -14,6 +14,9 @@ import ApprovalSteps, {
import { PROJECT_FLOCK_KANDANG_APPROVAL_LINE } from '@/config/approval-line'; import { PROJECT_FLOCK_KANDANG_APPROVAL_LINE } from '@/config/approval-line';
import ChickinFormView from '@/components/pages/production/chickin/form/tabs/ChickinFormView'; import ChickinFormView from '@/components/pages/production/chickin/form/tabs/ChickinFormView';
import ChickinLogsView from '@/components/pages/production/chickin/form/tabs/ChickLogsView'; import ChickinLogsView from '@/components/pages/production/chickin/form/tabs/ChickLogsView';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
import { Icon } from '@iconify/react';
import Badge from '@/components/Badge';
const ChickinFormKandang = ({ const ChickinFormKandang = ({
formType = 'add', formType = 'add',
initialValues, initialValues,
@@ -24,6 +27,7 @@ const ChickinFormKandang = ({
afterSubmit?: () => void; afterSubmit?: () => void;
}) => { }) => {
const [activeTabId, setActiveTabId] = useState<string>('formChickIn'); const [activeTabId, setActiveTabId] = useState<string>('formChickIn');
const [openChickin, setOpenChickin] = useState<boolean>(false);
const { const {
approvals, approvals,
@@ -43,102 +47,142 @@ const ChickinFormKandang = ({
}; };
return ( return (
<div className='flex flex-col gap-4'> <>
<FormHeader <DrawerHeader
title={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`} subtitle={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`}
backUrl={`/production/project-flock/detail?projectFlockId=${initialValues?.project_flock?.id}`} leftIcon='mdi:arrow-left'
leftIconHref={`/production/project-flock/detail?projectFlockId=${initialValues?.project_flock?.id}`}
/> />
{approvals && !approvalsLoading && ( {/* Informasi Kandang */}
<ApprovalSteps approvals={approvals} /> <div className='divider'></div>
)} <div className='px-4 pb-4 flex flex-col gap-4'>
<h2 className='text-xl font-semibold'>Informasi Kandang</h2>
<Card {approvals && !approvalsLoading && (
title='Informasi Kandang' <div className='mb-3 text-sm'>
className={{ <ApprovalSteps approvals={approvals} />
wrapper: 'w-full bg-white mt-4', </div>
}} )}
>
<Table<Kandang> {/* Badge Row */}
emptyContent={ <div className='flex flex-row gap-2'>
<div className='w-full p-5 text-center'> <Badge
<span className='text-lg opacity-50'> variant='soft'
Informasi Kandang belum tersedia... color='success'
</span> className={{
</div> badge: 'rounded-lg px-2',
} }}
data={[initialValues?.kandang]} >
columns={[ <Icon icon='mdi:circle' width={12} height={12} color='success' />{' '}
{ Aktif
header: 'Area', </Badge>
accessorFn: () => initialValues?.project_flock?.area.name || '-', <div className='divider divider-horizontal p-0 m-0'></div>
}, <Badge
{ color='neutral'
header: 'Lokasi', variant='soft'
accessorFn: () => className={{ badge: 'rounded-lg px-2' }}
initialValues?.project_flock?.location.name || '-', >
}, <Icon icon='mdi:home' width={12} height={12} />
{ {` Kapasitas ${formatNumber(initialValues.kandang.capacity)} Ekor`}
header: 'Flock', </Badge>
accessorFn: () => initialValues?.project_flock?.flock_name || '-', </div>
},
{ {/* Information Grid */}
header: 'Kandang', <div className='grid grid-cols-3 gap-4'>
accessorFn: (row) => row?.name || '-', {/* Area */}
}, <div
{ className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
header: 'Kapasitas', relative
accessorFn: (row) => before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
(row?.capacity && formatNumber(row?.capacity)) || '-', >
}, <Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
{ </div>
header: 'Penanggung Jawab', <div className='col-span-2'>
accessorFn: (row) => row?.pic?.name || '-', {initialValues.project_flock.area.name}
}, </div>
]}
className={{ {/* Lokasi */}
tableWrapperClassName: 'overflow-x-auto min-h-full!', <div
tableClassName: 'font-inter w-full table-auto min-h-full!', className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
headerRowClassName: 'border-b border-b-gray-200', relative
headerColumnClassName: before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', >
bodyRowClassName: 'border-b border-b-gray-200', <Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
bodyColumnClassName: </div>
'px-6 py-3 last:flex last:flex-row last:justify-end', <div className='col-span-2'>
paginationClassName: 'hidden', {initialValues.project_flock?.location.name}
}} </div>
{/* Kandang */}
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Kandang
</div>
<div className='col-span-2'>{initialValues.kandang.name}</div>
{/* Jumlah DOC */}
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Jumlah DOC
</div>
<div className='col-span-2'>
{formatNumber(
initialValues.chickins?.reduce(
(total, chickin) => total + chickin.usage_qty,
0
) ?? 0
)}{' '}
Ekor
</div>
</div>
</div>
<div className='divider'></div>
<div className='px-4 pb-4 flex flex-col gap-4'>
<h2 className='text-xl font-semibold'>Informasi Chick In</h2>
{/* Badge Row */}
<div className='flex flex-row gap-2'>
<Badge
variant='soft'
color={'success'}
className={{
badge: 'rounded-lg px-2',
}}
>
<Icon icon='mdi:circle' width={12} height={12} color={'success'} />{' '}
Perlu Chick In ({initialValues.available_qtys?.length ?? 0})
</Badge>
<div className='divider divider-horizontal p-0 m-0'></div>
<Badge
color='neutral'
variant='soft'
className={{ badge: 'rounded-lg px-2 cursor-pointer' }}
onClick={() => setOpenChickin(!openChickin)}
>
{`Riwayat Chick In ${formatNumber(initialValues.chickins?.length ?? 0)}`}
<Icon
icon={`mdi:${openChickin ? 'eye' : 'eye-off'}`}
width={12}
height={12}
/>
</Badge>
</div>
</div>
{openChickin && (
<ChickinLogsView
initialValues={initialValues}
afterSubmit={afterSubmitFormChickin}
/> />
</Card> )}
<Tabs <ChickinFormView
className='bg-white p-2' initialValues={initialValues}
onTabChange={setActiveTabId} formType={formType}
activeTabId={activeTabId} afterSubmit={afterSubmitFormChickin}
tabs={[
{
id: 'formChickIn',
label: 'Tambah Chick In',
content: (
<ChickinFormView
initialValues={initialValues}
formType={formType}
afterSubmit={afterSubmitFormChickin}
/>
),
},
{
content: (
<ChickinLogsView
initialValues={initialValues}
afterSubmit={afterSubmitFormChickin}
/>
),
id: 'logsChickIn',
label: 'Riwayat Chick In',
},
]}
variant='lifted'
/> />
</div> </>
); );
}; };
@@ -2,17 +2,12 @@ import Alert from '@/components/Alert';
import Button from '@/components/Button'; import Button from '@/components/Button';
import Card from '@/components/Card'; import Card from '@/components/Card';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import PillBadge from '@/components/PillBadge'; import PillBadge from '@/components/PillBadge';
import Table from '@/components/Table';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn, formatDate, formatNumber } from '@/lib/helper'; import { formatDate, formatNumber } from '@/lib/helper';
import { ChickinApi } from '@/services/api/production/chickin'; import { ChickinApi } from '@/services/api/production/chickin';
import { import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
Chickin,
ProjectFlockKandang,
} from '@/types/api/production/project-flock-kandang';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -54,105 +49,120 @@ const ChickinLogsView = ({
return ( return (
<> <>
<Card <div className='px-4 pb-4 flex flex-col gap-4'>
title='Riwayat Chick In' {/* Card List Chickin Logs */}
className={{ {(initialValues?.chickins || []).length === 0 ? (
wrapper: 'w-full bg-white', <div className='w-full p-8 text-center'>
}} <span className='text-lg opacity-50'>
> Belum ada riwayat Chick In...
<div className='flex flex-row justify-start gap-3 mt-3'> </span>
{initialValues?.approval?.step_number == 1 && ( </div>
<Button ) : (
color='success' (initialValues?.chickins || []).map((chickin, index) => {
variant='outline' const isApproved = chickin.usage_qty !== 0;
onClick={handleClickApprove} const isPending = chickin.pending_usage_qty !== 0;
> const quantity = isApproved
<Icon width={24} height={24} icon='material-symbols:check' /> ? chickin.usage_qty
Approve : isPending
</Button> ? chickin.pending_usage_qty
)} : 0;
</div>
<Table<Chickin> return (
data={initialValues?.chickins || []} <Card
columns={[ key={chickin.id || index}
{ variant='bordered'
header: '#', className={{
cell: (props) => props.row.index + 1, wrapper: 'w-full',
}, body: 'p-3',
{ }}
accessorFn: (row) => row.chick_in_date, >
header: 'Tanggal Chick In', <div className='flex flex-col gap-4'>
cell: (props) => { {/* Header with Status Badge */}
return formatDate(props.getValue() as string, 'DD MMM YYYY'); <div className='flex flex-row justify-between items-center'>
}, <div className='text-lg font-semibold'>
}, Chick In #{index + 1}
{ </div>
accessorFn: (row) => row.product_warehouse?.warehouse?.name, <PillBadge
header: 'Kandang', content={
}, isApproved ? 'Disetujui' : isPending ? 'Pending' : '-'
{ }
accessorFn: (row) => row.product_warehouse?.product?.name, color={
header: 'Produk', isApproved ? 'green' : isPending ? 'yellow' : 'gray'
}, }
{ />
accessorFn: (row) => row.usage_qty ?? row.pending_usage_qty, </div>
header: 'Jumlah Chick In',
cell: (props) => { {/* Tanggal Chick In */}
if (props.row.original.usage_qty != 0) { <div className='flex flex-row justify-between items-center'>
return formatNumber(props.row.original.usage_qty); <div className='flex flex-row gap-2 items-center text-gray-400'>
} else if (props.row.original.pending_usage_qty != 0) { <Icon icon={'mdi:calendar'} width={14} height={14} />{' '}
return formatNumber(props.row.original.pending_usage_qty); <span>Tanggal Chick In</span>
} else { </div>
return '-'; <div className='text-end text-gray-500'>
} {formatDate(chickin.chick_in_date, 'DD MMM YYYY')}
}, </div>
}, </div>
{
accessorFn: (row) => row.pending_usage_qty, {/* Kandang */}
header: 'Status', <div className='flex flex-row justify-between items-center'>
cell: (props) => { <div className='flex flex-row gap-2 items-center text-gray-400'>
return ( <Icon icon={'mdi:home'} width={14} height={14} />{' '}
<PillBadge <span>Kandang</span>
content={ </div>
props.row.original.usage_qty !== 0 <div className='text-end text-gray-500'>
? 'Disetujui' {chickin.product_warehouse?.warehouse?.name || '-'}
: props.row.original.pending_usage_qty !== 0 </div>
? 'Pending' </div>
: '-'
} {/* Produk */}
color={ <div className='flex flex-row justify-between items-center'>
props.row.original.usage_qty !== 0 <div className='flex flex-row gap-2 items-center text-gray-400'>
? 'green' <Icon
: props.row.original.pending_usage_qty !== 0 icon={'mdi:package-variant'}
? 'yellow' width={14}
: 'gray' height={14}
} />{' '}
/> <span>Produk</span>
); </div>
}, <div className='text-end text-gray-500'>
}, {chickin.product_warehouse?.product?.name || '-'}
]} </div>
className={{ </div>
containerClassName: cn({
'mb-20': initialValues?.chickins?.length === 0, {/* Jumlah Chick In */}
}), <div className='flex flex-row justify-between items-center'>
tableWrapperClassName: 'overflow-x-auto min-h-full!', <div className='flex flex-row gap-2 items-center text-gray-400'>
tableClassName: 'font-inter w-full table-auto min-h-full!', <Icon icon={'mdi:counter'} width={14} height={14} />{' '}
headerRowClassName: 'border-b border-b-gray-200', <span>Jumlah Chick In</span>
headerColumnClassName: </div>
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', <div className='text-end text-gray-500 font-semibold'>
bodyRowClassName: 'border-b border-b-gray-200', {quantity > 0 ? `${formatNumber(quantity)} Ekor` : '-'}
bodyColumnClassName: </div>
'px-6 py-3 last:flex last:flex-row last:justify-end', </div>
paginationClassName: 'hidden', </div>
}} </Card>
/> );
})
)}
{initialValues?.approval?.step_number == 1 && (
<Button
color='success'
onClick={handleClickApprove}
className='w-full'
>
<Icon width={24} height={24} icon='material-symbols:check' />
Approve Semua Chick In
</Button>
)}
{chickinErrorMessage && ( {chickinErrorMessage && (
<div className='w-full' onClick={() => setChickinErrorMessage('')}> <div className='w-full' onClick={() => setChickinErrorMessage('')}>
<Alert color='error'>{chickinErrorMessage}</Alert> <Alert color='error'>{chickinErrorMessage}</Alert>
</div> </div>
)} )}
</Card> </div>
<ConfirmationModalWithNotes <ConfirmationModalWithNotes
ref={confirmModal.ref} ref={confirmModal.ref}
type='success' type='success'
@@ -20,6 +20,7 @@ import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandan
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import Alert from '@/components/Alert'; import Alert from '@/components/Alert';
import { formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
import { Icon } from '@iconify/react';
const ChickinFormView = ({ const ChickinFormView = ({
initialValues, initialValues,
@@ -118,106 +119,142 @@ const ChickinFormView = ({
return ( return (
<form <form
className='flex flex-col gap-4' className='flex flex-col gap-4 p-4'
onReset={() => { onReset={() => {
handleReset(); handleReset();
}} }}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
> >
<Card {(formik.values.chickin_requests || []).map((chickinRequest, index) => {
title='Informasi Chick In DOC' const availableQty = initialValues?.available_qtys?.find(
className={{ (availableQty) =>
wrapper: 'w-full bg-white', availableQty.product_warehouse.id ===
}} chickinRequest.product_warehouse_id
> );
<Table<ChickinRequestFormValues> return (
data={formik.values.chickin_requests || []} <Card
columns={[ key={index}
{ // title={`${formatNumber(availableQty?.available_qty ?? 0)} Ekor - ${availableQty?.product_warehouse?.product?.name}`}
accessorFn: (row) => row.chick_in_date, variant='bordered'
header: 'Tanggal Chick In', size='sm'
cell(props) { className={{
return ( wrapper: 'w-full',
<DateInput body: 'p-3',
className={{ }}
wrapper: 'w-fit', >
inputWrapper: 'bg-white', <div className='flex flex-row justify-between items-center'>
}} <div className='text-lg font-semibold'>
name={`chickin_requests[${props.row.index}].chick_in_date`} {formatNumber(availableQty?.available_qty ?? 0)} Ekor -{' '}
value={ {availableQty?.product_warehouse?.product?.name}
formik.values.chickin_requests[props.row.index] </div>
?.chick_in_date as string {chickinRequest.chick_in_date && (
} <Icon
onChange={formik.handleChange} icon='mdi:check-circle-outline'
/> color='success'
); className='text-success'
}, width={20}
}, height={20}
{ />
accessorFn: (row) => row.product_warehouse_id, )}
header: 'Produk',
cell(props) {
const availableQty = initialValues?.available_qtys?.find(
(availableQty) =>
availableQty.product_warehouse.id ===
props.row.original.product_warehouse_id
);
return (
<div>{availableQty?.product_warehouse?.product?.name}</div>
);
},
},
{
accessorFn: (row) => row.product_warehouse_id,
header: 'Jumlah (ekor)',
cell(props) {
const availableQty = initialValues?.available_qtys?.find(
(availableQty) =>
availableQty.product_warehouse.id ===
props.row.original.product_warehouse_id
);
return (
<div>
{availableQty?.available_qty
? formatNumber(availableQty?.available_qty)
: '-'}
</div>
);
},
},
]}
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-2 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-2 py-2 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Isi persediaan DOC untuk kandang belum tersedia...
</span>
</div> </div>
} <DateInput
/> className={{
</Card> wrapper: 'w-full',
<div className='flex flex-row justify-center gap-3'> inputWrapper: 'bg-white',
<Button type='reset' color='warning' disabled={formik.isSubmitting}> }}
Reset label='Tanggal Chick In'
</Button> name={`chickin_requests[${index}].chick_in_date`}
value={chickinRequest.chick_in_date}
onChange={formik.handleChange}
/>
</Card>
);
})}
{/* <Table<ChickinRequestFormValues>
data={formik.values.chickin_requests || []}
columns={[
{
accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chick In',
cell(props) {
return (
<DateInput
className={{
wrapper: 'w-fit',
inputWrapper: 'bg-white',
}}
name={`chickin_requests[${props.row.index}].chick_in_date`}
value={
formik.values.chickin_requests[props.row.index]
?.chick_in_date as string
}
onChange={formik.handleChange}
/>
);
},
},
{
accessorFn: (row) => row.product_warehouse_id,
header: 'Produk',
cell(props) {
const availableQty = initialValues?.available_qtys?.find(
(availableQty) =>
availableQty.product_warehouse.id ===
props.row.original.product_warehouse_id
);
return (
<div>{availableQty?.product_warehouse?.product?.name}</div>
);
},
},
{
accessorFn: (row) => row.product_warehouse_id,
header: 'Jumlah (ekor)',
cell(props) {
const availableQty = initialValues?.available_qtys?.find(
(availableQty) =>
availableQty.product_warehouse.id ===
props.row.original.product_warehouse_id
);
return (
<div>
{availableQty?.available_qty
? formatNumber(availableQty?.available_qty)
: '-'}
</div>
);
},
},
]}
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-2 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-2 py-2 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Isi persediaan DOC untuk kandang belum tersedia...
</span>
</div>
}
/> */}
{formik.values.chickin_requests?.length > 0 && (
<Button <Button
type='submit' type='submit'
color='primary' color='primary'
disabled={!formik.isValid || formik.isSubmitting} disabled={!formik.isValid || formik.isSubmitting}
> >
Submit <Icon icon='mdi:checkbox-marked-outline' width={24} height={24} />
Chick In
</Button> </Button>
</div> )}
{chickinErrorMessage && ( {chickinErrorMessage && (
<div className='w-full' onClick={() => setChickinErrorMessage('')}> <div className='w-full' onClick={() => setChickinErrorMessage('')}>
<Alert color='error'>{chickinErrorMessage}</Alert> <Alert color='error'>{chickinErrorMessage}</Alert>
@@ -10,8 +10,6 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import Table from '@/components/Table'; import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
@@ -23,7 +21,7 @@ import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { CellContext, SortingState } from '@tanstack/react-table'; import { CellContext, SortingState } from '@tanstack/react-table';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { ChangeEventHandler, useEffect, useState } from 'react'; import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import useSWR from 'swr'; import useSWR from 'swr';
@@ -124,7 +122,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
}); });
const router = useRouter(); const router = useRouter();
// State // ===== State =====
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const selectedRowIds = Object.keys(rowSelection) const selectedRowIds = Object.keys(rowSelection)
.filter((id) => rowSelection[id]) .filter((id) => rowSelection[id])
@@ -151,7 +149,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
// Fetch Data // ===== Fetch Data =====
const { const {
data: projectFlocks, data: projectFlocks,
isLoading, isLoading,
@@ -192,7 +190,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
KandangApi.getAllFetcher KandangApi.getAllFetcher
); );
// Data to Options Mapping // ===== Data to Options Mapping ======
const optionsArea = isResponseSuccess(areas) const optionsArea = isResponseSuccess(areas)
? areas?.data.map((area) => ({ ? areas?.data.map((area) => ({
value: area.id, value: area.id,
@@ -212,7 +210,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
})) }))
: []; : [];
// Handler // ====== HANDLER ======
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
const newVal = val as OptionType; const newVal = val as OptionType;
setPageSize(newVal.value as number); setPageSize(newVal.value as number);
@@ -220,17 +218,17 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
await ProjectFlockApi.delete(selectedProjectFlock?.id as number); await ProjectFlockApi.delete(selectedSingleRow?.id as number);
refreshProjectFlocks(); refreshProjectFlocks();
deleteModal.closeModal(); deleteModal.closeModal();
toast.success('Successfully delete Project Flock!'); toast.success('Successfully delete Project Flock!');
setIsDeleteLoading(false); setIsDeleteLoading(false);
setRowSelection({});
}; };
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
updateFilter('search', e.target.value); updateFilter('search', e.target.value);
}; };
const confirmApprovalHandler = async ( const confirmApprovalHandler = async (
notes: string, notes: string,
approvalAction: 'APPROVED' | 'REJECTED' approvalAction: 'APPROVED' | 'REJECTED'
@@ -260,10 +258,29 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
setIsApproveLoading(false); setIsApproveLoading(false);
}; };
// ====== EFFECT ======
useEffect(() => { useEffect(() => {
refreshProjectFlocks(); refreshProjectFlocks();
}, [refresh]); }, [refresh]);
// ====== MEMO ======
const selectedSingleRow: ProjectFlock | null | undefined = useMemo(() => {
return selectedRowIds.length === 1
? isResponseSuccess(projectFlocks)
? projectFlocks?.data.find((row) => row.id === selectedRowIds[0])
: null
: null;
}, [rowSelection]);
const canApprove = useMemo(() => {
if (!selectedSingleRow || isApproveLoading) return false;
const isPengajuan = selectedSingleRow.approval.step_number == 1;
const isNotRejected = selectedSingleRow.approval.action != 'REJECTED';
return isPengajuan && isNotRejected;
}, [selectedSingleRow, isApproveLoading]);
return ( return (
<> <>
<div className='min-h-screen w-full p-0 sm:p-4'> <div className='min-h-screen w-full p-0 sm:p-4'>
@@ -617,9 +634,10 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
{ {
action: 'DELETE', action: 'DELETE',
icon: 'material-symbols:delete-outline-rounded', icon: 'material-symbols:delete-outline-rounded',
label: `Hapus ${selectedRowIds.length} data`, label: `Hapus data`,
hidden: selectedRowIds.length !== 1,
onClick: () => { onClick: () => {
toast.error(`Konfirmasi hapus ${selectedRowIds.length} data.`); deleteModal.openModal();
}, },
}, },
]} ]}
@@ -632,6 +650,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
setApprovalAction('APPROVED'); setApprovalAction('APPROVED');
confirmModal.openModal(); confirmModal.openModal();
}, },
disabled: !canApprove,
}, },
{ {
icon: 'mdi:times', icon: 'mdi:times',
@@ -102,7 +102,7 @@ const ProjectFlockClosingForm = ({
className={{ badge: 'rounded-lg px-2' }} className={{ badge: 'rounded-lg px-2' }}
> >
<Icon icon='mdi:home' width={12} height={12} /> <Icon icon='mdi:home' width={12} height={12} />
{` Kapasitas ${formatNumber(projectFlockKandang.kandang.capacity)} Ekor`} {` Kapasitas ${formatNumber(projectFlockKandang.kandang?.capacity)} Ekor`}
</Badge> </Badge>
</div> </div>
@@ -34,6 +34,10 @@ const ProjectFlockDetail = ({
null null
); );
const selectedKandang = projectFlock.kandangs.find(
(kandang) => kandang.id === Number(selectedKandangId)
);
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
const deleteProjectFlockRes = await ProjectFlockApi.delete( const deleteProjectFlockRes = await ProjectFlockApi.delete(
@@ -328,16 +332,21 @@ const ProjectFlockDetail = ({
value={selectedKandangId?.toString()} value={selectedKandangId?.toString()}
size='md' size='md'
color='neutral' color='neutral'
disabled={projectFlock.approval.step_number == 1}
> >
{projectFlock.kandangs.map((kandang) => ( {projectFlock.kandangs.map((kandang) => (
<div <div
key={kandang.id} key={kandang.id}
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`} className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`}
onClick={() => setSelectedKamdangId(kandang.id.toString())} onClick={() =>
projectFlock.approval.step_number > 1 &&
setSelectedKamdangId(kandang.id.toString())
}
> >
<RadioGroupItem <RadioGroupItem
value={kandang.id.toString()} value={kandang.id.toString()}
label={kandang.name} label={kandang.name}
disabled={projectFlock.approval.step_number == 1}
/> />
<div className='text-end'> <div className='text-end'>
<Badge <Badge
@@ -354,14 +363,16 @@ const ProjectFlockDetail = ({
</Card> </Card>
<div className='grid grid-cols-4 gap-3'> <div className='grid grid-cols-4 gap-3'>
<Link <Link
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandangId}&projectFlockId=${projectFlock.id}`} href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
className='m-0 p-0' className='m-0 p-0'
> >
<Button <Button
className='w-full px-2 py-1 text-sm' className='w-full px-2 py-1 text-sm'
variant='outline' variant='outline'
color='success' color='success'
disabled={!selectedKandangId} disabled={
!selectedKandangId || projectFlock.approval.step_number == 1
}
> >
Chickin <Icon icon='mdi:checkbox-marked-outline' /> Chickin <Icon icon='mdi:checkbox-marked-outline' />
</Button> </Button>
@@ -374,7 +385,9 @@ const ProjectFlockDetail = ({
className='w-full px-2 py-1 text-sm' className='w-full px-2 py-1 text-sm'
variant='outline' variant='outline'
color='error' color='error'
disabled={!selectedKandangId} disabled={
!selectedKandangId || projectFlock.approval.step_number == 1
}
> >
Close <Icon icon='mdi:checkbox-marked-circle-outline' /> Close <Icon icon='mdi:checkbox-marked-circle-outline' />
</Button> </Button>