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