mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): refactor chickin views and adjust approval logic in project flocks
This commit is contained in:
@@ -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) &&
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user