mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
feat(FE-314-315): API Integration project budgets and refactoring UI
This commit is contained in:
+33
-20
@@ -7,26 +7,39 @@
|
||||
default: false;
|
||||
prefersdark: false;
|
||||
color-scheme: 'light';
|
||||
--color-base-100: oklch(98% 0.001 106.423);
|
||||
--color-base-200: oklch(97% 0.001 106.424);
|
||||
--color-base-300: oklch(92% 0.003 48.717);
|
||||
--color-base-content: oklch(22.389% 0.031 278.072);
|
||||
--color-primary: oklch(60% 0.126 221.723);
|
||||
--color-primary-content: oklch(100% 0 0);
|
||||
--color-secondary: oklch(52% 0.105 223.128);
|
||||
--color-secondary-content: oklch(100% 0 0);
|
||||
--color-accent: oklch(45% 0.085 224.283);
|
||||
--color-accent-content: oklch(100% 0 0);
|
||||
--color-neutral: oklch(39% 0.07 227.392);
|
||||
--color-neutral-content: oklch(100% 0 0);
|
||||
--color-info: oklch(58% 0.158 241.966);
|
||||
--color-info-content: oklch(100% 0 0);
|
||||
--color-success: oklch(62% 0.194 149.214);
|
||||
--color-success-content: oklch(100% 0 0);
|
||||
--color-warning: oklch(85% 0.199 91.936);
|
||||
--color-warning-content: oklch(0% 0 0);
|
||||
--color-error: oklch(57% 0.245 27.325);
|
||||
--color-error-content: oklch(100% 0 0);
|
||||
|
||||
/* Primary Colors */
|
||||
--color-primary: oklch(39.4% 0.177 301.9);
|
||||
--color-primary-content: oklch(87.5% 0.038 274.5);
|
||||
|
||||
/* Secondary Colors */
|
||||
--color-secondary: oklch(60.1% 0.258 335.7);
|
||||
--color-secondary-content: oklch(99.4% 0.007 337.8);
|
||||
|
||||
/* Accent Colors */
|
||||
--color-accent: oklch(76.2% 0.155 170.8);
|
||||
--color-accent-content: oklch(7.2% 0.007 167.6);
|
||||
|
||||
/* Neutral Colors */
|
||||
--color-neutral: oklch(22.4% 0.032 258.8);
|
||||
--color-neutral-content: oklch(87.7% 0.016 257.0);
|
||||
|
||||
/* Base Colors */
|
||||
--color-base-100: oklch(100% 0 0); /* #ffffff */
|
||||
--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-field: 0.25rem;
|
||||
--radius-box: 0.25rem;
|
||||
|
||||
@@ -12,7 +12,7 @@ const AddProjectFlock = () => {
|
||||
// },
|
||||
// }));
|
||||
return (
|
||||
<section className='w-full p-4 flex flex-row justify-center'>
|
||||
<section className='w-full flex flex-row justify-center'>
|
||||
<ProjectFlockForm formType='add' />
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ const ProjectFlockEdit = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full p-4 flex flex-col justify-center'>
|
||||
<div className='w-full flex flex-col justify-center'>
|
||||
{isLoadingProjectFlock && (
|
||||
<span className='loading loading-spinner loading-xl' />
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import { cn } from '@/lib/helper';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
@@ -50,7 +51,7 @@ const FloatingActionsButton = ({
|
||||
<div
|
||||
className={cn(
|
||||
`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'
|
||||
)}
|
||||
>
|
||||
@@ -72,14 +73,15 @@ const FloatingActionsButton = ({
|
||||
key={index}
|
||||
onClick={action.onClick}
|
||||
className='text-white hover:text-gray-400 tooltip tooltip-bottom'
|
||||
data-tip={action.label}
|
||||
>
|
||||
<Icon
|
||||
icon={action.icon}
|
||||
width={24}
|
||||
height={24}
|
||||
className={`text-${getActionColor(action.action)} font-thin`}
|
||||
/>
|
||||
<Tooltip content={action.label || action.action}>
|
||||
<Icon
|
||||
icon={action.icon}
|
||||
width={20}
|
||||
height={20}
|
||||
className={`text-${getActionColor(action.action)} font-thin`}
|
||||
/>
|
||||
</Tooltip>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -91,7 +93,9 @@ const FloatingActionsButton = ({
|
||||
onClick={onClose}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,7 @@ const CheckboxInput = ({
|
||||
id={name}
|
||||
name={name}
|
||||
className={cn(
|
||||
'checkbox cursor-pointer',
|
||||
'checkbox rounded-md cursor-pointer',
|
||||
{
|
||||
'border-error': isError,
|
||||
'border-success': isValid,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import Badge from '@/components/Badge';
|
||||
import Button from '@/components/Button';
|
||||
import FloatingActionsButton from '@/components/FloatingActionsButton';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
@@ -13,7 +14,7 @@ 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 } from '@/lib/helper';
|
||||
import { cn, formatDate } from '@/lib/helper';
|
||||
import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data';
|
||||
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
||||
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='flex flex-col sm:flex-row gap-3 w-full'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
href='/production/project-flock/add'
|
||||
@@ -278,7 +278,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah
|
||||
</Button>
|
||||
<Button
|
||||
{/* <Button
|
||||
variant='outline'
|
||||
color='success'
|
||||
onClick={() => {
|
||||
@@ -303,7 +303,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
>
|
||||
<Icon icon='mdi:times' width={24} height={24} />
|
||||
Reject
|
||||
</Button>
|
||||
</Button> */}
|
||||
<div className='ms-auto w-full sm:w-auto'>
|
||||
<DebouncedTextInput
|
||||
name='search'
|
||||
@@ -395,9 +395,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
id: 'select',
|
||||
header: ({ table }) => {
|
||||
const allRows = table.getRowModel().rows;
|
||||
const selectableRows = allRows.filter(
|
||||
(row) => row.original?.approval?.step_number == 1
|
||||
);
|
||||
const selectableRows = allRows;
|
||||
|
||||
const allSelected =
|
||||
selectableRows.every((row) => row.getIsSelected()) &&
|
||||
@@ -421,12 +419,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
checked={allSelected}
|
||||
indeterminate={someSelected}
|
||||
onChange={toggleSelectableRows}
|
||||
disabled={
|
||||
isResponseSuccess(projectFlocks) &&
|
||||
projectFlocks?.data?.filter(
|
||||
(flock) => flock.approval.step_number == 1
|
||||
).length == 0
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -435,14 +427,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
return (
|
||||
<CheckboxInput
|
||||
name='row'
|
||||
checked={
|
||||
row.getIsSelected() &&
|
||||
row.original.approval.step_number == 1
|
||||
}
|
||||
disabled={
|
||||
!row.getCanSelect() ||
|
||||
row.original.approval.step_number != 1
|
||||
}
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
indeterminate={row.getIsSomeSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
/>
|
||||
@@ -473,6 +459,40 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
{
|
||||
accessorKey: 'approval.step_name',
|
||||
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',
|
||||
@@ -500,51 +520,51 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
accessorKey: 'created_at',
|
||||
header: 'Dibuat pada',
|
||||
cell: (props) =>
|
||||
new Date(props.row.original.created_at).toLocaleDateString(),
|
||||
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
|
||||
},
|
||||
{
|
||||
header: 'Aksi',
|
||||
cell: (props) => {
|
||||
const currentPageSize =
|
||||
props.table.getPaginationRowModel().rows.length;
|
||||
const currentPageRows =
|
||||
props.table.getPaginationRowModel().flatRows;
|
||||
const currentRowRelativeIndex =
|
||||
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||
// {
|
||||
// header: 'Aksi',
|
||||
// cell: (props) => {
|
||||
// const currentPageSize =
|
||||
// props.table.getPaginationRowModel().rows.length;
|
||||
// const currentPageRows =
|
||||
// props.table.getPaginationRowModel().flatRows;
|
||||
// const currentRowRelativeIndex =
|
||||
// currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||
|
||||
const isLast2Rows =
|
||||
currentRowRelativeIndex > currentPageSize - 2;
|
||||
// const isLast2Rows =
|
||||
// currentRowRelativeIndex > currentPageSize - 2;
|
||||
|
||||
const deleteClickHandler = () => {
|
||||
setSelectedProjectFlock(props.row.original);
|
||||
deleteModal.openModal();
|
||||
};
|
||||
// const deleteClickHandler = () => {
|
||||
// setSelectedProjectFlock(props.row.original);
|
||||
// deleteModal.openModal();
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentPageSize > 2 && (
|
||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
</RowDropdownOptions>
|
||||
)}
|
||||
// return (
|
||||
// <>
|
||||
// {currentPageSize > 2 && (
|
||||
// <RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||
// <RowOptionsMenu
|
||||
// type='dropdown'
|
||||
// props={props}
|
||||
// deleteClickHandler={deleteClickHandler}
|
||||
// />
|
||||
// </RowDropdownOptions>
|
||||
// )}
|
||||
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
</RowCollapseOptions>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
// {currentPageSize <= 2 && (
|
||||
// <RowCollapseOptions>
|
||||
// <RowOptionsMenu
|
||||
// type='collapse'
|
||||
// props={props}
|
||||
// deleteClickHandler={deleteClickHandler}
|
||||
// />
|
||||
// </RowCollapseOptions>
|
||||
// )}
|
||||
// </>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
]}
|
||||
pageSize={tableFilterState.pageSize}
|
||||
page={
|
||||
@@ -597,7 +617,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
{
|
||||
action: 'DELETE',
|
||||
icon: 'material-symbols:delete-outline-rounded',
|
||||
label: 'Hapus Massal',
|
||||
label: `Hapus ${selectedRowIds.length} data`,
|
||||
onClick: () => {
|
||||
toast.error(`Konfirmasi hapus ${selectedRowIds.length} data.`);
|
||||
},
|
||||
@@ -611,7 +631,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
onClick: () => {
|
||||
setApprovalAction('APPROVED');
|
||||
confirmModal.openModal();
|
||||
setRowSelection({});
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -621,7 +640,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
onClick: () => {
|
||||
setApprovalAction('REJECTED');
|
||||
confirmModal.openModal();
|
||||
setRowSelection({});
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Badge from '@/components/Badge';
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
const ProjectFlockDetail = ({
|
||||
@@ -17,6 +19,7 @@ const ProjectFlockDetail = ({
|
||||
}: {
|
||||
projectFlock: ProjectFlock;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const [openBudgets, setOpenBudget] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -32,18 +35,37 @@ const ProjectFlockDetail = ({
|
||||
<Icon icon='mdi:close' width={24} height={24} />
|
||||
</Link>
|
||||
<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')}
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +80,7 @@ const ProjectFlockDetail = ({
|
||||
variant='soft'
|
||||
color={
|
||||
projectFlock.approval.step_number == 1
|
||||
? 'secondary'
|
||||
? 'neutral'
|
||||
: projectFlock.approval.step_number == 2
|
||||
? 'success'
|
||||
: projectFlock.approval.step_number >= 3
|
||||
@@ -75,7 +97,7 @@ const ProjectFlockDetail = ({
|
||||
height={12}
|
||||
color={
|
||||
projectFlock.approval.step_number == 1
|
||||
? 'secondary'
|
||||
? 'neutral'
|
||||
: projectFlock.approval.step_number == 2
|
||||
? 'success'
|
||||
: projectFlock.approval.step_number >= 3
|
||||
@@ -87,7 +109,7 @@ const ProjectFlockDetail = ({
|
||||
</Badge>
|
||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
||||
<Badge
|
||||
color='secondary'
|
||||
color='neutral'
|
||||
variant='soft'
|
||||
className={{ badge: 'rounded-lg px-2' }}
|
||||
>
|
||||
@@ -103,7 +125,7 @@ const ProjectFlockDetail = ({
|
||||
<div className='col-span-2'>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='secondary'
|
||||
color='neutral'
|
||||
className={{
|
||||
badge: 'rounded-lg px-2',
|
||||
}}
|
||||
@@ -191,7 +213,7 @@ const ProjectFlockDetail = ({
|
||||
</Badge>
|
||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
||||
<Badge
|
||||
color='secondary'
|
||||
color='neutral'
|
||||
variant='soft'
|
||||
className={{ badge: 'rounded-lg px-2 cursor-pointer' }}
|
||||
onClick={() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { Icon } from '@iconify/react';
|
||||
import { FormikErrors, useFormik } from 'formik';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { use, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import useSWR, { KeyedMutator } from 'swr';
|
||||
import {
|
||||
ProjectFlockBudgetsSchemaType,
|
||||
@@ -47,6 +47,7 @@ import Card from '@/components/Card';
|
||||
import ProjectFlockKandangTable from '@/components/pages/production/project-flock/form/ProjectFlockKandangTable';
|
||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||
import { useUiStore } from '@/stores/ui/ui.store';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface ProjectFlockFormProps {
|
||||
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
|
||||
const { isLoadingOptions: isLoadingFlocks, options: optionsFlock } =
|
||||
useSelect(FlockApi.basePath, 'id', 'name');
|
||||
@@ -221,7 +209,12 @@ const ProjectFlockForm = ({
|
||||
formik.setFieldValue('area_id', (val as OptionType)?.value);
|
||||
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);
|
||||
setSelectedLocation('');
|
||||
@@ -254,7 +247,12 @@ const ProjectFlockForm = ({
|
||||
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) => {
|
||||
@@ -282,7 +280,7 @@ const ProjectFlockForm = ({
|
||||
const updateProjectFlockHandler = async (
|
||||
payload: CreateProjectFlockPayload
|
||||
) => {
|
||||
const updateProjectFlockRes = await ProjectFlockApi.update(
|
||||
const updateProjectFlockRes = await ProjectFlockApi.resubmit(
|
||||
initialValues?.id as number,
|
||||
payload
|
||||
);
|
||||
@@ -314,21 +312,14 @@ const ProjectFlockForm = ({
|
||||
0,
|
||||
initialValues?.flock_name?.lastIndexOf(' ')
|
||||
) ?? '';
|
||||
const optionFind = optionsFlock.find((flock) => {
|
||||
return flock.label == trimFlock;
|
||||
}) as OptionType;
|
||||
return {
|
||||
flock: initialValues?.flock_name
|
||||
? {
|
||||
value:
|
||||
optionsFlock.find((flock) => {
|
||||
return flock.label == trimFlock;
|
||||
})?.value ?? 0,
|
||||
label:
|
||||
formType != 'detail'
|
||||
? (optionsFlock.find((flock) => {
|
||||
return flock.label == trimFlock;
|
||||
})?.label ?? '')
|
||||
: initialValues?.flock_name,
|
||||
}
|
||||
: null,
|
||||
flock:
|
||||
optionsFlock.find((flock) => {
|
||||
return flock.label == trimFlock;
|
||||
}) ?? null,
|
||||
area: initialValues?.area
|
||||
? {
|
||||
value: initialValues.area?.id,
|
||||
@@ -355,14 +346,8 @@ const ProjectFlockForm = ({
|
||||
: null,
|
||||
flock_name:
|
||||
optionsFlock.find((flock) => {
|
||||
return (
|
||||
flock.label ==
|
||||
initialValues?.flock_name?.slice(
|
||||
0,
|
||||
initialValues?.flock_name?.lastIndexOf(' ')
|
||||
)
|
||||
);
|
||||
})?.label ?? '',
|
||||
return flock.label == trimFlock;
|
||||
})?.label ?? trimFlock,
|
||||
area_id: initialValues?.area?.id ?? 0,
|
||||
category: initialValues?.category as NonNullable<
|
||||
'GROWING' | 'LAYING' | undefined
|
||||
@@ -372,7 +357,18 @@ const ProjectFlockForm = ({
|
||||
kandang_ids: initialValues?.kandangs?.map(
|
||||
(k: Kandang) => k.id
|
||||
) 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_id: '',
|
||||
@@ -387,94 +383,13 @@ const ProjectFlockForm = ({
|
||||
// Formik
|
||||
const formik = useFormik<ProjectFlockFormValues>({
|
||||
initialValues: {
|
||||
flock: initialValues?.flock_name
|
||||
? {
|
||||
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: '',
|
||||
},
|
||||
],
|
||||
...formikInitialValues,
|
||||
} as ProjectFlockFormValues,
|
||||
enableReinitialize: true,
|
||||
validationSchema:
|
||||
formType == 'add' ? ProjectFlockFormSchema : UpdateProjectFlockFormSchema,
|
||||
validateOnBlur: true,
|
||||
validateOnChange: true,
|
||||
validateOnMount: true,
|
||||
// validateOnChange: true,
|
||||
// validateOnMount: true,
|
||||
onSubmit: async (values) => {
|
||||
setProjectFlockFormErrorMessage('');
|
||||
const payload: CreateProjectFlockPayload = {
|
||||
@@ -522,7 +437,18 @@ const ProjectFlockForm = ({
|
||||
}, [initialValues, setSelectedArea, formType]);
|
||||
|
||||
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]);
|
||||
|
||||
// Aktifkan lokasi jika formType = 'detail'
|
||||
@@ -542,10 +468,6 @@ const ProjectFlockForm = ({
|
||||
}
|
||||
}, [formType, initialValues]);
|
||||
|
||||
useEffect(() => {
|
||||
formik.validateForm();
|
||||
}, [formik.values]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedRowIds = Object.keys(rowSelection)
|
||||
.filter((id) => rowSelection[id])
|
||||
@@ -583,6 +505,19 @@ const ProjectFlockForm = ({
|
||||
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
|
||||
const confirmationModalDeleteClickHandler = async () => {
|
||||
setIsDeleteLoading(true);
|
||||
@@ -727,31 +662,59 @@ const ProjectFlockForm = ({
|
||||
: undefined;
|
||||
const inputPeriod =
|
||||
(initialValues?.period ?? selectedPeriod == 0) ? 1 : selectedPeriod;
|
||||
|
||||
const filteredNonStockOptions = optionsNonstock.filter((nonstock) => {
|
||||
return !(formik.values.project_budgets ?? []).some(
|
||||
(budget) => budget.nonstock_id === nonstock.value
|
||||
);
|
||||
const isNonstockAlreadyInBudgets = (
|
||||
formik.values.project_budgets ?? []
|
||||
).some((budget) => budget.nonstock_id === nonstock.value);
|
||||
|
||||
return !isNonstockAlreadyInBudgets;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='w-full'>
|
||||
<header className='flex flex-col gap-4 mb-6'>
|
||||
<Button
|
||||
href='/production/project-flock'
|
||||
variant='link'
|
||||
className='w-fit p-0 text-primary'
|
||||
>
|
||||
<Icon icon='uil:arrow-left' width={24} height={24} />
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<h1 className='text-2xl font-bold text-center'>
|
||||
{formType === 'add' && 'Tambah Project Flock'}
|
||||
{formType === 'edit' && 'Edit Project Flock'}
|
||||
{formType === 'detail' && 'Detail Project Flock'}
|
||||
</h1>
|
||||
</header>
|
||||
{/* Header */}
|
||||
<div className='flex flex-row justify-between items-center px-4 pt-4'>
|
||||
<div className='flex flex-row h-full gap-2'>
|
||||
<Link
|
||||
href={
|
||||
formType == 'add'
|
||||
? '/production/project-flock'
|
||||
: `/production/project-flock/detail?projectFlockId=${initialValues?.id}`
|
||||
}
|
||||
className='hover:text-gray-400'
|
||||
>
|
||||
<Icon
|
||||
icon={formType == 'add' ? 'mdi:close' : 'mdi:arrow-left'}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</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 && (
|
||||
<div className='my-4'>
|
||||
<div role='alert' className='alert alert-error'>
|
||||
@@ -829,15 +792,11 @@ const ProjectFlockForm = ({
|
||||
onSubmit={formik.handleSubmit}
|
||||
onReset={formik.handleReset}
|
||||
>
|
||||
{/* Card Informasi Umum */}
|
||||
<Card
|
||||
title='Informasi Umum'
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full mb-4',
|
||||
}}
|
||||
>
|
||||
<div className='grid sm:grid-cols-2 gap-4'>
|
||||
{/* Form Informasi Umum */}
|
||||
<div className='divider mt-3'></div>
|
||||
<div className='flex flex-col gap-4 px-4'>
|
||||
<h2 className='text-2xl font-semibold'>Informasi Umum</h2>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<SelectInput
|
||||
required
|
||||
label='Area'
|
||||
@@ -852,6 +811,25 @@ const ProjectFlockForm = ({
|
||||
isClearable
|
||||
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
|
||||
required
|
||||
label='Flock'
|
||||
@@ -882,25 +860,6 @@ const ProjectFlockForm = ({
|
||||
isClearable
|
||||
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
|
||||
required
|
||||
label='FCR'
|
||||
@@ -937,17 +896,12 @@ const ProjectFlockForm = ({
|
||||
value={selectedLocation ? inputPeriod : ''}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Card Pilih Kandang */}
|
||||
<Card
|
||||
collapsible
|
||||
title='Pilih Kandang'
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full mb-4',
|
||||
}}
|
||||
>
|
||||
{/* Form Pilih Kandang */}
|
||||
<div className='divider'></div>
|
||||
<div className='flex flex-col gap-4 px-4 pb-4'>
|
||||
<h2 className='text-2xl font-semibold'>Pilih Kandang</h2>
|
||||
<div className='overflow-x-auto duration-300 ease-in-out'>
|
||||
{isLoadingKandang && (
|
||||
<span className='loading loading-dots loading-xl'></span>
|
||||
@@ -964,33 +918,43 @@ const ProjectFlockForm = ({
|
||||
initialValues={initialValues}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Card Estimasi Budget */}
|
||||
<Card
|
||||
collapsible
|
||||
title='Estimasi Aggaran per Kandang'
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
}}
|
||||
>
|
||||
<table className='w-full mt-4'>
|
||||
<thead>
|
||||
<tr className='border-b border-gray-300'>
|
||||
<th className='text-start px-2 py-3'>Produk</th>
|
||||
<th className='text-start px-2 py-3'>Kuantitas</th>
|
||||
<th className='text-start px-2 py-3'>Harga Satuan</th>
|
||||
<th className='text-start px-2 py-3'>Harga Total</th>
|
||||
<th className='text-start px-2 py-3'>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.project_budgets &&
|
||||
formik.values.project_budgets.length > 0 ? (
|
||||
formik.values.project_budgets.map((budget, index) => (
|
||||
<tr key={index} className='align-top'>
|
||||
<td className='px-2 py-3'>
|
||||
<div className='divider'></div>
|
||||
<div className='flex flex-col gap-4 px-4 pb-4'>
|
||||
<h2 className='text-2xl font-semibold'>
|
||||
Estimasi Aggaran Per Flock
|
||||
</h2>
|
||||
<div className='flex flex-col gap-4'>
|
||||
{formik.values.project_budgets &&
|
||||
formik.values.project_budgets.length > 0 ? (
|
||||
formik.values.project_budgets.map((budget, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
body: 'p-3',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex flex-row justify-between items-center mb-2'>
|
||||
<div className='text-lg'>Anggaran ke-{index + 1}</div>
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
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
|
||||
isClearable
|
||||
options={filteredNonStockOptions ?? []}
|
||||
@@ -1034,8 +998,8 @@ const ProjectFlockForm = ({
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className='px-2 py-3'>
|
||||
</div>
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<NumberInput
|
||||
name={`project_budgets[${index}].qty`}
|
||||
placeholder='Masukkan jumlah'
|
||||
@@ -1046,11 +1010,13 @@ const ProjectFlockForm = ({
|
||||
onBlur={formik.handleBlur}
|
||||
allowNegative={false}
|
||||
endAdornment={
|
||||
isResponseSuccess(nonstocks)
|
||||
? (nonstocks.data.find(
|
||||
(ns) => ns.id === budget.nonstock_id
|
||||
)?.uom?.name ?? '')
|
||||
: ''
|
||||
<div className='text-gray-500'>
|
||||
{isResponseSuccess(nonstocks)
|
||||
? (nonstocks.data.find(
|
||||
(ns) => ns.id === budget.nonstock_id
|
||||
)?.uom?.name ?? '')
|
||||
: ''}
|
||||
</div>
|
||||
}
|
||||
errorMessage={
|
||||
(
|
||||
@@ -1070,8 +1036,8 @@ const ProjectFlockForm = ({
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className='px-2 py-3'>
|
||||
</div>
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<NumberInput
|
||||
name={`project_budgets[${index}].price`}
|
||||
value={formik.values.project_budgets[index].price}
|
||||
@@ -1082,6 +1048,17 @@ const ProjectFlockForm = ({
|
||||
placeholder='Masukkan harga satuan'
|
||||
allowNegative={false}
|
||||
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={
|
||||
(
|
||||
formik.errors.project_budgets?.[
|
||||
@@ -1100,8 +1077,8 @@ const ProjectFlockForm = ({
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className='px-2 py-3'>
|
||||
</div>
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<NumberInput
|
||||
name={`project_budgets[${index}].total_price`}
|
||||
value={
|
||||
@@ -1115,9 +1092,12 @@ const ProjectFlockForm = ({
|
||||
)
|
||||
}
|
||||
onBlur={formik.handleBlur}
|
||||
placeholder='Masukkan harga satuan'
|
||||
placeholder='Masukkan harga total'
|
||||
allowNegative={false}
|
||||
startAdornment='Rp'
|
||||
endAdornment={
|
||||
<div className='text-gray-500'>Total</div>
|
||||
}
|
||||
errorMessage={
|
||||
(
|
||||
formik.errors.project_budgets?.[
|
||||
@@ -1137,108 +1117,50 @@ const ProjectFlockForm = ({
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className='px-2 py-4'>
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
onClick={() =>
|
||||
onDeleteBudgetRowHandler(
|
||||
budget.nonstock_id as number,
|
||||
index
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={4} className='text-center py-4 text-gray-500'>
|
||||
Tidak ada data estimasi anggaran.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<div className='text-center py-4 text-gray-400'>
|
||||
Tidak ada data estimasi anggaran.
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
type='button'
|
||||
onClick={onAddBudgetRowHandler}
|
||||
disabled={filteredNonStockOptions.length == 0}
|
||||
color='success'
|
||||
className='w-fit self-center'
|
||||
>
|
||||
<Icon icon='mdi:plus' width={16} height={16} /> Add Budget
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
onClick={onAddBudgetRowHandler}
|
||||
disabled={filteredNonStockOptions.length == 0}
|
||||
>
|
||||
<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 className='flex flex-row justify-center gap-2 flex-wrap my-6 px-4'>
|
||||
{/* <div className='w-120'>
|
||||
<div className='text-primary text-sm'>
|
||||
{JSON.stringify(formik.values)}
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<ConfirmationModal
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Badge from '@/components/Badge';
|
||||
import Card from '@/components/Card';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import PillBadge from '@/components/PillBadge';
|
||||
import Table from '@/components/Table';
|
||||
@@ -9,6 +11,7 @@ import {
|
||||
ProjectFlock,
|
||||
ProjectFlockPeriods,
|
||||
} from '@/types/api/production/project-flock';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { OnChangeFn, Row } from '@tanstack/react-table';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -29,161 +32,119 @@ const ProjectFlockKandangTable = ({
|
||||
initialValues?: ProjectFlock;
|
||||
formType: 'add' | 'edit' | 'detail';
|
||||
}) => {
|
||||
const initialKandangIdSet = useMemo(() => {
|
||||
return initialValues?.kandangs.map((k) => k.id) ?? [];
|
||||
}, [initialValues]);
|
||||
const isRowEnabled = (row: Row<Kandang>) => {
|
||||
const isDisabled =
|
||||
!initialKandangIdSet.includes(row.original.id) &&
|
||||
(row.original.status == 'ACTIVE' ||
|
||||
row.original.status == 'PENGAJUAN' ||
|
||||
formType == 'detail');
|
||||
return !isDisabled;
|
||||
// Fungsi untuk menangani perubahan checkbox
|
||||
const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => {
|
||||
// Hanya izinkan perubahan jika tidak dalam mode 'detail'
|
||||
if (formType === 'detail') return;
|
||||
|
||||
// Pastikan kandang.id ada dan tidak null/undefined
|
||||
if (kandang.id === undefined) return;
|
||||
|
||||
const kandangIdString = kandang.id.toString();
|
||||
|
||||
setRowSelection((prev) => {
|
||||
const newSelection = { ...prev };
|
||||
if (isChecked) {
|
||||
newSelection[kandangIdString] = true;
|
||||
} else {
|
||||
delete newSelection[kandangIdString];
|
||||
}
|
||||
return newSelection;
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Table<Kandang>
|
||||
data={listKandang}
|
||||
columns={[
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => {
|
||||
const allRows = table.getRowModel().rows;
|
||||
// 1. Filter semua baris dengan logika yang sama persis seperti di cell
|
||||
const selectableRows = allRows.filter(isRowEnabled);
|
||||
{listKandang.length > 0 ? (
|
||||
<>
|
||||
{/* ... Bagian Badge Status ... */}
|
||||
<div className='flex flex-row mb-4'>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='primary'
|
||||
className={{
|
||||
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 allSelected =
|
||||
selectableRows.length > 0 &&
|
||||
selectableRows.every((row) => row.getIsSelected());
|
||||
const isSelected =
|
||||
!!rowSelection[kandangIdString] ||
|
||||
(kandang.id !== undefined &&
|
||||
selectedIds.includes(kandang.id));
|
||||
|
||||
// 3. Cek apakah BEBERAPA baris yang BISA DIPILIH sudah terpilih
|
||||
const someSelected =
|
||||
selectableRows.some((row) => row.getIsSelected()) &&
|
||||
!allSelected;
|
||||
const isDisabled =
|
||||
formType == 'detail' || kandang.status != 'NON_ACTIVE';
|
||||
|
||||
// 4. Fungsi toggle HANYA akan mentoggle baris yang BISA DIPILIH
|
||||
const toggleSelectableRows = () => {
|
||||
const shouldSelect = !allSelected;
|
||||
selectableRows.forEach((row) =>
|
||||
row.toggleSelected(shouldSelect)
|
||||
return (
|
||||
<div key={index} className='flex flex-row justify-between'>
|
||||
<CheckboxInput
|
||||
name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-full flex flex-row justify-center'>
|
||||
<CheckboxInput
|
||||
name='allRow'
|
||||
checked={allSelected}
|
||||
indeterminate={someSelected}
|
||||
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}
|
||||
/>
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<div className='text-center py-4 text-gray-400'>
|
||||
Pilih lokasi terlebih dahulu
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user