feat(FE-314-315): API Integration project budgets and refactoring UI

This commit is contained in:
randy-ar
2025-12-03 21:09:12 +07:00
parent 31f758d680
commit f0ec758d7f
10 changed files with 544 additions and 572 deletions
+33 -20
View File
@@ -7,26 +7,39 @@
default: false; default: false;
prefersdark: false; prefersdark: false;
color-scheme: 'light'; color-scheme: 'light';
--color-base-100: oklch(98% 0.001 106.423);
--color-base-200: oklch(97% 0.001 106.424); /* Primary Colors */
--color-base-300: oklch(92% 0.003 48.717); --color-primary: oklch(39.4% 0.177 301.9);
--color-base-content: oklch(22.389% 0.031 278.072); --color-primary-content: oklch(87.5% 0.038 274.5);
--color-primary: oklch(60% 0.126 221.723);
--color-primary-content: oklch(100% 0 0); /* Secondary Colors */
--color-secondary: oklch(52% 0.105 223.128); --color-secondary: oklch(60.1% 0.258 335.7);
--color-secondary-content: oklch(100% 0 0); --color-secondary-content: oklch(99.4% 0.007 337.8);
--color-accent: oklch(45% 0.085 224.283);
--color-accent-content: oklch(100% 0 0); /* Accent Colors */
--color-neutral: oklch(39% 0.07 227.392); --color-accent: oklch(76.2% 0.155 170.8);
--color-neutral-content: oklch(100% 0 0); --color-accent-content: oklch(7.2% 0.007 167.6);
--color-info: oklch(58% 0.158 241.966);
--color-info-content: oklch(100% 0 0); /* Neutral Colors */
--color-success: oklch(62% 0.194 149.214); --color-neutral: oklch(22.4% 0.032 258.8);
--color-success-content: oklch(100% 0 0); --color-neutral-content: oklch(87.7% 0.016 257.0);
--color-warning: oklch(85% 0.199 91.936);
--color-warning-content: oklch(0% 0 0); /* Base Colors */
--color-error: oklch(57% 0.245 27.325); --color-base-100: oklch(100% 0 0); /* #ffffff */
--color-error-content: oklch(100% 0 0); --color-base-200: oklch(97.2% 0 0); /* #f2f2f2 */
--color-base-300: oklch(93.1% 0.002 249.7); /* #e5e6e6 */
--color-base-content: oklch(18.6% 0.024 257.7); /* #1f2937 */
/* Status/Utility Colors */
--color-info: oklch(67.4% 0.176 238.9);
--color-info-content: oklch(0% 0 0); /* #000000 */
--color-success: oklch(62.3% 0.147 149.0);
--color-success-content: oklch(100% 0 0); /* #ffffff */
--color-warning: oklch(82.2% 0.165 91.9);
--color-warning-content: oklch(0% 0 0); /* #000000 */
--color-error: oklch(61.8% 0.203 27.8);
--color-error-content: oklch(100% 0 0); /* #fffffff */
--radius-selector: 0rem; --radius-selector: 0rem;
--radius-field: 0.25rem; --radius-field: 0.25rem;
--radius-box: 0.25rem; --radius-box: 0.25rem;
@@ -12,7 +12,7 @@ const AddProjectFlock = () => {
// }, // },
// })); // }));
return ( return (
<section className='w-full p-4 flex flex-row justify-center'> <section className='w-full flex flex-row justify-center'>
<ProjectFlockForm formType='add' /> <ProjectFlockForm formType='add' />
</section> </section>
); );
@@ -37,7 +37,7 @@ const ProjectFlockEdit = () => {
} }
return ( return (
<div className='w-full p-4 flex flex-col justify-center'> <div className='w-full flex flex-col justify-center'>
{isLoadingProjectFlock && ( {isLoadingProjectFlock && (
<span className='loading loading-spinner loading-xl' /> <span className='loading loading-spinner loading-xl' />
)} )}
+13 -9
View File
@@ -1,5 +1,6 @@
'use client'; 'use client';
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';
@@ -50,7 +51,7 @@ const FloatingActionsButton = ({
<div <div
className={cn( className={cn(
`absolute ${positionStyles} inset-x-1/2 -translate-x-1/2 z-50`, `absolute ${positionStyles} inset-x-1/2 -translate-x-1/2 z-50`,
'mx-auto w-full max-w-xl sm:mx-0 bg-base-300 p-4 rounded-xl shadow-md transition-all duration-300 transform', 'mx-auto w-full max-w-lg sm:mx-0 bg-base-300 p-4 rounded-xl shadow-md transition-all duration-300 transform',
'bg-slate-950 backdrop-blur-md' 'bg-slate-950 backdrop-blur-md'
)} )}
> >
@@ -72,14 +73,15 @@ const FloatingActionsButton = ({
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'
data-tip={action.label}
> >
<Icon <Tooltip content={action.label || action.action}>
icon={action.icon} <Icon
width={24} icon={action.icon}
height={24} width={20}
className={`text-${getActionColor(action.action)} font-thin`} height={20}
/> className={`text-${getActionColor(action.action)} font-thin`}
/>
</Tooltip>
</button> </button>
); );
})} })}
@@ -91,7 +93,9 @@ const FloatingActionsButton = ({
onClick={onClose} onClick={onClose}
className='text-white hover:text-gray-400' className='text-white hover:text-gray-400'
> >
<Icon icon='mdi:close' width={24} height={24} /> <Tooltip content='Close'>
<Icon icon='mdi:close' width={20} height={20} />
</Tooltip>
</button> </button>
</div> </div>
</div> </div>
+1 -1
View File
@@ -53,7 +53,7 @@ const CheckboxInput = ({
id={name} id={name}
name={name} name={name}
className={cn( className={cn(
'checkbox cursor-pointer', 'checkbox rounded-md cursor-pointer',
{ {
'border-error': isError, 'border-error': isError,
'border-success': isValid, 'border-success': isValid,
@@ -1,5 +1,6 @@
'use client'; 'use client';
import Badge from '@/components/Badge';
import Button from '@/components/Button'; import Button from '@/components/Button';
import FloatingActionsButton from '@/components/FloatingActionsButton'; import FloatingActionsButton from '@/components/FloatingActionsButton';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
@@ -13,7 +14,7 @@ import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; 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 } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data'; import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data';
import { ProjectFlockApi } from '@/services/api/production/project-flock'; import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
@@ -270,7 +271,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
<div className='w-full flex flex-col justify-between items-end gap-2'> <div className='w-full flex flex-col justify-between items-end gap-2'>
<div className='flex flex-col sm:flex-row gap-3 w-full'> <div className='flex flex-col sm:flex-row gap-3 w-full'>
<Button <Button
variant='outline'
color='primary' color='primary'
className='w-full sm:w-fit' className='w-full sm:w-fit'
href='/production/project-flock/add' href='/production/project-flock/add'
@@ -278,7 +278,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Tambah
</Button> </Button>
<Button {/* <Button
variant='outline' variant='outline'
color='success' color='success'
onClick={() => { onClick={() => {
@@ -303,7 +303,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
> >
<Icon icon='mdi:times' width={24} height={24} /> <Icon icon='mdi:times' width={24} height={24} />
Reject Reject
</Button> </Button> */}
<div className='ms-auto w-full sm:w-auto'> <div className='ms-auto w-full sm:w-auto'>
<DebouncedTextInput <DebouncedTextInput
name='search' name='search'
@@ -395,9 +395,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
id: 'select', id: 'select',
header: ({ table }) => { header: ({ table }) => {
const allRows = table.getRowModel().rows; const allRows = table.getRowModel().rows;
const selectableRows = allRows.filter( const selectableRows = allRows;
(row) => row.original?.approval?.step_number == 1
);
const allSelected = const allSelected =
selectableRows.every((row) => row.getIsSelected()) && selectableRows.every((row) => row.getIsSelected()) &&
@@ -421,12 +419,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
checked={allSelected} checked={allSelected}
indeterminate={someSelected} indeterminate={someSelected}
onChange={toggleSelectableRows} onChange={toggleSelectableRows}
disabled={
isResponseSuccess(projectFlocks) &&
projectFlocks?.data?.filter(
(flock) => flock.approval.step_number == 1
).length == 0
}
/> />
</div> </div>
); );
@@ -435,14 +427,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
return ( return (
<CheckboxInput <CheckboxInput
name='row' name='row'
checked={ checked={row.getIsSelected()}
row.getIsSelected() && disabled={!row.getCanSelect()}
row.original.approval.step_number == 1
}
disabled={
!row.getCanSelect() ||
row.original.approval.step_number != 1
}
indeterminate={row.getIsSomeSelected()} indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()} onChange={row.getToggleSelectedHandler()}
/> />
@@ -473,6 +459,40 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
{ {
accessorKey: 'approval.step_name', accessorKey: 'approval.step_name',
header: 'Status', header: 'Status',
cell: (props) => {
const approval = props.row.original.approval;
return (
<Badge
variant='soft'
className={{
badge:
'rounded-lg px-2 w-full flex flex-row justify-start',
}}
color={
approval.step_number == 1
? 'neutral'
: approval.step_number == 2
? 'success'
: 'error'
}
>
<Icon
icon='mdi:circle'
width={12}
height={12}
color={
approval.step_number == 1
? 'neutral'
: approval.step_number == 2
? 'success'
: 'error'
}
/>
{approval.step_name}
</Badge>
);
},
}, },
{ {
header: 'Kandang', header: 'Kandang',
@@ -500,51 +520,51 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
accessorKey: 'created_at', accessorKey: 'created_at',
header: 'Dibuat pada', header: 'Dibuat pada',
cell: (props) => cell: (props) =>
new Date(props.row.original.created_at).toLocaleDateString(), formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
}, },
{ // {
header: 'Aksi', // header: 'Aksi',
cell: (props) => { // cell: (props) => {
const currentPageSize = // const currentPageSize =
props.table.getPaginationRowModel().rows.length; // props.table.getPaginationRowModel().rows.length;
const currentPageRows = // const currentPageRows =
props.table.getPaginationRowModel().flatRows; // props.table.getPaginationRowModel().flatRows;
const currentRowRelativeIndex = // const currentRowRelativeIndex =
currentPageRows.findIndex((r) => r.id === props.row.id) + 1; // currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
const isLast2Rows = // const isLast2Rows =
currentRowRelativeIndex > currentPageSize - 2; // currentRowRelativeIndex > currentPageSize - 2;
const deleteClickHandler = () => { // const deleteClickHandler = () => {
setSelectedProjectFlock(props.row.original); // setSelectedProjectFlock(props.row.original);
deleteModal.openModal(); // deleteModal.openModal();
}; // };
return ( // return (
<> // <>
{currentPageSize > 2 && ( // {currentPageSize > 2 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}> // <RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu // <RowOptionsMenu
type='dropdown' // type='dropdown'
props={props} // props={props}
deleteClickHandler={deleteClickHandler} // deleteClickHandler={deleteClickHandler}
/> // />
</RowDropdownOptions> // </RowDropdownOptions>
)} // )}
{currentPageSize <= 2 && ( // {currentPageSize <= 2 && (
<RowCollapseOptions> // <RowCollapseOptions>
<RowOptionsMenu // <RowOptionsMenu
type='collapse' // type='collapse'
props={props} // props={props}
deleteClickHandler={deleteClickHandler} // deleteClickHandler={deleteClickHandler}
/> // />
</RowCollapseOptions> // </RowCollapseOptions>
)} // )}
</> // </>
); // );
}, // },
}, // },
]} ]}
pageSize={tableFilterState.pageSize} pageSize={tableFilterState.pageSize}
page={ page={
@@ -597,7 +617,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
{ {
action: 'DELETE', action: 'DELETE',
icon: 'material-symbols:delete-outline-rounded', icon: 'material-symbols:delete-outline-rounded',
label: 'Hapus Massal', label: `Hapus ${selectedRowIds.length} data`,
onClick: () => { onClick: () => {
toast.error(`Konfirmasi hapus ${selectedRowIds.length} data.`); toast.error(`Konfirmasi hapus ${selectedRowIds.length} data.`);
}, },
@@ -611,7 +631,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
onClick: () => { onClick: () => {
setApprovalAction('APPROVED'); setApprovalAction('APPROVED');
confirmModal.openModal(); confirmModal.openModal();
setRowSelection({});
}, },
}, },
{ {
@@ -621,7 +640,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
onClick: () => { onClick: () => {
setApprovalAction('REJECTED'); setApprovalAction('REJECTED');
confirmModal.openModal(); confirmModal.openModal();
setRowSelection({});
}, },
}, },
]} ]}
@@ -1,6 +1,7 @@
import Badge from '@/components/Badge'; import Badge from '@/components/Badge';
import Button from '@/components/Button'; import Button from '@/components/Button';
import Card from '@/components/Card'; import Card from '@/components/Card';
import Tooltip from '@/components/Tooltip';
import { import {
formatCurrency, formatCurrency,
formatDate, formatDate,
@@ -10,6 +11,7 @@ import {
import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
const ProjectFlockDetail = ({ const ProjectFlockDetail = ({
@@ -17,6 +19,7 @@ const ProjectFlockDetail = ({
}: { }: {
projectFlock: ProjectFlock; projectFlock: ProjectFlock;
}) => { }) => {
const router = useRouter();
const [openBudgets, setOpenBudget] = useState(false); const [openBudgets, setOpenBudget] = useState(false);
return ( return (
@@ -32,18 +35,37 @@ const ProjectFlockDetail = ({
<Icon icon='mdi:close' width={24} height={24} /> <Icon icon='mdi:close' width={24} height={24} />
</Link> </Link>
<div className='divider divider-horizontal p-0 m-0'></div> <div className='divider divider-horizontal p-0 m-0'></div>
<div className='text-sm text-secondary'> <div className='text-sm text-neutral'>
Created On {formatDate(projectFlock.created_at, 'MMM DD, YYYY')} Created On {formatDate(projectFlock.created_at, 'MMM DD, YYYY')}
</div> </div>
</div> </div>
<div className='flex flex-row gap-3 justify-end'> <div className='flex flex-row gap-3 justify-end'>
{projectFlock?.approval?.step_number == 2 && (
<Link
href={`/production/project-flock/chickin/add?projectFlockId=${projectFlock?.id}`}
className='text-success'
>
<Tooltip content='Chick In' position='bottom'>
<Icon
icon='mdi:checkbox-marked-outline'
width={20}
height={20}
data-tip={'Chick In'}
/>
</Tooltip>
</Link>
)}
<Link <Link
href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`} href={`/production/project-flock/detail/edit?projectFlockId=${projectFlock.id}`}
> >
<Icon icon='mdi:square-edit-outline' width={20} height={20} /> <Tooltip content='Edit' position='bottom'>
<Icon icon='mdi:square-edit-outline' width={20} height={20} />
</Tooltip>
</Link> </Link>
<Button variant='link' className='p-0 text-error'> <Button variant='link' className='p-0 text-error'>
<Icon icon='mdi:trash-can-outline' width={20} height={20} /> <Tooltip content='Hapus' position='bottom'>
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
</Tooltip>
</Button> </Button>
</div> </div>
</div> </div>
@@ -58,7 +80,7 @@ const ProjectFlockDetail = ({
variant='soft' variant='soft'
color={ color={
projectFlock.approval.step_number == 1 projectFlock.approval.step_number == 1
? 'secondary' ? 'neutral'
: projectFlock.approval.step_number == 2 : projectFlock.approval.step_number == 2
? 'success' ? 'success'
: projectFlock.approval.step_number >= 3 : projectFlock.approval.step_number >= 3
@@ -75,7 +97,7 @@ const ProjectFlockDetail = ({
height={12} height={12}
color={ color={
projectFlock.approval.step_number == 1 projectFlock.approval.step_number == 1
? 'secondary' ? 'neutral'
: projectFlock.approval.step_number == 2 : projectFlock.approval.step_number == 2
? 'success' ? 'success'
: projectFlock.approval.step_number >= 3 : projectFlock.approval.step_number >= 3
@@ -87,7 +109,7 @@ const ProjectFlockDetail = ({
</Badge> </Badge>
<div className='divider divider-horizontal p-0 m-0'></div> <div className='divider divider-horizontal p-0 m-0'></div>
<Badge <Badge
color='secondary' color='neutral'
variant='soft' variant='soft'
className={{ badge: 'rounded-lg px-2' }} className={{ badge: 'rounded-lg px-2' }}
> >
@@ -103,7 +125,7 @@ const ProjectFlockDetail = ({
<div className='col-span-2'> <div className='col-span-2'>
<Badge <Badge
variant='soft' variant='soft'
color='secondary' color='neutral'
className={{ className={{
badge: 'rounded-lg px-2', badge: 'rounded-lg px-2',
}} }}
@@ -191,7 +213,7 @@ const ProjectFlockDetail = ({
</Badge> </Badge>
<div className='divider divider-horizontal p-0 m-0'></div> <div className='divider divider-horizontal p-0 m-0'></div>
<Badge <Badge
color='secondary' color='neutral'
variant='soft' variant='soft'
className={{ badge: 'rounded-lg px-2 cursor-pointer' }} className={{ badge: 'rounded-lg px-2 cursor-pointer' }}
onClick={() => { onClick={() => {
@@ -17,7 +17,7 @@ import {
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { FormikErrors, useFormik } from 'formik'; import { FormikErrors, useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { use, useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import useSWR, { KeyedMutator } from 'swr'; import useSWR, { KeyedMutator } from 'swr';
import { import {
ProjectFlockBudgetsSchemaType, ProjectFlockBudgetsSchemaType,
@@ -47,6 +47,7 @@ import Card from '@/components/Card';
import ProjectFlockKandangTable from '@/components/pages/production/project-flock/form/ProjectFlockKandangTable'; import ProjectFlockKandangTable from '@/components/pages/production/project-flock/form/ProjectFlockKandangTable';
import { Nonstock } from '@/types/api/master-data/nonstock'; import { Nonstock } from '@/types/api/master-data/nonstock';
import { useUiStore } from '@/stores/ui/ui.store'; import { useUiStore } from '@/stores/ui/ui.store';
import Link from 'next/link';
interface ProjectFlockFormProps { interface ProjectFlockFormProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
@@ -110,19 +111,6 @@ const ProjectFlockForm = ({
) )
); );
useEffect(() => {
if (initialValues?.approval?.step_name) {
const pengajuanRejected =
initialValues.approval.step_number == 1 &&
initialValues.approval.action == 'REJECTED';
const approvedDisabled =
initialValues.approval.step_number !== 1 || pengajuanRejected;
setIsApprovedDisabled(approvedDisabled);
setIsRejectedDisabled(!approvedDisabled || pengajuanRejected);
setApprovalAction(!approvedDisabled ? 'APPROVED' : 'REJECTED');
}
}, [initialValues]);
// Fetch Data // Fetch Data
const { isLoadingOptions: isLoadingFlocks, options: optionsFlock } = const { isLoadingOptions: isLoadingFlocks, options: optionsFlock } =
useSelect(FlockApi.basePath, 'id', 'name'); useSelect(FlockApi.basePath, 'id', 'name');
@@ -221,7 +209,12 @@ const ProjectFlockForm = ({
formik.setFieldValue('area_id', (val as OptionType)?.value); formik.setFieldValue('area_id', (val as OptionType)?.value);
formik.setFieldValue('area', val); formik.setFieldValue('area', val);
formik.setFieldTouched('area_id', true); if (Boolean(val)) {
formik.setFieldTouched('area_id', false);
formik.setFieldError('area_id', '');
} else {
formik.setFieldTouched('area_id', true);
}
setSelectedArea((val as OptionType)?.value as string); setSelectedArea((val as OptionType)?.value as string);
setSelectedLocation(''); setSelectedLocation('');
@@ -254,7 +247,12 @@ const ProjectFlockForm = ({
val ? (val as OptionType)?.value : 0 val ? (val as OptionType)?.value : 0
); );
formik.setFieldTouched(`${inputName}_id`, true); if (Boolean(val)) {
formik.setFieldTouched(`${inputName}_id`, false);
formik.setFieldError(`${inputName}_id`, '');
} else {
formik.setFieldTouched(`${inputName}_id`, true);
}
}; };
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => { const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -282,7 +280,7 @@ const ProjectFlockForm = ({
const updateProjectFlockHandler = async ( const updateProjectFlockHandler = async (
payload: CreateProjectFlockPayload payload: CreateProjectFlockPayload
) => { ) => {
const updateProjectFlockRes = await ProjectFlockApi.update( const updateProjectFlockRes = await ProjectFlockApi.resubmit(
initialValues?.id as number, initialValues?.id as number,
payload payload
); );
@@ -314,21 +312,14 @@ const ProjectFlockForm = ({
0, 0,
initialValues?.flock_name?.lastIndexOf(' ') initialValues?.flock_name?.lastIndexOf(' ')
) ?? ''; ) ?? '';
const optionFind = optionsFlock.find((flock) => {
return flock.label == trimFlock;
}) as OptionType;
return { return {
flock: initialValues?.flock_name flock:
? { optionsFlock.find((flock) => {
value: return flock.label == trimFlock;
optionsFlock.find((flock) => { }) ?? null,
return flock.label == trimFlock;
})?.value ?? 0,
label:
formType != 'detail'
? (optionsFlock.find((flock) => {
return flock.label == trimFlock;
})?.label ?? '')
: initialValues?.flock_name,
}
: null,
area: initialValues?.area area: initialValues?.area
? { ? {
value: initialValues.area?.id, value: initialValues.area?.id,
@@ -355,14 +346,8 @@ const ProjectFlockForm = ({
: null, : null,
flock_name: flock_name:
optionsFlock.find((flock) => { optionsFlock.find((flock) => {
return ( return flock.label == trimFlock;
flock.label == })?.label ?? trimFlock,
initialValues?.flock_name?.slice(
0,
initialValues?.flock_name?.lastIndexOf(' ')
)
);
})?.label ?? '',
area_id: initialValues?.area?.id ?? 0, area_id: initialValues?.area?.id ?? 0,
category: initialValues?.category as NonNullable< category: initialValues?.category as NonNullable<
'GROWING' | 'LAYING' | undefined 'GROWING' | 'LAYING' | undefined
@@ -372,7 +357,18 @@ const ProjectFlockForm = ({
kandang_ids: initialValues?.kandangs?.map( kandang_ids: initialValues?.kandangs?.map(
(k: Kandang) => k.id (k: Kandang) => k.id
) as number[], ) as number[],
project_budgets: [ project_budgets: initialValues?.project_budgets?.map((budget) => {
return {
nonstock: {
value: budget.nonstock?.id ?? '',
label: budget.nonstock?.name ?? '',
},
nonstock_id: budget.nonstock?.id ?? '',
qty: budget.qty,
price: budget.price,
total_price: budget.qty * budget.price,
};
}) ?? [
{ {
nonstock: null, nonstock: null,
nonstock_id: '', nonstock_id: '',
@@ -387,94 +383,13 @@ const ProjectFlockForm = ({
// Formik // Formik
const formik = useFormik<ProjectFlockFormValues>({ const formik = useFormik<ProjectFlockFormValues>({
initialValues: { initialValues: {
flock: initialValues?.flock_name ...formikInitialValues,
? {
value:
optionsFlock.find((flock) => {
return (
flock.label ==
initialValues?.flock_name?.slice(
0,
initialValues?.flock_name?.lastIndexOf(' ')
)
);
})?.value ?? 0,
label:
formType != 'detail'
? (optionsFlock.find((flock) => {
return (
flock.label ==
initialValues?.flock_name?.slice(
0,
initialValues?.flock_name?.lastIndexOf(' ')
)
);
})?.label ?? '')
: initialValues?.flock_name,
}
: null,
area: initialValues?.area
? {
value: initialValues.area?.id,
label: initialValues.area.name,
}
: null,
category_option: initialValues?.category
? {
value: initialValues.category,
label: initialValues.category,
}
: null,
fcr: initialValues?.fcr
? {
value: initialValues.fcr?.id,
label: initialValues.fcr.name,
}
: null,
location: initialValues?.location
? {
value: initialValues.location?.id,
label: initialValues.location.name,
}
: null,
flock_name:
formType != 'detail'
? optionsFlock.find((flock) => {
return (
flock.label ==
initialValues?.flock_name?.slice(
0,
initialValues?.flock_name?.lastIndexOf(' ')
)
);
})?.label
: (initialValues?.flock_name ?? ''),
area_id: initialValues?.area?.id ?? 0,
category: initialValues?.category as NonNullable<
'GROWING' | 'LAYING' | undefined
>,
fcr_id: initialValues?.fcr?.id ?? 0,
location_id: initialValues?.location?.id ?? 0,
kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) as (
| number
| undefined
)[],
project_budgets: [
{
nonstock: null,
nonstock_id: '',
qty: '',
price: '',
total_price: '',
},
],
} as ProjectFlockFormValues, } as ProjectFlockFormValues,
enableReinitialize: true,
validationSchema: validationSchema:
formType == 'add' ? ProjectFlockFormSchema : UpdateProjectFlockFormSchema, formType == 'add' ? ProjectFlockFormSchema : UpdateProjectFlockFormSchema,
validateOnBlur: true, validateOnBlur: true,
validateOnChange: true, // validateOnChange: true,
validateOnMount: true, // validateOnMount: true,
onSubmit: async (values) => { onSubmit: async (values) => {
setProjectFlockFormErrorMessage(''); setProjectFlockFormErrorMessage('');
const payload: CreateProjectFlockPayload = { const payload: CreateProjectFlockPayload = {
@@ -522,7 +437,18 @@ const ProjectFlockForm = ({
}, [initialValues, setSelectedArea, formType]); }, [initialValues, setSelectedArea, formType]);
useEffect(() => { useEffect(() => {
formikSetValues(formikInitialValues); const trimFlock =
initialValues?.flock_name?.slice(
0,
initialValues?.flock_name?.lastIndexOf(' ')
) ?? '';
formikSetValues({
...formikInitialValues,
flock: optionsFlock.find((flock) => {
return flock.label == trimFlock;
}) as OptionType,
flock_name: trimFlock ?? '',
});
}, [formikSetValues]); }, [formikSetValues]);
// Aktifkan lokasi jika formType = 'detail' // Aktifkan lokasi jika formType = 'detail'
@@ -542,10 +468,6 @@ const ProjectFlockForm = ({
} }
}, [formType, initialValues]); }, [formType, initialValues]);
useEffect(() => {
formik.validateForm();
}, [formik.values]);
useEffect(() => { useEffect(() => {
const selectedRowIds = Object.keys(rowSelection) const selectedRowIds = Object.keys(rowSelection)
.filter((id) => rowSelection[id]) .filter((id) => rowSelection[id])
@@ -583,6 +505,19 @@ const ProjectFlockForm = ({
return unsub; return unsub;
}, []); }, []);
useEffect(() => {
if (initialValues?.approval?.step_name) {
const pengajuanRejected =
initialValues.approval.step_number == 1 &&
initialValues.approval.action == 'REJECTED';
const approvedDisabled =
initialValues.approval.step_number !== 1 || pengajuanRejected;
setIsApprovedDisabled(approvedDisabled);
setIsRejectedDisabled(!approvedDisabled || pengajuanRejected);
setApprovalAction(!approvedDisabled ? 'APPROVED' : 'REJECTED');
}
}, [initialValues]);
// Actions handler // Actions handler
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
@@ -727,31 +662,59 @@ const ProjectFlockForm = ({
: undefined; : undefined;
const inputPeriod = const inputPeriod =
(initialValues?.period ?? selectedPeriod == 0) ? 1 : selectedPeriod; (initialValues?.period ?? selectedPeriod == 0) ? 1 : selectedPeriod;
const filteredNonStockOptions = optionsNonstock.filter((nonstock) => { const filteredNonStockOptions = optionsNonstock.filter((nonstock) => {
return !(formik.values.project_budgets ?? []).some( const isNonstockAlreadyInBudgets = (
(budget) => budget.nonstock_id === nonstock.value formik.values.project_budgets ?? []
); ).some((budget) => budget.nonstock_id === nonstock.value);
return !isNonstockAlreadyInBudgets;
}); });
return ( return (
<> <>
<section className='w-full'> <section className='w-full'>
<header className='flex flex-col gap-4 mb-6'> {/* Header */}
<Button <div className='flex flex-row justify-between items-center px-4 pt-4'>
href='/production/project-flock' <div className='flex flex-row h-full gap-2'>
variant='link' <Link
className='w-fit p-0 text-primary' href={
> formType == 'add'
<Icon icon='uil:arrow-left' width={24} height={24} /> ? '/production/project-flock'
Kembali : `/production/project-flock/detail?projectFlockId=${initialValues?.id}`
</Button> }
className='hover:text-gray-400'
<h1 className='text-2xl font-bold text-center'> >
{formType === 'add' && 'Tambah Project Flock'} <Icon
{formType === 'edit' && 'Edit Project Flock'} icon={formType == 'add' ? 'mdi:close' : 'mdi:arrow-left'}
{formType === 'detail' && 'Detail Project Flock'} width={24}
</h1> height={24}
</header> />
</Link>
<div className='divider divider-horizontal p-0 m-0'></div>
<div className='text-sm text-neutral'>
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
</div>
</div>
<div className='flex flex-row justify-end'>
<Button
onClick={() => {
if (initialValues?.id) {
deleteModal.openModal();
}
}}
variant='link'
className='p-0 text-error'
>
<Icon
icon='material-symbols:delete-outline-rounded'
width={20}
height={20}
className='justify-start text-sm'
/>
</Button>
</div>
</div>
{projectFlockFormErrorMessage && ( {projectFlockFormErrorMessage && (
<div className='my-4'> <div className='my-4'>
<div role='alert' className='alert alert-error'> <div role='alert' className='alert alert-error'>
@@ -829,15 +792,11 @@ const ProjectFlockForm = ({
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
onReset={formik.handleReset} onReset={formik.handleReset}
> >
{/* Card Informasi Umum */} {/* Form Informasi Umum */}
<Card <div className='divider mt-3'></div>
title='Informasi Umum' <div className='flex flex-col gap-4 px-4'>
variant='bordered' <h2 className='text-2xl font-semibold'>Informasi Umum</h2>
className={{ <div className='flex flex-col gap-4'>
wrapper: 'w-full mb-4',
}}
>
<div className='grid sm:grid-cols-2 gap-4'>
<SelectInput <SelectInput
required required
label='Area' label='Area'
@@ -852,6 +811,25 @@ const ProjectFlockForm = ({
isClearable isClearable
isDisabled={formType === 'detail'} isDisabled={formType === 'detail'}
/> />
<SelectInput
required
label='Lokasi'
value={formik.values.location as OptionType}
onChange={locationChangeHandler}
options={
selectedArea != '' || initialValues?.area?.id
? optionsLocation
: []
}
isLoading={isLoadingLocations}
isError={
formik.touched.location_id &&
Boolean(formik.errors.location_id)
}
errorMessage={formik.errors.location_id as string}
isClearable
isDisabled={formType === 'detail' || disabledLocation}
/>
<SelectInput <SelectInput
required required
label='Flock' label='Flock'
@@ -882,25 +860,6 @@ const ProjectFlockForm = ({
isClearable isClearable
isDisabled={formType === 'detail'} isDisabled={formType === 'detail'}
/> />
<SelectInput
required
label='Lokasi'
value={formik.values.location as OptionType}
onChange={locationChangeHandler}
options={
selectedArea != '' || initialValues?.area?.id
? optionsLocation
: []
}
isLoading={isLoadingLocations}
isError={
formik.touched.location_id &&
Boolean(formik.errors.location_id)
}
errorMessage={formik.errors.location_id as string}
isClearable
isDisabled={formType === 'detail' || disabledLocation}
/>
<SelectInput <SelectInput
required required
label='FCR' label='FCR'
@@ -937,17 +896,12 @@ const ProjectFlockForm = ({
value={selectedLocation ? inputPeriod : ''} value={selectedLocation ? inputPeriod : ''}
/> />
</div> </div>
</Card> </div>
{/* Card Pilih Kandang */} {/* Form Pilih Kandang */}
<Card <div className='divider'></div>
collapsible <div className='flex flex-col gap-4 px-4 pb-4'>
title='Pilih Kandang' <h2 className='text-2xl font-semibold'>Pilih Kandang</h2>
variant='bordered'
className={{
wrapper: 'w-full mb-4',
}}
>
<div className='overflow-x-auto duration-300 ease-in-out'> <div className='overflow-x-auto duration-300 ease-in-out'>
{isLoadingKandang && ( {isLoadingKandang && (
<span className='loading loading-dots loading-xl'></span> <span className='loading loading-dots loading-xl'></span>
@@ -964,33 +918,43 @@ const ProjectFlockForm = ({
initialValues={initialValues} initialValues={initialValues}
/> />
</div> </div>
</Card> </div>
{/* Card Estimasi Budget */} {/* Card Estimasi Budget */}
<Card <div className='divider'></div>
collapsible <div className='flex flex-col gap-4 px-4 pb-4'>
title='Estimasi Aggaran per Kandang' <h2 className='text-2xl font-semibold'>
variant='bordered' Estimasi Aggaran Per Flock
className={{ </h2>
wrapper: 'w-full', <div className='flex flex-col gap-4'>
}} {formik.values.project_budgets &&
> formik.values.project_budgets.length > 0 ? (
<table className='w-full mt-4'> formik.values.project_budgets.map((budget, index) => (
<thead> <Card
<tr className='border-b border-gray-300'> key={index}
<th className='text-start px-2 py-3'>Produk</th> variant='bordered'
<th className='text-start px-2 py-3'>Kuantitas</th> className={{
<th className='text-start px-2 py-3'>Harga Satuan</th> wrapper: 'w-full',
<th className='text-start px-2 py-3'>Harga Total</th> body: 'p-3',
<th className='text-start px-2 py-3'>Aksi</th> }}
</tr> >
</thead> <div className='flex flex-col gap-2'>
<tbody> <div className='flex flex-row justify-between items-center mb-2'>
{formik.values.project_budgets && <div className='text-lg'>Anggaran ke-{index + 1}</div>
formik.values.project_budgets.length > 0 ? ( <Button
formik.values.project_budgets.map((budget, index) => ( type='button'
<tr key={index} className='align-top'> color='error'
<td className='px-2 py-3'> onClick={() =>
onDeleteBudgetRowHandler(
budget.nonstock_id as number,
index
)
}
>
<Icon icon='mdi:trash' width={16} height={16} />
</Button>
</div>
<div className='flex flex-row justify-between items-center'>
<SelectInput <SelectInput
isClearable isClearable
options={filteredNonStockOptions ?? []} options={filteredNonStockOptions ?? []}
@@ -1034,8 +998,8 @@ const ProjectFlockForm = ({
) )
} }
/> />
</td> </div>
<td className='px-2 py-3'> <div className='flex flex-row justify-between items-center'>
<NumberInput <NumberInput
name={`project_budgets[${index}].qty`} name={`project_budgets[${index}].qty`}
placeholder='Masukkan jumlah' placeholder='Masukkan jumlah'
@@ -1046,11 +1010,13 @@ const ProjectFlockForm = ({
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
allowNegative={false} allowNegative={false}
endAdornment={ endAdornment={
isResponseSuccess(nonstocks) <div className='text-gray-500'>
? (nonstocks.data.find( {isResponseSuccess(nonstocks)
(ns) => ns.id === budget.nonstock_id ? (nonstocks.data.find(
)?.uom?.name ?? '') (ns) => ns.id === budget.nonstock_id
: '' )?.uom?.name ?? '')
: ''}
</div>
} }
errorMessage={ errorMessage={
( (
@@ -1070,8 +1036,8 @@ const ProjectFlockForm = ({
) )
} }
/> />
</td> </div>
<td className='px-2 py-3'> <div className='flex flex-row justify-between items-center'>
<NumberInput <NumberInput
name={`project_budgets[${index}].price`} name={`project_budgets[${index}].price`}
value={formik.values.project_budgets[index].price} value={formik.values.project_budgets[index].price}
@@ -1082,6 +1048,17 @@ const ProjectFlockForm = ({
placeholder='Masukkan harga satuan' placeholder='Masukkan harga satuan'
allowNegative={false} allowNegative={false}
startAdornment='Rp' startAdornment='Rp'
endAdornment={
<div className='text-gray-500'>
{`Per ${
isResponseSuccess(nonstocks)
? (nonstocks.data.find(
(ns) => ns.id === budget.nonstock_id
)?.uom?.name ?? 'Item')
: 'Item'
}`}
</div>
}
errorMessage={ errorMessage={
( (
formik.errors.project_budgets?.[ formik.errors.project_budgets?.[
@@ -1100,8 +1077,8 @@ const ProjectFlockForm = ({
) )
} }
/> />
</td> </div>
<td className='px-2 py-3'> <div className='flex flex-row justify-between items-center'>
<NumberInput <NumberInput
name={`project_budgets[${index}].total_price`} name={`project_budgets[${index}].total_price`}
value={ value={
@@ -1115,9 +1092,12 @@ const ProjectFlockForm = ({
) )
} }
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
placeholder='Masukkan harga satuan' placeholder='Masukkan harga total'
allowNegative={false} allowNegative={false}
startAdornment='Rp' startAdornment='Rp'
endAdornment={
<div className='text-gray-500'>Total</div>
}
errorMessage={ errorMessage={
( (
formik.errors.project_budgets?.[ formik.errors.project_budgets?.[
@@ -1137,108 +1117,50 @@ const ProjectFlockForm = ({
) )
} }
/> />
</td> </div>
<td className='px-2 py-4'> </div>
<Button </Card>
type='button' ))
color='error' ) : (
onClick={() => <div className='text-center py-4 text-gray-400'>
onDeleteBudgetRowHandler( Tidak ada data estimasi anggaran.
budget.nonstock_id as number, </div>
index )}
) <Button
} type='button'
> onClick={onAddBudgetRowHandler}
<Icon icon='mdi:trash' width={16} height={16} /> disabled={filteredNonStockOptions.length == 0}
</Button> color='success'
</td> className='w-fit self-center'
</tr> >
)) <Icon icon='mdi:plus' width={16} height={16} /> Add Budget
) : ( </Button>
<tr> </div>
<td colSpan={4} className='text-center py-4 text-gray-500'> </div>
Tidak ada data estimasi anggaran.
</td>
</tr>
)}
</tbody>
</table>
<Button <div className='flex flex-row justify-center gap-2 flex-wrap my-6 px-4'>
type='button' {/* <div className='w-120'>
onClick={onAddBudgetRowHandler} <div className='text-primary text-sm'>
disabled={filteredNonStockOptions.length == 0} {JSON.stringify(formik.values)}
>
<Icon icon='mdi:plus' width={16} height={16} />
Tambah Item
</Button>
</Card>
<div className='flex flex-row justify-center gap-2 flex-wrap my-6'>
{formType !== 'detail' && (
<div className='flex flex-row justify-end gap-2'>
<Button
type='button'
color='warning'
className='px-4'
onClick={formik.handleReset}
>
Reset
</Button>
<Button
type='submit'
color='primary'
isLoading={formik.isSubmitting}
disabled={
!formik.isValid || formik.isSubmitting
// TODO: Add logic && ketika nilai kandang_ids sudah beda dari initial values
}
className='px-4'
>
Submit
</Button>
</div> </div>
<div className='text-error text-sm'>
{JSON.stringify(formik.errors)}
</div>
</div> */}
{formType !== 'detail' && (
<Button
type='submit'
color='primary'
isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting}
className='px-4 w-full'
>
<Icon icon='mdi:plus' width={24} height={24} />
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
</Button>
)} )}
</div> </div>
</form> </form>
{formType != 'add' && (
<div className='flex flex-row gap-2 mb-6'>
{formType != 'edit' && (
<Button
onClick={() => {
router.push(
`/production/project-flock/detail/edit?projectFlockId=${initialValues?.id}`
);
}}
color='warning'
>
<Icon
icon='mdi:pencil-outline'
width={16}
height={16}
className='justify-start text-sm'
/>
Edit
</Button>
)}
<Button
onClick={() => {
if (initialValues?.id) {
deleteModal.openModal();
}
}}
color='error'
>
<Icon
icon='material-symbols:delete-outline-rounded'
width={16}
height={16}
className='justify-start text-sm'
/>
Delete
</Button>
</div>
)}
</section> </section>
<ConfirmationModal <ConfirmationModal
@@ -1,5 +1,7 @@
'use client'; 'use client';
import Badge from '@/components/Badge';
import Card from '@/components/Card';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import PillBadge from '@/components/PillBadge'; import PillBadge from '@/components/PillBadge';
import Table from '@/components/Table'; import Table from '@/components/Table';
@@ -9,6 +11,7 @@ import {
ProjectFlock, ProjectFlock,
ProjectFlockPeriods, ProjectFlockPeriods,
} from '@/types/api/production/project-flock'; } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react';
import { OnChangeFn, Row } from '@tanstack/react-table'; import { OnChangeFn, Row } from '@tanstack/react-table';
import { useMemo } from 'react'; import { useMemo } from 'react';
@@ -29,161 +32,119 @@ const ProjectFlockKandangTable = ({
initialValues?: ProjectFlock; initialValues?: ProjectFlock;
formType: 'add' | 'edit' | 'detail'; formType: 'add' | 'edit' | 'detail';
}) => { }) => {
const initialKandangIdSet = useMemo(() => { // Fungsi untuk menangani perubahan checkbox
return initialValues?.kandangs.map((k) => k.id) ?? []; const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => {
}, [initialValues]); // Hanya izinkan perubahan jika tidak dalam mode 'detail'
const isRowEnabled = (row: Row<Kandang>) => { if (formType === 'detail') return;
const isDisabled =
!initialKandangIdSet.includes(row.original.id) && // Pastikan kandang.id ada dan tidak null/undefined
(row.original.status == 'ACTIVE' || if (kandang.id === undefined) return;
row.original.status == 'PENGAJUAN' ||
formType == 'detail'); const kandangIdString = kandang.id.toString();
return !isDisabled;
setRowSelection((prev) => {
const newSelection = { ...prev };
if (isChecked) {
newSelection[kandangIdString] = true;
} else {
delete newSelection[kandangIdString];
}
return newSelection;
});
}; };
return ( return (
<> <>
<Table<Kandang> {listKandang.length > 0 ? (
data={listKandang} <>
columns={[ {/* ... Bagian Badge Status ... */}
{ <div className='flex flex-row mb-4'>
id: 'select', <Badge
header: ({ table }) => { variant='soft'
const allRows = table.getRowModel().rows; color='primary'
// 1. Filter semua baris dengan logika yang sama persis seperti di cell className={{
const selectableRows = allRows.filter(isRowEnabled); badge: 'rounded-lg px-2',
}}
>
<Icon icon='mdi:circle' width={12} height={12} />
Tersedia (
{
listKandang.filter((kandang) => kandang.status == 'NON_ACTIVE')
.length
}
)
</Badge>
<div className='divider divider-horizontal mx-1'></div>
<Badge
variant='soft'
color='neutral'
className={{
badge: 'rounded-lg px-2',
}}
>
<Icon icon='mdi:circle' width={12} height={12} />
Tidak Tersedia (
{
listKandang.filter((kandang) => kandang.status != 'NON_ACTIVE')
.length
}
)
</Badge>
</div>
{/* --- */}
<Card
variant='bordered'
className={{
wrapper: 'w-full rounded-lg',
body: 'p-4',
}}
>
<div className='flex flex-col gap-4 w-full'>
{listKandang.map((kandang, index) => {
const kandangIdString =
kandang.id?.toString() ?? `temp-${index}`;
// 2. Cek apakah SEMUA baris yang BISA DIPILIH sudah terpilih const isSelected =
const allSelected = !!rowSelection[kandangIdString] ||
selectableRows.length > 0 && (kandang.id !== undefined &&
selectableRows.every((row) => row.getIsSelected()); selectedIds.includes(kandang.id));
// 3. Cek apakah BEBERAPA baris yang BISA DIPILIH sudah terpilih const isDisabled =
const someSelected = formType == 'detail' || kandang.status != 'NON_ACTIVE';
selectableRows.some((row) => row.getIsSelected()) &&
!allSelected;
// 4. Fungsi toggle HANYA akan mentoggle baris yang BISA DIPILIH return (
const toggleSelectableRows = () => { <div key={index} className='flex flex-row justify-between'>
const shouldSelect = !allSelected; <CheckboxInput
selectableRows.forEach((row) => name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox
row.toggleSelected(shouldSelect) label={kandang.name}
checked={isSelected}
disabled={isDisabled}
onChange={(e) =>
handleCheckboxChange(kandang, e.currentTarget.checked)
}
/>
<Badge
variant='soft'
color={
kandang.status == 'NON_ACTIVE' ? 'primary' : 'neutral'
}
className={{
badge: 'rounded-lg px-2',
}}
>
<Icon icon='mdi:circle' width={12} height={12} />
{kandang.status != 'NON_ACTIVE' && 'Tidak'} Tersedia
</Badge>
</div>
); );
}; })}
</div>
return ( </Card>
<div className='w-full flex flex-row justify-center'> </>
<CheckboxInput ) : (
name='allRow' <div className='text-center py-4 text-gray-400'>
checked={allSelected} Pilih lokasi terlebih dahulu
indeterminate={someSelected} </div>
onChange={toggleSelectableRows} )}
disabled={
selectableRows.length === 0 || formType == 'detail'
}
/>
</div>
);
},
cell: ({ row }) => {
return (
<CheckboxInput
name='row'
checked={
(row.getIsSelected() &&
(row.original.status == 'NON_ACTIVE' ||
row.original.status == 'PENGAJUAN')) ||
(selectedIds && selectedIds.includes(row.original.id))
}
disabled={
formType == 'detail' ||
(!initialKandangIdSet.includes(row.original.id) &&
(row.original.status == 'ACTIVE' ||
row.original.status == 'PENGAJUAN'))
}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
);
},
},
{
accessorFn: (row) => row.name,
header: 'Kandang',
},
{
accessorFn: (row) => row.status,
header: 'Status',
cell: (props) => {
return (
<PillBadge
color={(() => {
switch (props.row.original.status) {
case 'ACTIVE':
return 'red';
case 'PENGAJUAN':
return 'green';
case 'NON_ACTIVE':
return 'blue';
default:
return 'gray';
}
})()}
content={props.row.original.status
.toLowerCase()
.replace(/_/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase())}
/>
);
},
},
{
accessorFn: (row) => row.capacity,
header: 'Kapasitas',
},
{
accessorFn: (row) => row.location?.name,
header: 'Periode',
cell: (props) => {
const period =
listPeriods.length > 0
? listPeriods.find((p) => p.id == props.row.original.id)
: undefined;
const calcPeriod = period?.period == 0 ? 1 : period?.period;
const selected = props.row.getIsSelected();
const initPeriod = initialValues?.period;
return formType == 'detail'
? selected
? initPeriod
: '-'
: formType == 'add'
? (calcPeriod ?? '-')
: selected
? (initPeriod ?? '-')
: (calcPeriod ?? '-');
},
},
{
accessorFn: (row) => row.pic?.name,
header: 'Penanggung Jawab',
},
]}
className={{
containerClassName: cn({
'mb-20': listKandang?.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',
}}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
/>
</> </>
); );
}; };
@@ -141,6 +141,38 @@ export class ProjectFlockService extends BaseApiService<
} }
} }
/**
* Resubmit Project Flock
*/
async resubmit(
id: number,
payload: UpdateProjectFlockPayload
): Promise<BaseApiResponse<ProjectFlock> | undefined> {
try {
const updatePath = `${this.basePath}/${id}/resubmit`;
const headers = {
'Content-Type': 'application/json',
...(this.header ?? {}),
};
const updateRes = await httpClient<BaseApiResponse<ProjectFlock>>(
updatePath,
{
method: 'PUT',
body: payload,
headers,
}
);
return updateRes;
} catch (error: unknown) {
if (axios.isAxiosError<BaseApiResponse<ProjectFlock>>(error)) {
return error.response?.data;
}
return undefined;
}
}
/** /**
* Approve single Project Flock * Approve single Project Flock
*/ */