mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
Merge branch 'feat/FE/US-75/chick-in-doc' into 'development'
[FEAT/FE][US#75] Chick In DOC See merge request mbugroup/lti-web-client!34
This commit is contained in:
Generated
+457
-548
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
|
"react-number-format": "^5.4.4",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import Modal, { useModal } from '@/components/Modal';
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
|
import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -14,7 +15,7 @@ import { Kandang } from '@/types/api/master-data/kandang';
|
|||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
@@ -33,6 +34,10 @@ const AddChickin = () => {
|
|||||||
const [selectedKandang, setSelectedKandang] = useState<Kandang | undefined>(
|
const [selectedKandang, setSelectedKandang] = useState<Kandang | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
const [projectFlockKandang, setProjectFlockKandang] =
|
||||||
|
useState<BaseApiResponse<ProjectFlockKandang>>();
|
||||||
|
const [isLoadingProjectFlockKandang, setIsLoadingProjectFlockKandang] =
|
||||||
|
useState(false);
|
||||||
const [searchProjectFlock, setSearchProjectFlock] = useState('');
|
const [searchProjectFlock, setSearchProjectFlock] = useState('');
|
||||||
|
|
||||||
// Fetch Data
|
// Fetch Data
|
||||||
@@ -41,44 +46,26 @@ const AddChickin = () => {
|
|||||||
(id: number) => ProjectFlockApi.getSingle(id)
|
(id: number) => ProjectFlockApi.getSingle(id)
|
||||||
);
|
);
|
||||||
const { data: listProjectFlock, isLoading: isLoadingListProjectFlock } =
|
const { data: listProjectFlock, isLoading: isLoadingListProjectFlock } =
|
||||||
useSWR(`${ProjectFlockApi.basePath}?${new URLSearchParams({
|
useSWR(
|
||||||
|
`${ProjectFlockApi.basePath}?${new URLSearchParams({
|
||||||
search: searchProjectFlock,
|
search: searchProjectFlock,
|
||||||
}).toString()}`, ProjectFlockApi.getAllFetcher);
|
}).toString()}`,
|
||||||
|
ProjectFlockApi.getAllFetcher
|
||||||
const getProjectFlockKandangUrl = `/kandangs/lookup`;
|
|
||||||
const {
|
|
||||||
data: projectFlockKandang,
|
|
||||||
isLoading: isLoadingProjectFlockKandang,
|
|
||||||
mutate: refreshProjectFlockKandang,
|
|
||||||
} = useSWR(getProjectFlockKandangUrl, () =>
|
|
||||||
ProjectFlockApi.customRequest<BaseApiResponse<ProjectFlockKandang>, 'GET'>(
|
|
||||||
getProjectFlockKandangUrl,
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
params: {
|
|
||||||
project_flock_id: projectFlockId ?? 0,
|
|
||||||
kandang_id: selectedKandang?.id,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getProjectFlockKandangUrl = `/kandangs/lookup`;
|
||||||
// Mapping Options
|
// Mapping Options
|
||||||
const options = isResponseSuccess(listProjectFlock)
|
const options = isResponseSuccess(listProjectFlock)
|
||||||
? listProjectFlock?.data.map((projectFlock) => {
|
? listProjectFlock?.data.map((projectFlock) => {
|
||||||
return {
|
return {
|
||||||
value: projectFlock.id,
|
value: projectFlock.id,
|
||||||
label: `${projectFlock?.flock.name} - ${projectFlock?.category} - Periode ${projectFlock.period}` ,
|
label: `${projectFlock?.flock?.name} - ${projectFlock?.category} - Periode ${projectFlock.period}`,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const chickinModal = useModal();
|
const chickinModal = useModal();
|
||||||
|
const alertModal = useModal();
|
||||||
// Use Effect
|
|
||||||
useEffect(() => {
|
|
||||||
refreshProjectFlockKandang();
|
|
||||||
}, [selectedKandang, refreshProjectFlockKandang]);
|
|
||||||
|
|
||||||
if (!projectFlockId) {
|
if (!projectFlockId) {
|
||||||
router.back();
|
router.back();
|
||||||
@@ -99,13 +86,33 @@ const AddChickin = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle Function
|
// Handle Function
|
||||||
const handleChickinClick = (kandang: Kandang) => {
|
const handleChickinClick = async (kandang: Kandang) => {
|
||||||
|
setIsLoadingProjectFlockKandang(true);
|
||||||
setSelectedKandang(kandang);
|
setSelectedKandang(kandang);
|
||||||
refreshProjectFlockKandang();
|
const ProjectFlockKandangRes = await ProjectFlockApi.customRequest<
|
||||||
|
BaseApiResponse<ProjectFlockKandang>,
|
||||||
|
'GET'
|
||||||
|
>(getProjectFlockKandangUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
project_flock_id: projectFlockId ?? 0,
|
||||||
|
kandang_id: kandang.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (isResponseSuccess(ProjectFlockKandangRes)) {
|
||||||
|
setProjectFlockKandang(ProjectFlockKandangRes);
|
||||||
|
setIsLoadingProjectFlockKandang(false);
|
||||||
|
if (
|
||||||
|
ProjectFlockKandangRes.data.available_quantity &&
|
||||||
|
ProjectFlockKandangRes.data.available_quantity > 0
|
||||||
|
) {
|
||||||
chickinModal.openModal();
|
chickinModal.openModal();
|
||||||
|
} else {
|
||||||
|
alertModal.openModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const handleAfterSubmit = () => {
|
const handleAfterSubmit = () => {
|
||||||
refreshProjectFlockKandang();
|
|
||||||
chickinModal.closeModal();
|
chickinModal.closeModal();
|
||||||
router.push('/production/chickin');
|
router.push('/production/chickin');
|
||||||
};
|
};
|
||||||
@@ -126,7 +133,7 @@ const AddChickin = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className='flex flex-col gap-4 w-full my-4'>
|
<div className='flex flex-col gap-4 w-full my-4'>
|
||||||
<div className='max-w-1/4'>
|
<div className='max-w-full sm:max-w-1/2 md:max-w-3/5 lg:max-w-2/5'>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
isSearchable
|
isSearchable
|
||||||
@@ -134,8 +141,8 @@ const AddChickin = () => {
|
|||||||
options={options}
|
options={options}
|
||||||
isLoading={isLoadingListProjectFlock}
|
isLoading={isLoadingListProjectFlock}
|
||||||
value={{
|
value={{
|
||||||
label: `${projectFlock.data.flock.name} - ${projectFlock.data.category} - Periode ${projectFlock.data.period}`,
|
label: `${projectFlock.data?.flock?.name} - ${projectFlock.data?.category} - Periode ${projectFlock.data?.period}`,
|
||||||
value: projectFlock.data.id,
|
value: projectFlock.data?.id,
|
||||||
}}
|
}}
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
router.push(
|
router.push(
|
||||||
@@ -152,7 +159,7 @@ const AddChickin = () => {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<Table<Kandang>
|
<Table<Kandang>
|
||||||
data={projectFlock.data.kandangs}
|
data={projectFlock.data?.kandangs}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: '#',
|
header: '#',
|
||||||
@@ -173,10 +180,10 @@ const AddChickin = () => {
|
|||||||
<Button
|
<Button
|
||||||
color='success'
|
color='success'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
isLoading={isLoadingProjectFlockKandang}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleChickinClick(props.row.original);
|
handleChickinClick(props.row.original);
|
||||||
}}
|
}}
|
||||||
|
disabled={isLoadingProjectFlockKandang}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='mdi:home-import-outline'
|
icon='mdi:home-import-outline'
|
||||||
@@ -195,7 +202,7 @@ const AddChickin = () => {
|
|||||||
containerClassName: cn({
|
containerClassName: cn({
|
||||||
'mb-20':
|
'mb-20':
|
||||||
isResponseSuccess(projectFlock) &&
|
isResponseSuccess(projectFlock) &&
|
||||||
projectFlock.data.kandangs?.length === 0,
|
projectFlock.data?.kandangs?.length === 0,
|
||||||
}),
|
}),
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||||
@@ -232,14 +239,28 @@ const AddChickin = () => {
|
|||||||
<ChickinForm
|
<ChickinForm
|
||||||
initialValues={{
|
initialValues={{
|
||||||
project_flock_kandang: projectFlockKandang.data,
|
project_flock_kandang: projectFlockKandang.data,
|
||||||
created_user: projectFlock.data.created_user,
|
created_user: projectFlock.data?.created_user,
|
||||||
created_at: projectFlock.data.created_at,
|
created_at: projectFlock.data?.created_at,
|
||||||
updated_at: projectFlock.data.updated_at,
|
updated_at: projectFlock.data?.updated_at,
|
||||||
|
approval: projectFlock.data?.approval,
|
||||||
}}
|
}}
|
||||||
afterSubmit={handleAfterSubmit}
|
afterSubmit={handleAfterSubmit}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={alertModal.ref}
|
||||||
|
type='info'
|
||||||
|
text={`Persediaan Day Old Chick pada kandang (${selectedKandang?.name}) belum ada, mohon isi terlebih dahulu di bagian Persediaan!`}
|
||||||
|
secondaryButton={undefined}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'info',
|
||||||
|
onClick: () => {
|
||||||
|
alertModal.closeModal();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import SuspenseHelper from "@/components/helper/SuspenseHelper"
|
||||||
|
|
||||||
|
const Layout = ({
|
||||||
|
children
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode
|
||||||
|
}>) => {
|
||||||
|
return <SuspenseHelper>{children}</SuspenseHelper>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
|
||||||
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { ChickinApi } from '@/services/api/production';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import {
|
||||||
|
Chickin,
|
||||||
|
ChickinApprovalPayload,
|
||||||
|
} from '@/types/api/production/chickin';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Refactor code - pindahin detail ke reuseable component
|
||||||
|
* setelah implement approval and reject
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DetailChickin = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const chickinId = searchParams.get('chickinId');
|
||||||
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
|
const confirmModal = useModal();
|
||||||
|
const deleteModal = useModal();
|
||||||
|
const chickinModal = useModal();
|
||||||
|
const {
|
||||||
|
data: chickin,
|
||||||
|
isLoading,
|
||||||
|
mutate: refreshChickin,
|
||||||
|
} = useSWR(chickinId, (id: number) => ChickinApi.getSingle(id));
|
||||||
|
|
||||||
|
const [isApprovedDisabled, setIsApprovedDisabled] = useState(
|
||||||
|
// chickin.data?.approval.step_number == 1 ? false : true
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const [isRejectedDisabled, setIsRejectedDisabled] = useState(
|
||||||
|
!isApprovedDisabled
|
||||||
|
);
|
||||||
|
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
|
||||||
|
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!chickinId) {
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoading && (!chickin || isResponseError(chickin))) {
|
||||||
|
router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResponseSuccess(chickin)) {
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
|
<span className='loading loading-spinner loading-xl' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmationModalClickHandler = async ({
|
||||||
|
action = 'APPROVED',
|
||||||
|
}: {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
|
}) => {
|
||||||
|
if (chickin?.data.id === undefined) return;
|
||||||
|
setIsApproveLoading(true);
|
||||||
|
const approveChickinRes = await ChickinApi.customRequest<
|
||||||
|
BaseApiResponse<Chickin>,
|
||||||
|
ChickinApprovalPayload
|
||||||
|
>(`/approvals`, {
|
||||||
|
method: 'POST',
|
||||||
|
payload: {
|
||||||
|
action: action,
|
||||||
|
approvable_ids: [chickin.data.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isResponseSuccess(approveChickinRes)) {
|
||||||
|
if (refreshChickin) {
|
||||||
|
await refreshChickin();
|
||||||
|
}
|
||||||
|
toast.success(approveChickinRes.message as string);
|
||||||
|
}
|
||||||
|
if (isResponseError(approveChickinRes)) {
|
||||||
|
toast.error(approveChickinRes?.message as string);
|
||||||
|
}
|
||||||
|
confirmModal.closeModal();
|
||||||
|
setIsApproveLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
|
setIsDeleteLoading(true);
|
||||||
|
const deleteProjectFlockRes = await ChickinApi.delete(
|
||||||
|
chickin.data?.id as number
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseSuccess(deleteProjectFlockRes)) {
|
||||||
|
toast.success(deleteProjectFlockRes?.message as string);
|
||||||
|
router.push('/production/chickin');
|
||||||
|
}
|
||||||
|
if (isResponseError(deleteProjectFlockRes)) {
|
||||||
|
toast.error(deleteProjectFlockRes?.message as string);
|
||||||
|
}
|
||||||
|
deleteModal.closeModal();
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='w-full p-4 flex flex-col justify-center gap-4'>
|
||||||
|
{isLoading && <span className='loading loading-spinner loading-xl' />}
|
||||||
|
{!isLoading && isResponseSuccess(chickin) && (
|
||||||
|
<>
|
||||||
|
{/* <div className='w-full flex flex-col sm:flex-row gap-2'>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='success'
|
||||||
|
onClick={(() => {
|
||||||
|
if (chickin?.data.id) {
|
||||||
|
setApprovalAction('APPROVED');
|
||||||
|
confirmModal.openModal();
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
disabled={!chickin?.data.id || isApprovedDisabled}
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='error'
|
||||||
|
onClick={() => {
|
||||||
|
if (chickin?.data.id) {
|
||||||
|
setApprovalAction('REJECTED');
|
||||||
|
confirmModal.openModal();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!chickin?.data.id || isRejectedDisabled}
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:times' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</div> */}
|
||||||
|
<Card
|
||||||
|
title='Informasi Umum'
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 gap-4 mt-4'>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Flock</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{
|
||||||
|
chickin.data.project_flock_kandang?.project_flock.flock
|
||||||
|
.name
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Area</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{
|
||||||
|
chickin.data.project_flock_kandang?.project_flock.area
|
||||||
|
.name
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Kategori</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{chickin.data.project_flock_kandang?.project_flock.category}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Lokasi</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{
|
||||||
|
chickin.data.project_flock_kandang?.project_flock.location
|
||||||
|
.name
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Periode</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{chickin.data.project_flock_kandang?.project_flock.period}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Kandang</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{chickin.data.project_flock_kandang?.kandang.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
title='Detail Chickin'
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 gap-4 mt-4'>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Flock Kandang</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{
|
||||||
|
chickin.data.project_flock_kandang?.project_flock.flock
|
||||||
|
.name
|
||||||
|
}{' '}
|
||||||
|
- {chickin.data.project_flock_kandang?.kandang.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Tanggal Chickin</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{chickin.data.chick_in_date
|
||||||
|
? new Date(chickin.data.chick_in_date).toLocaleDateString(
|
||||||
|
'id-ID'
|
||||||
|
)
|
||||||
|
: '-'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Jumlah (Ekor)</div>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{chickin.data.quantity?.toLocaleString('id-ID')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='font-semibold text-sm'>Catatan</div>
|
||||||
|
<div className='text-sm'>{chickin.data.note}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<div className='w-full flex flex-col sm:flex-row gap-2'>
|
||||||
|
<Button
|
||||||
|
color='error'
|
||||||
|
onClick={() => {
|
||||||
|
deleteModal.openModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:times' width={24} height={24} />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button color='warning'
|
||||||
|
onClick={() => {
|
||||||
|
chickinModal.openModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:pencil-outline' width={24} height={24} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={deleteModal.ref}
|
||||||
|
type='error'
|
||||||
|
text={`Apakah anda yakin ingin menghapus data Project Flock ini (${chickin?.data.project_flock_kandang?.project_flock.flock.name} - ${chickin?.data.project_flock_kandang?.kandang.name})?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'error',
|
||||||
|
isLoading: isDeleteLoading,
|
||||||
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Modal ref={chickinModal.ref}>
|
||||||
|
<div className='flex flex-row justify-between items-center'>
|
||||||
|
<h1 className='text-xl font-semibold text-center mb-6'>
|
||||||
|
Chickin Kandang -{' '}
|
||||||
|
{chickin?.data?.project_flock_kandang &&
|
||||||
|
chickin?.data?.project_flock_kandang.kandang?.name}
|
||||||
|
</h1>
|
||||||
|
<Button
|
||||||
|
color='error'
|
||||||
|
variant='link'
|
||||||
|
onClick={chickinModal.closeModal}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className='text-black'
|
||||||
|
icon='uil:times'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ChickinForm
|
||||||
|
initialValues={chickin?.data}
|
||||||
|
formType='edit'
|
||||||
|
afterSubmit={() => {
|
||||||
|
refreshChickin();
|
||||||
|
chickinModal.closeModal();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<ConfirmationModal
|
||||||
|
ref={confirmModal.ref}
|
||||||
|
type={approvalAction == 'APPROVED' ? 'success' : 'error'}
|
||||||
|
text={`Apakah anda yakin ingin ${
|
||||||
|
approvalAction == 'APPROVED' ? 'approve' : 'reject'
|
||||||
|
} chickin berikut? (${
|
||||||
|
chickin?.data.project_flock_kandang?.project_flock.flock.name
|
||||||
|
} - ${chickin?.data.project_flock_kandang?.kandang.name})?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: approvalAction == 'APPROVED' ? 'success' : 'error',
|
||||||
|
isLoading: isApproveLoading,
|
||||||
|
onClick: () => {
|
||||||
|
confirmationModalClickHandler({
|
||||||
|
action: approvalAction,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailChickin;
|
||||||
@@ -1,46 +1,51 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
|
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
|
||||||
import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm";
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
|
import { ProjectFlockApi } from '@/services/api/production';
|
||||||
import { ProjectFlockApi } from "@/services/api/production";
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import useSWR from 'swr';
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
const ProjectFlockDetail = () => {
|
const ProjectFlockDetail = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const projectFlockId = searchParams.get("projectFlockId");
|
const projectFlockId = searchParams.get('projectFlockId');
|
||||||
|
|
||||||
const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR(
|
const {
|
||||||
projectFlockId,
|
data: projectFlock,
|
||||||
(id: number) => ProjectFlockApi.getSingle(id)
|
isLoading: isLoadingProjectFlock,
|
||||||
);
|
mutate: refreshProjectFlock,
|
||||||
|
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
|
||||||
|
|
||||||
if(!projectFlockId){
|
if (!projectFlockId) {
|
||||||
router.back();
|
router.back();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-row justify-center items-center p-4">
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
<span className="loading loading-spinner loading-xl" />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))){
|
if (
|
||||||
router.replace("/404");
|
!isLoadingProjectFlock &&
|
||||||
|
(!projectFlock || isResponseError(projectFlock))
|
||||||
|
) {
|
||||||
|
router.replace('/404');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-4 flex flex-row justify-center">
|
<div className='w-full p-4 flex flex-row justify-center'>
|
||||||
{isLoadingCostumer && <span className="loading loading-spinner loading-xl" />}
|
{isLoadingProjectFlock && (
|
||||||
{!isLoadingCostumer && isResponseSuccess(projectFlock) && (
|
<span className='loading loading-spinner loading-xl' />
|
||||||
<ProjectFlockForm formType="detail" initialValues={projectFlock.data} />
|
)}
|
||||||
|
{!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
|
||||||
|
<ProjectFlockForm formType='detail' initialValues={projectFlock.data} refreshProjectFlocks={refreshProjectFlock} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProjectFlockDetail;
|
export default ProjectFlockDetail;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm"
|
|
||||||
import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable";
|
import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable";
|
||||||
|
|
||||||
const ProjectFlock = () => {
|
const ProjectFlock = () => {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
|
interface PillBadgeProps {
|
||||||
|
content: ReactNode;
|
||||||
|
color?: 'yellow' | 'blue' | 'green' | 'red' | 'purple' | 'gray';
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PillBadge = ({ content, color = 'gray', className }: PillBadgeProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'w-fit min-w-max px-2 py-0.5 flex justify-center items-center gap-1 rounded-full border border-gray-200 bg-gray-50 text-gray-500 drop-shadow-xs capitalize',
|
||||||
|
{
|
||||||
|
'border-yellow-200 bg-yellow-50 text-yellow-500': color === 'yellow',
|
||||||
|
'border-blue-200 bg-blue-50 text-blue-500': color === 'blue',
|
||||||
|
'border-green-200 bg-green-50 text-green-500': color === 'green',
|
||||||
|
'border-red-200 bg-red-50 text-red-500': color === 'red',
|
||||||
|
'border-purple-200 bg-purple-50 text-purple-500': color === 'purple',
|
||||||
|
'border-neutral-200 bg-neutral-50 text-neutral-500': color === 'gray',
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PillBadge;
|
||||||
@@ -48,6 +48,8 @@ export interface TableProps<TData extends object> {
|
|||||||
sorting?: SortingState;
|
sorting?: SortingState;
|
||||||
setSorting?: OnChangeFn<SortingState>;
|
setSorting?: OnChangeFn<SortingState>;
|
||||||
manualSorting?: boolean;
|
manualSorting?: boolean;
|
||||||
|
rowSelection?: Record<string, boolean>;
|
||||||
|
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||||
@@ -86,6 +88,8 @@ const Table = <TData extends object>({
|
|||||||
sorting,
|
sorting,
|
||||||
setSorting,
|
setSorting,
|
||||||
manualSorting = false,
|
manualSorting = false,
|
||||||
|
rowSelection,
|
||||||
|
setRowSelection,
|
||||||
}: TableProps<TData>) => {
|
}: TableProps<TData>) => {
|
||||||
const isServerSideTable =
|
const isServerSideTable =
|
||||||
totalItems !== undefined &&
|
totalItems !== undefined &&
|
||||||
@@ -137,6 +141,15 @@ const Table = <TData extends object>({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rowSelection && setRowSelection) {
|
||||||
|
tableOptions.onRowSelectionChange = setRowSelection;
|
||||||
|
tableOptions.state = {
|
||||||
|
...tableOptions.state,
|
||||||
|
rowSelection,
|
||||||
|
};
|
||||||
|
tableOptions.getRowId = (row) => (row as { id: string }).id;
|
||||||
|
}
|
||||||
|
|
||||||
const table = useReactTable(tableOptions);
|
const table = useReactTable(tableOptions);
|
||||||
const { setPageSize } = table;
|
const { setPageSize } = table;
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const DateInput = ({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 flex items-center',
|
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all duration-200 flex items-center',
|
||||||
{
|
{
|
||||||
'border-error': isError,
|
'border-error': isError,
|
||||||
'border-success!': isValid,
|
'border-success!': isValid,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const FileInput = ({
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
'grow file-input w-full h-12 rounded-lg!',
|
'grow file-input w-full h-12 rounded',
|
||||||
className?.input
|
className?.input
|
||||||
)}
|
)}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
|||||||
@@ -1,415 +1,59 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import {
|
import { ChangeEvent, ReactNode } from "react";
|
||||||
ChangeEvent,
|
import { NumericFormat, OnValueChange } from "react-number-format";
|
||||||
ChangeEventHandler,
|
import TextInput, { TextInputProps } from "@/components/input/TextInput";
|
||||||
FocusEventHandler,
|
|
||||||
ReactNode,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
interface NumberInputProps extends Omit<TextInputProps, "type"> {
|
||||||
import Inputmask from 'inputmask';
|
|
||||||
|
|
||||||
const createInputMask = (
|
|
||||||
maskType: MaskType,
|
|
||||||
decimals: number,
|
|
||||||
thousandSeparator: string,
|
|
||||||
decimalSeparator: string,
|
|
||||||
allowNegative: boolean,
|
|
||||||
oncomplete?: () => void,
|
|
||||||
onincomplete?: () => void,
|
|
||||||
oncleared?: () => void
|
|
||||||
): Inputmask.Instance => {
|
|
||||||
const options: Inputmask.Options = {
|
|
||||||
alias: 'numeric',
|
|
||||||
groupSeparator: thousandSeparator,
|
|
||||||
radixPoint: decimalSeparator,
|
|
||||||
digits: decimals,
|
|
||||||
allowMinus: allowNegative,
|
|
||||||
rightAlign: false,
|
|
||||||
insertMode: true,
|
|
||||||
autoUnmask: false,
|
|
||||||
clearMaskOnLostFocus: false,
|
|
||||||
digitsOptional: decimals > 0,
|
|
||||||
placeholder: '0',
|
|
||||||
numericInput: false,
|
|
||||||
positionCaretOnClick: 'radixFocus',
|
|
||||||
greedy: true,
|
|
||||||
oncomplete,
|
|
||||||
onincomplete,
|
|
||||||
oncleared
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Inputmask(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MaskType = 'currency' | 'weight' | 'decimal' | 'number' | 'text';
|
|
||||||
|
|
||||||
export interface NumberInputProps {
|
|
||||||
label?: string;
|
|
||||||
bottomLabel?: string;
|
|
||||||
name: string;
|
|
||||||
value?: number | string;
|
|
||||||
placeholder?: string;
|
|
||||||
|
|
||||||
className?: {
|
|
||||||
wrapper?: string;
|
|
||||||
label?: string;
|
|
||||||
inputWrapper?: string;
|
|
||||||
input?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
isError?: boolean;
|
|
||||||
isValid?: boolean;
|
|
||||||
errorMessage?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
isLoading?: boolean;
|
|
||||||
|
|
||||||
startAdornment?: ReactNode;
|
|
||||||
endAdornment?: ReactNode;
|
|
||||||
|
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
||||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
|
||||||
onFocus?: FocusEventHandler<HTMLInputElement>;
|
|
||||||
|
|
||||||
maskType?: MaskType;
|
|
||||||
decimals?: number;
|
|
||||||
thousandSeparator?: string;
|
thousandSeparator?: string;
|
||||||
decimalSeparator?: string;
|
decimalSeparator?: string;
|
||||||
currencyPrefix?: string;
|
decimalScale?: number;
|
||||||
weightUnit?: string;
|
|
||||||
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
allowNegative?: boolean;
|
allowNegative?: boolean;
|
||||||
|
prefix?: string;
|
||||||
oncomplete?: () => void;
|
suffix?: string;
|
||||||
onincomplete?: () => void;
|
fixedDecimalScale?: boolean;
|
||||||
oncleared?: () => void;
|
inputPrefix?: ReactNode;
|
||||||
|
inputSuffix?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberInput = ({
|
const NumberInput = ({
|
||||||
label,
|
thousandSeparator = ",",
|
||||||
bottomLabel,
|
decimalSeparator = ".",
|
||||||
name,
|
decimalScale = 5,
|
||||||
value,
|
allowNegative = true,
|
||||||
placeholder,
|
|
||||||
className,
|
|
||||||
isError,
|
|
||||||
isValid,
|
|
||||||
errorMessage,
|
|
||||||
startAdornment,
|
|
||||||
endAdornment,
|
|
||||||
disabled = false,
|
|
||||||
required = false,
|
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
inputPrefix,
|
||||||
onFocus,
|
inputSuffix,
|
||||||
readOnly = false,
|
...restProps
|
||||||
isLoading = false,
|
}: NumberInputProps) => {
|
||||||
maskType = 'number',
|
const valueChangeHandler: OnValueChange = (
|
||||||
decimals = 0,
|
numberFormatValues,
|
||||||
thousandSeparator = ',',
|
sourceInfo,
|
||||||
decimalSeparator = '.',
|
) => {
|
||||||
currencyPrefix = 'Rp ',
|
const newChangeEvent = sourceInfo.event as
|
||||||
weightUnit = 'kg',
|
| ChangeEvent<HTMLInputElement>
|
||||||
allowNegative = false,
|
| undefined;
|
||||||
oncomplete,
|
|
||||||
onincomplete,
|
|
||||||
oncleared,
|
|
||||||
}: NumberInputProps) => {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const inputmaskRef = useRef<Inputmask.Instance | null>(null);
|
|
||||||
const [maskComplete, setMaskComplete] = useState<boolean>(false);
|
|
||||||
const [maskIncomplete, setMaskIncomplete] = useState<boolean>(false);
|
|
||||||
const [maskCleared, setMaskCleared] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const getInputPrefix = (): string => {
|
if (newChangeEvent) {
|
||||||
switch (maskType) {
|
newChangeEvent.target.value = numberFormatValues.value;
|
||||||
case 'currency':
|
|
||||||
return currencyPrefix;
|
onChange?.(newChangeEvent);
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInputSuffix = (): string => {
|
|
||||||
switch (maskType) {
|
|
||||||
case 'weight':
|
|
||||||
return weightUnit;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputRef.current && !readOnly && !disabled) {
|
|
||||||
if (inputmaskRef.current) {
|
|
||||||
try {
|
|
||||||
inputmaskRef.current.remove();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Error removing Inputmask:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleComplete = () => {
|
|
||||||
setMaskComplete(true);
|
|
||||||
setMaskIncomplete(false);
|
|
||||||
setMaskCleared(false);
|
|
||||||
if (oncomplete) oncomplete();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIncomplete = () => {
|
|
||||||
setMaskIncomplete(true);
|
|
||||||
setMaskComplete(false);
|
|
||||||
setMaskCleared(false);
|
|
||||||
if (onincomplete) onincomplete();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCleared = () => {
|
|
||||||
setMaskCleared(true);
|
|
||||||
setMaskComplete(false);
|
|
||||||
setMaskIncomplete(false);
|
|
||||||
if (oncleared) oncleared();
|
|
||||||
};
|
|
||||||
|
|
||||||
const im = createInputMask(
|
|
||||||
maskType,
|
|
||||||
decimals,
|
|
||||||
',',
|
|
||||||
'.',
|
|
||||||
allowNegative,
|
|
||||||
handleComplete,
|
|
||||||
handleIncomplete,
|
|
||||||
handleCleared
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
im.mask(inputRef.current);
|
|
||||||
inputmaskRef.current = im;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Error applying Inputmask:', error);
|
|
||||||
inputmaskRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (inputmaskRef.current) {
|
|
||||||
try {
|
|
||||||
inputmaskRef.current.remove();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Error removing Inputmask on cleanup:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [maskType, decimals, thousandSeparator, decimalSeparator, allowNegative, readOnly, disabled, oncomplete, onincomplete, oncleared]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputRef.current && value !== undefined) {
|
|
||||||
if (value === null || value === '') {
|
|
||||||
inputRef.current.value = '';
|
|
||||||
} else {
|
|
||||||
inputRef.current.value = String(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
const currentValue = (e.currentTarget as HTMLInputElement).value;
|
|
||||||
console.log('✅ After format:', currentValue);
|
|
||||||
|
|
||||||
if (onChange) {
|
|
||||||
const syntheticEvent = {
|
|
||||||
target: {
|
|
||||||
name,
|
|
||||||
value: currentValue,
|
|
||||||
},
|
|
||||||
} as ChangeEvent<HTMLInputElement>;
|
|
||||||
onChange(syntheticEvent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const inputPrefix = getInputPrefix();
|
|
||||||
const inputSuffix = getInputSuffix();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<NumericFormat
|
||||||
className={cn(
|
thousandSeparator={thousandSeparator}
|
||||||
'w-full flex flex-col gap-2 text-start',
|
decimalSeparator={decimalSeparator}
|
||||||
className?.wrapper
|
customInput={TextInput}
|
||||||
)}
|
onValueChange={valueChangeHandler}
|
||||||
>
|
decimalScale={decimalScale}
|
||||||
{label && (
|
allowNegative={allowNegative}
|
||||||
<label
|
startAdornment={inputPrefix}
|
||||||
htmlFor={name}
|
endAdornment={inputSuffix}
|
||||||
className={cn(
|
{...restProps}
|
||||||
'w-full text-sm font-normal leading-5',
|
|
||||||
{
|
|
||||||
'text-error': isError,
|
|
||||||
},
|
|
||||||
className?.label
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
{required && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
<span className='tooltip tooltip-error' data-tip='required'>
|
|
||||||
<span className='text-error'> *</span>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='relative flex'>
|
|
||||||
{inputPrefix && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'inline-flex items-center px-4 py-2 border border-r-0 rounded-l-md transition-all duration-200',
|
|
||||||
{
|
|
||||||
'bg-gray-100 border-gray-300': !disabled,
|
|
||||||
'bg-gray-50 border-gray-200': disabled,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'text-sm font-medium select-none whitespace-nowrap',
|
|
||||||
{
|
|
||||||
'text-gray-600': !disabled,
|
|
||||||
'text-gray-400': disabled,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{inputPrefix}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'input h-12 text-base font-normal leading-6 flex-1 rounded-lg! outline-none! transition-all duration-200 flex items-center bg-white',
|
|
||||||
{
|
|
||||||
'border-error': isError,
|
|
||||||
'border-success!': isValid,
|
|
||||||
'rounded-l-none!': inputPrefix,
|
|
||||||
'rounded-r-none!': inputSuffix,
|
|
||||||
'input-disabled': disabled,
|
|
||||||
'cursor-not-allowed': disabled,
|
|
||||||
'bg-gray-50': disabled,
|
|
||||||
},
|
|
||||||
className?.inputWrapper
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{startAdornment && startAdornment}
|
|
||||||
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
id={name}
|
|
||||||
name={name}
|
|
||||||
ref={inputRef}
|
|
||||||
placeholder={placeholder || '0'}
|
|
||||||
onKeyUp={handleKeyUp}
|
|
||||||
onFocus={onFocus}
|
|
||||||
onBlur={onBlur}
|
|
||||||
disabled={disabled}
|
|
||||||
className={cn(
|
|
||||||
'grow bg-transparent outline-none',
|
|
||||||
{
|
|
||||||
'cursor-not-allowed': disabled,
|
|
||||||
'text-gray-500': disabled,
|
|
||||||
},
|
|
||||||
className?.input
|
|
||||||
)}
|
|
||||||
readOnly={readOnly}
|
|
||||||
inputMode='text'
|
|
||||||
autoComplete='off'
|
|
||||||
spellCheck={false}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(isLoading || endAdornment) && (
|
|
||||||
<div className='flex flex-row gap-2'>
|
|
||||||
{isLoading && <span className='loading loading-spinner' />}
|
|
||||||
{endAdornment && endAdornment}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{inputSuffix && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'inline-flex items-center px-4 py-2 border border-l-0 rounded-r-md transition-all duration-200',
|
|
||||||
{
|
|
||||||
'bg-gray-100 border-gray-300': !disabled,
|
|
||||||
'bg-gray-50 border-gray-200': disabled,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'text-sm font-medium select-none whitespace-nowrap',
|
|
||||||
{
|
|
||||||
'text-gray-600': !disabled,
|
|
||||||
'text-gray-400': disabled,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{inputSuffix}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(maskType === 'text' || (oncomplete || onincomplete || oncleared)) && (
|
|
||||||
<div className='flex gap-2 text-xs'>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'px-2 py-1 rounded transition-all duration-200',
|
|
||||||
maskComplete
|
|
||||||
? 'bg-green-100 text-green-700 border border-green-200'
|
|
||||||
: 'bg-gray-50 text-gray-400 border border-gray-200'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Complete
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'px-2 py-1 rounded transition-all duration-200',
|
|
||||||
maskIncomplete
|
|
||||||
? 'bg-yellow-100 text-yellow-700 border border-yellow-200'
|
|
||||||
: 'bg-gray-50 text-gray-400 border border-gray-200'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Incomplete
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'px-2 py-1 rounded transition-all duration-200',
|
|
||||||
maskCleared
|
|
||||||
? 'bg-blue-100 text-blue-700 border border-blue-200'
|
|
||||||
: 'bg-gray-50 text-gray-400 border border-gray-200'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Cleared
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isError && bottomLabel && (
|
|
||||||
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
|
||||||
)}
|
|
||||||
{isError && errorMessage && (
|
|
||||||
<p className='w-full text-sm text-error'>{errorMessage}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NumberInput;
|
export default NumberInput;
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
classNames={{
|
classNames={{
|
||||||
control: ({ isFocused, isDisabled }) =>
|
control: ({ isFocused, isDisabled }) =>
|
||||||
cn(
|
cn(
|
||||||
'w-full min-h-12! rounded-lg! border bg-white transition-shadow cursor-pointer!',
|
'w-full min-h-12! rounded border bg-white transition-shadow cursor-pointer!',
|
||||||
{
|
{
|
||||||
'border-red-500! ring-2 ring-red-200': isError,
|
'border-red-500! ring-2 ring-red-200': isError,
|
||||||
'border-indigo-500 ring-2 ring-indigo-200': isFocused,
|
'border-indigo-500 ring-2 ring-indigo-200': isFocused,
|
||||||
@@ -176,7 +176,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
input: () => cn('text-gray-900'),
|
input: () => cn('text-gray-900'),
|
||||||
indicatorsContainer: () => cn('flex items-center gap-1 pr-2'),
|
indicatorsContainer: () => cn('flex items-center gap-1 pr-2'),
|
||||||
dropdownIndicator: ({ isFocused }) =>
|
dropdownIndicator: ({ isFocused }) =>
|
||||||
cn('p-1 rounded-md hover:bg-gray-100', {
|
cn('p-1 rounded hover:bg-gray-100', {
|
||||||
'text-gray-900': isFocused,
|
'text-gray-900': isFocused,
|
||||||
'text-gray-500': !isFocused,
|
'text-gray-500': !isFocused,
|
||||||
'text-error!': isError,
|
'text-error!': isError,
|
||||||
@@ -185,7 +185,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
cn('border border-gray-200 rounded-lg bg-white shadow-lg!'),
|
cn('border border-gray-200 rounded-lg bg-white shadow-lg!'),
|
||||||
menuList: () => cn('p-2! max-h-60 overflow-auto'),
|
menuList: () => cn('p-2! max-h-60 overflow-auto'),
|
||||||
option: ({ isFocused, isSelected }) =>
|
option: ({ isFocused, isSelected }) =>
|
||||||
cn('mt-1 px-3 py-2 rounded-md cursor-pointer!', {
|
cn('mt-1 px-3 py-2 rounded cursor-pointer!', {
|
||||||
'bg-indigo-600 text-white': isFocused,
|
'bg-indigo-600 text-white': isFocused,
|
||||||
'bg-blue-500!': isSelected,
|
'bg-blue-500!': isSelected,
|
||||||
'text-gray-700': !isFocused && !isSelected,
|
'text-gray-700': !isFocused && !isSelected,
|
||||||
@@ -193,7 +193,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
multiValue: ({ getValue, index }) => {
|
multiValue: ({ getValue, index }) => {
|
||||||
const selectedValues = getValue() as T[];
|
const selectedValues = getValue() as T[];
|
||||||
return cn(
|
return cn(
|
||||||
'bg-indigo-50 rounded-md py-0.5 pl-2 pr-1 flex items-center gap-1!',
|
'bg-indigo-50 rounded py-0.5 pl-2 pr-1 flex items-center gap-1!',
|
||||||
selectedValues[index]?.className
|
selectedValues[index]?.className
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const TextArea = ({
|
|||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
'input h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all bg-white',
|
'input h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all bg-white',
|
||||||
{
|
{
|
||||||
'border-error': isError,
|
'border-error': isError,
|
||||||
'border-success!': isValid,
|
'border-success!': isValid,
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ const TextInput = ({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 bg-white',
|
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all duration-200',
|
||||||
{
|
{
|
||||||
'border-error': isError,
|
'border-error': isError,
|
||||||
'border-success!': isValid,
|
'border-success!': isValid,
|
||||||
|
|||||||
@@ -1377,9 +1377,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
handleDeliveryCostChange(idx, e.target.value)
|
handleDeliveryCostChange(idx, e.target.value)
|
||||||
}
|
}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='currency'
|
|
||||||
decimals={0}
|
|
||||||
min={0}
|
|
||||||
{...isRepeaterInputError(
|
{...isRepeaterInputError(
|
||||||
'deliveries',
|
'deliveries',
|
||||||
'delivery_cost',
|
'delivery_cost',
|
||||||
@@ -1404,9 +1401,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='currency'
|
|
||||||
decimals={0}
|
|
||||||
min={0}
|
|
||||||
{...isRepeaterInputError(
|
{...isRepeaterInputError(
|
||||||
'deliveries',
|
'deliveries',
|
||||||
'delivery_cost_per_item',
|
'delivery_cost_per_item',
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
|||||||
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn, formatNumber } from '@/lib/helper';
|
||||||
import { ChickinApi, ProjectFlockApi } from '@/services/api/production';
|
import { ChickinApi } from '@/services/api/production';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { Chickin } from '@/types/api/production/chickin';
|
import { Chickin } from '@/types/api/production/chickin';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -57,13 +57,6 @@ const ChickinTable = () => {
|
|||||||
`${ChickinApi.basePath}${getTableFilterQueryString()}`,
|
`${ChickinApi.basePath}${getTableFilterQueryString()}`,
|
||||||
ChickinApi.getAllFetcher
|
ChickinApi.getAllFetcher
|
||||||
);
|
);
|
||||||
const {
|
|
||||||
data: projectFlocks,
|
|
||||||
isLoading: isLoadingProjectFlocks,
|
|
||||||
} = useSWR(
|
|
||||||
`${ProjectFlockApi.basePath}${getTableFilterQueryString()}`,
|
|
||||||
ProjectFlockApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const searchChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateFilter('search', event.target.value);
|
updateFilter('search', event.target.value);
|
||||||
@@ -131,6 +124,13 @@ const ChickinTable = () => {
|
|||||||
{
|
{
|
||||||
accessorFn: (row) => row.quantity,
|
accessorFn: (row) => row.quantity,
|
||||||
header: 'Jumlah Chickin',
|
header: 'Jumlah Chickin',
|
||||||
|
cell: (props) => {
|
||||||
|
if (props.row.original.quantity) {
|
||||||
|
return formatNumber(props.row.original.quantity);
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.chick_in_date,
|
accessorFn: (row) => row.chick_in_date,
|
||||||
@@ -287,7 +287,7 @@ const RowOptionsMenu = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
href={`/production/chickin/detail?projectFlockId=${props.row.original.id}`}
|
href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='primary'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
ChickinFormValues,
|
ChickinFormValues,
|
||||||
UpdateChickinFormSchema,
|
UpdateChickinFormSchema,
|
||||||
} from '@/components/pages/production/chickin/form/ChickinForm.schema';
|
} from '@/components/pages/production/chickin/form/ChickinForm.schema';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { use, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { ChickinApi } from '@/services/api/production';
|
import { ChickinApi } from '@/services/api/production';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
@@ -20,6 +20,7 @@ import toast from 'react-hot-toast';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import TextArea from '@/components/input/TextArea';
|
import TextArea from '@/components/input/TextArea';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
|
||||||
interface ChickinFormProps {
|
interface ChickinFormProps {
|
||||||
formType?: 'add' | 'detail' | 'edit';
|
formType?: 'add' | 'detail' | 'edit';
|
||||||
@@ -45,8 +46,11 @@ const ChickinForm = ({
|
|||||||
const formikInitialValue = useMemo<ChickinFormValues>(() => {
|
const formikInitialValue = useMemo<ChickinFormValues>(() => {
|
||||||
return {
|
return {
|
||||||
chick_in_date: formatDateForInput(initialValues?.chick_in_date) ?? '',
|
chick_in_date: formatDateForInput(initialValues?.chick_in_date) ?? '',
|
||||||
note: initialValues?.note ?? `Catatan Chickin ${initialValues?.project_flock_kandang?.project_flock.flock.name}`,
|
note: initialValues?.note ?? '',
|
||||||
quantity: initialValues?.quantity ?? 1,
|
quantity:
|
||||||
|
initialValues?.quantity ??
|
||||||
|
initialValues?.project_flock_kandang?.available_quantity ??
|
||||||
|
0,
|
||||||
};
|
};
|
||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
@@ -71,10 +75,7 @@ const ChickinForm = ({
|
|||||||
payload: UpdateChickinPayload,
|
payload: UpdateChickinPayload,
|
||||||
afterSubmit: (() => void) | undefined
|
afterSubmit: (() => void) | undefined
|
||||||
) => {
|
) => {
|
||||||
const res = await ChickinApi.update(
|
const res = await ChickinApi.update(payload.id, payload);
|
||||||
payload.project_flock_kandang_id as number,
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
if (isResponseError(res)) {
|
if (isResponseError(res)) {
|
||||||
setChickinFormErrorMessage(res.message);
|
setChickinFormErrorMessage(res.message);
|
||||||
return;
|
return;
|
||||||
@@ -95,7 +96,10 @@ const ChickinForm = ({
|
|||||||
// reset error message
|
// reset error message
|
||||||
setChickinFormErrorMessage('');
|
setChickinFormErrorMessage('');
|
||||||
|
|
||||||
if (initialValues?.project_flock_kandang?.id == undefined) {
|
if (
|
||||||
|
initialValues?.project_flock_kandang?.id == undefined ||
|
||||||
|
(formType == 'edit' && initialValues?.id == undefined)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +107,13 @@ const ChickinForm = ({
|
|||||||
const payload = {
|
const payload = {
|
||||||
chick_in_date: values.chick_in_date,
|
chick_in_date: values.chick_in_date,
|
||||||
project_flock_kandang_id: initialValues?.project_flock_kandang?.id,
|
project_flock_kandang_id: initialValues?.project_flock_kandang?.id,
|
||||||
|
note: values.note,
|
||||||
|
quantity: values.quantity,
|
||||||
|
id: initialValues.id ?? 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// cek type form yang disubmit
|
// cek type form yang disubmit
|
||||||
|
console.log(formType);
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
case 'add':
|
case 'add':
|
||||||
handleCreate(payload, afterSubmit);
|
handleCreate(payload, afterSubmit);
|
||||||
@@ -144,28 +152,34 @@ const ChickinForm = ({
|
|||||||
}
|
}
|
||||||
errorMessage={formik.errors.chick_in_date}
|
errorMessage={formik.errors.chick_in_date}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<NumberInput
|
||||||
value={formik.values.quantity}
|
value={formik.values.quantity}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
name='quantity'
|
name='quantity'
|
||||||
label='Jumlah Chickin'
|
label='Jumlah (Ekor)'
|
||||||
required
|
required
|
||||||
isError={formik.touched.quantity && Boolean(formik.errors.quantity)}
|
isError={
|
||||||
errorMessage={formik.errors.quantity}
|
(formik.touched.quantity && Boolean(formik.errors.quantity)) ||
|
||||||
type='number'
|
formik.values.quantity == 0
|
||||||
disabled
|
}
|
||||||
|
errorMessage={
|
||||||
|
formik.values.quantity == 0
|
||||||
|
? 'Masukan Persediaan Day Old Chick terlebih dahulu.'
|
||||||
|
: formik.errors.quantity
|
||||||
|
}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
<TextArea
|
<TextArea
|
||||||
required
|
required
|
||||||
label='Catatan'
|
label='Catatan'
|
||||||
name='note'
|
name='note'
|
||||||
|
placeholder='Masukan catatan chickin'
|
||||||
value={formik.values.note}
|
value={formik.values.note}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
isError={formik.touched.note && Boolean(formik.errors.note)}
|
isError={formik.touched.note && Boolean(formik.errors.note)}
|
||||||
errorMessage={formik.errors.note}
|
errorMessage={formik.errors.note}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
{initialValues?.project_flock_kandang?.id == undefined && (
|
{initialValues?.project_flock_kandang?.id == undefined && (
|
||||||
<p className='text-error'>Project Flock Kandang tidak ditemukan.</p>
|
<p className='text-error'>Project Flock Kandang tidak ditemukan.</p>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
@@ -16,13 +17,12 @@ import { ProjectFlockApi } from '@/services/api/production';
|
|||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
|
||||||
import { Icon } from '@iconify/react';
|
|
||||||
import {
|
import {
|
||||||
CellContext,
|
ProjectFlockApprovalPayload,
|
||||||
ColumnDef,
|
ProjectFlock,
|
||||||
SortingState,
|
} from '@/types/api/production/project-flock';
|
||||||
} from '@tanstack/react-table';
|
import { Icon } from '@iconify/react';
|
||||||
|
import { CellContext, SortingState } from '@tanstack/react-table';
|
||||||
import { ChangeEventHandler, useState } from 'react';
|
import { ChangeEventHandler, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
@@ -56,6 +56,7 @@ const RowOptionsMenu = ({
|
|||||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
{props.row.original.approval.step_name === 'Aktif' && (
|
||||||
<Button
|
<Button
|
||||||
href={`/production/chickin/add?projectFlockId=${props.row.original.id}`}
|
href={`/production/chickin/add?projectFlockId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -65,7 +66,9 @@ const RowOptionsMenu = ({
|
|||||||
<Icon icon='mdi:home-import-outline' width={16} height={16} />
|
<Icon icon='mdi:home-import-outline' width={16} height={16} />
|
||||||
Chickin
|
Chickin
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button
|
)}
|
||||||
|
{props.row.original.approval.step_name === 'Pengajuan' && (
|
||||||
|
<Button
|
||||||
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
|
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='warning'
|
color='warning'
|
||||||
@@ -73,7 +76,8 @@ const RowOptionsMenu = ({
|
|||||||
>
|
>
|
||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
Edit
|
Edit
|
||||||
</Button> */}
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={deleteClickHandler}
|
onClick={deleteClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -117,10 +121,15 @@ const ProjectFlockTable = () => {
|
|||||||
periodFilter: 'period',
|
periodFilter: 'period',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// State
|
||||||
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
|
const selectedRowIds = Object.keys(rowSelection)
|
||||||
|
.filter((id) => rowSelection[id])
|
||||||
|
.map((id) => parseInt(id));
|
||||||
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
||||||
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
||||||
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
|
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
|
||||||
|
|
||||||
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
|
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
|
||||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||||
null
|
null
|
||||||
@@ -129,6 +138,13 @@ const ProjectFlockTable = () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [periodInputValue, setPeriodInputValue] = useState<number | null>(null);
|
const [periodInputValue, setPeriodInputValue] = useState<number | null>(null);
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [selectedProjectFlock, setSelectedProjectFlock] =
|
||||||
|
useState<ProjectFlock>();
|
||||||
|
const deleteModal = useModal();
|
||||||
|
const confirmModal = useModal();
|
||||||
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
|
||||||
// Fetch Data
|
// Fetch Data
|
||||||
const {
|
const {
|
||||||
@@ -144,20 +160,20 @@ const ProjectFlockTable = () => {
|
|||||||
search: areaSelectInputValue,
|
search: areaSelectInputValue,
|
||||||
limit: '100',
|
limit: '100',
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
const {
|
const { data: areas, isLoading: isLoadingAreas } = useSWR(
|
||||||
data: areas,
|
areaUrl,
|
||||||
isLoading: isLoadingAreas,
|
AreaApi.getAllFetcher
|
||||||
} = useSWR(areaUrl, AreaApi.getAllFetcher);
|
);
|
||||||
|
|
||||||
const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
||||||
search: locationSelectInputValue,
|
search: locationSelectInputValue,
|
||||||
area_id: selectedArea != null ? selectedArea.value.toString() : '',
|
area_id: selectedArea != null ? selectedArea.value.toString() : '',
|
||||||
limit: '100',
|
limit: '100',
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
const {
|
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
||||||
data: locations,
|
locationUrl,
|
||||||
isLoading: isLoadingLocations,
|
LocationApi.getAllFetcher
|
||||||
} = useSWR(locationUrl, LocationApi.getAllFetcher);
|
);
|
||||||
|
|
||||||
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
||||||
search: kandangSelectInputValue,
|
search: kandangSelectInputValue,
|
||||||
@@ -165,10 +181,10 @@ const ProjectFlockTable = () => {
|
|||||||
selectedLocation != null ? selectedLocation.value.toString() : '',
|
selectedLocation != null ? selectedLocation.value.toString() : '',
|
||||||
limit: '100',
|
limit: '100',
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
const {
|
const { data: kandangs, isLoading: isLoadingKandang } = useSWR(
|
||||||
data: kandangs,
|
kandangUrl,
|
||||||
isLoading: isLoadingKandang,
|
KandangApi.getAllFetcher
|
||||||
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
);
|
||||||
|
|
||||||
// Data to Options Mapping
|
// Data to Options Mapping
|
||||||
const optionsArea = isResponseSuccess(areas)
|
const optionsArea = isResponseSuccess(areas)
|
||||||
@@ -190,139 +206,6 @@ const ProjectFlockTable = () => {
|
|||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// State
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
|
||||||
const [selectedProjectFlock, setSelectedProjectFlock] =
|
|
||||||
useState<ProjectFlock>();
|
|
||||||
const deleteModal = useModal();
|
|
||||||
const confirmModal = useModal();
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
|
||||||
const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]);
|
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
|
||||||
|
|
||||||
// Columns
|
|
||||||
const projectFlocksColumns: ColumnDef<ProjectFlock>[] = [
|
|
||||||
{
|
|
||||||
id: 'select',
|
|
||||||
header: () => {
|
|
||||||
const allSelected =
|
|
||||||
isResponseSuccess(projectFlocks) &&
|
|
||||||
projectFlocks.data.length > 0 &&
|
|
||||||
selectedIds.length === projectFlocks.data.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
className='checkbox checkbox-sm'
|
|
||||||
checked={allSelected}
|
|
||||||
onChange={(e) => handleSelectAll(e.target.checked)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cell: (props) => {
|
|
||||||
const id = props.row.original.id;
|
|
||||||
const isChecked = selectedIds.includes(id);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
className='checkbox checkbox-sm'
|
|
||||||
checked={isChecked}
|
|
||||||
onChange={(e) => handleSelectRow(id, e.target.checked)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: 'flock.name',
|
|
||||||
header: 'Flock',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'area.name',
|
|
||||||
header: 'Area',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'location.name',
|
|
||||||
header: 'Lokasi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'fcr.name',
|
|
||||||
header: 'FCR',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'category',
|
|
||||||
header: 'Kategori',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Kandang',
|
|
||||||
cell: (props) => {
|
|
||||||
const kandang = props.row.original.kandangs;
|
|
||||||
if (kandang) {
|
|
||||||
const kandangNames = kandang.map((k: Kandang) => k.name);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'period',
|
|
||||||
header: 'Periode',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'created_at',
|
|
||||||
header: 'Dibuat pada',
|
|
||||||
cell: (props) =>
|
|
||||||
new Date(props.row.original.created_at).toLocaleDateString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 deleteClickHandler = () => {
|
|
||||||
setSelectedProjectFlock(props.row.original);
|
|
||||||
deleteModal.openModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{currentPageSize > 2 && (
|
|
||||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
|
||||||
<RowOptionsMenu
|
|
||||||
type='dropdown'
|
|
||||||
props={props}
|
|
||||||
deleteClickHandler={deleteClickHandler}
|
|
||||||
/>
|
|
||||||
</RowDropdownOptions>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentPageSize <= 2 && (
|
|
||||||
<RowCollapseOptions>
|
|
||||||
<RowOptionsMenu
|
|
||||||
type='dropdown'
|
|
||||||
props={props}
|
|
||||||
deleteClickHandler={deleteClickHandler}
|
|
||||||
/>
|
|
||||||
</RowCollapseOptions>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Handler
|
// Handler
|
||||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
const newVal = val as OptionType;
|
const newVal = val as OptionType;
|
||||||
@@ -341,45 +224,17 @@ const ProjectFlockTable = () => {
|
|||||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
updateFilter('search', e.target.value);
|
updateFilter('search', e.target.value);
|
||||||
};
|
};
|
||||||
const handleSelectAll = (checked: boolean) => {
|
|
||||||
if (checked && isResponseSuccess(projectFlocks)) {
|
|
||||||
const allIds = projectFlocks.data.map((item) => item.id);
|
|
||||||
setSelectedIds(allIds);
|
|
||||||
setSelectedFlocks(projectFlocks.data);
|
|
||||||
} else {
|
|
||||||
setSelectedIds([]);
|
|
||||||
setSelectedFlocks([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectRow = (id: number, checked: boolean) => {
|
|
||||||
if (!isResponseSuccess(projectFlocks)) return;
|
|
||||||
|
|
||||||
const targetFlock = projectFlocks.data.find((item) => item.id === id);
|
|
||||||
|
|
||||||
if (!targetFlock) return;
|
|
||||||
|
|
||||||
if (checked) {
|
|
||||||
setSelectedIds((prev) => [...prev, id]);
|
|
||||||
setSelectedFlocks((prev) => [...(prev || []), targetFlock]);
|
|
||||||
} else {
|
|
||||||
setSelectedIds((prev) => prev.filter((val) => val !== id));
|
|
||||||
setSelectedFlocks((prev) =>
|
|
||||||
(prev || []).filter((flock) => flock.id !== id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmationModalApproveClickHandler = async () => {
|
const confirmationModalApproveClickHandler = async () => {
|
||||||
setIsApproveLoading(true);
|
setIsApproveLoading(true);
|
||||||
const approveProjectFlockRes = await ProjectFlockApi.customRequest<
|
const approveProjectFlockRes = await ProjectFlockApi.customRequest<
|
||||||
BaseApiResponse<ProjectFlock>,
|
BaseApiResponse<ProjectFlock>,
|
||||||
'POST'
|
ProjectFlockApprovalPayload
|
||||||
>(`/approve`, {
|
>(`/approvals`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
payload: 'POST',
|
payload: {
|
||||||
params: {
|
action: 'APPROVED',
|
||||||
ids: selectedFlocks.map((flock) => flock.id).join(','),
|
approvable_ids: selectedRowIds.map((id) => id),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -391,6 +246,8 @@ const ProjectFlockTable = () => {
|
|||||||
toast.error(approveProjectFlockRes?.message as string);
|
toast.error(approveProjectFlockRes?.message as string);
|
||||||
confirmModal.closeModal();
|
confirmModal.closeModal();
|
||||||
}
|
}
|
||||||
|
setRowSelection({});
|
||||||
|
refreshProjectFlocks();
|
||||||
setIsApproveLoading(false);
|
setIsApproveLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -412,11 +269,9 @@ const ProjectFlockTable = () => {
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
color='success'
|
color='success'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedIds.length > 0) {
|
|
||||||
confirmModal.openModal();
|
confirmModal.openModal();
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!(selectedIds.length > 0)}
|
disabled={selectedRowIds.length === 0}
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
@@ -508,7 +363,162 @@ const ProjectFlockTable = () => {
|
|||||||
|
|
||||||
<Table<ProjectFlock>
|
<Table<ProjectFlock>
|
||||||
data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []}
|
data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []}
|
||||||
columns={projectFlocksColumns}
|
columns={[
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => {
|
||||||
|
const allRows = table.getRowModel().rows;
|
||||||
|
const selectableRows = allRows.filter(
|
||||||
|
(row) => row.original?.approval?.step_number == 1
|
||||||
|
);
|
||||||
|
|
||||||
|
const allSelected = selectableRows.every((row) =>
|
||||||
|
row.getIsSelected()
|
||||||
|
) && selectableRows.length != 0;
|
||||||
|
|
||||||
|
const someSelected =
|
||||||
|
selectableRows.some((row) => row.getIsSelected()) &&
|
||||||
|
!allSelected;
|
||||||
|
|
||||||
|
const toggleSelectableRows = () => {
|
||||||
|
const shouldSelect = !allSelected;
|
||||||
|
selectableRows.forEach((row) =>
|
||||||
|
row.toggleSelected(shouldSelect)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center'>
|
||||||
|
<CheckboxInput
|
||||||
|
name='allRow'
|
||||||
|
checked={allSelected}
|
||||||
|
indeterminate={someSelected}
|
||||||
|
onChange={toggleSelectableRows}
|
||||||
|
disabled={
|
||||||
|
isResponseSuccess(projectFlocks) &&
|
||||||
|
projectFlocks?.data?.filter(
|
||||||
|
(flock) => flock.approval.step_number == 1
|
||||||
|
).length == 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<CheckboxInput
|
||||||
|
name='row'
|
||||||
|
checked={
|
||||||
|
row.getIsSelected() &&
|
||||||
|
row.original.approval.step_number == 1
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
!row.getCanSelect() ||
|
||||||
|
row.original.approval.step_number != 1
|
||||||
|
}
|
||||||
|
indeterminate={row.getIsSomeSelected()}
|
||||||
|
onChange={row.getToggleSelectedHandler()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: 'flock.name',
|
||||||
|
header: 'Flock',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'area.name',
|
||||||
|
header: 'Area',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'location.name',
|
||||||
|
header: 'Lokasi',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'fcr.name',
|
||||||
|
header: 'FCR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'category',
|
||||||
|
header: 'Kategori',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'approval.step_name',
|
||||||
|
header: 'Status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Kandang',
|
||||||
|
cell: (props) => {
|
||||||
|
const kandang = props.row.original.kandangs;
|
||||||
|
if (kandang) {
|
||||||
|
const kandangNames = kandang.map((k: Kandang) => k.name);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{kandangNames.length > 0
|
||||||
|
? kandangNames.join(', ')
|
||||||
|
: 'Tidak ada'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'period',
|
||||||
|
header: 'Periode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Dibuat pada',
|
||||||
|
cell: (props) =>
|
||||||
|
new Date(props.row.original.created_at).toLocaleDateString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 deleteClickHandler = () => {
|
||||||
|
setSelectedProjectFlock(props.row.original);
|
||||||
|
deleteModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{currentPageSize > 2 && (
|
||||||
|
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||||
|
<RowOptionsMenu
|
||||||
|
type='dropdown'
|
||||||
|
props={props}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
/>
|
||||||
|
</RowDropdownOptions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentPageSize <= 2 && (
|
||||||
|
<RowCollapseOptions>
|
||||||
|
<RowOptionsMenu
|
||||||
|
type='dropdown'
|
||||||
|
props={props}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
/>
|
||||||
|
</RowCollapseOptions>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
pageSize={tableFilterState.pageSize}
|
pageSize={tableFilterState.pageSize}
|
||||||
page={
|
page={
|
||||||
isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0
|
isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0
|
||||||
@@ -522,6 +532,8 @@ const ProjectFlockTable = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
setRowSelection={setRowSelection}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn({
|
containerClassName: cn({
|
||||||
'mb-20':
|
'mb-20':
|
||||||
@@ -559,18 +571,19 @@ const ProjectFlockTable = () => {
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={confirmModal.ref}
|
ref={confirmModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
text={
|
text={`Apakah anda yakin ingin reject data transfer ke laying ini (${selectedRowIds.length} data)?`}
|
||||||
selectedFlocks.length > 0
|
// text={
|
||||||
? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks
|
// selectedFlocks.length > 0
|
||||||
.map(
|
// ? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks
|
||||||
(flock) =>
|
// .map(
|
||||||
`${flock.flock?.name ?? '(Tanpa nama)'} - ${
|
// (flock) =>
|
||||||
flock.area?.name ?? '-'
|
// `${flock.flock?.name ?? '(Tanpa nama)'} - ${
|
||||||
}`
|
// flock.area?.name ?? '-'
|
||||||
)
|
// }`
|
||||||
.join(', ')})`
|
// )
|
||||||
: 'Tidak ada Project Flock yang dipilih.'
|
// .join(', ')})`
|
||||||
}
|
// : 'Tidak ada Project Flock yang dipilih.'
|
||||||
|
// }
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ import { Icon } from '@iconify/react';
|
|||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR, { KeyedMutator } from 'swr';
|
||||||
import {
|
import {
|
||||||
ProjectFlockFormSchema,
|
ProjectFlockFormSchema,
|
||||||
ProjectFlockFormValues,
|
ProjectFlockFormValues,
|
||||||
UpdateProjectFlockFormSchema,
|
UpdateProjectFlockFormSchema,
|
||||||
} from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema';
|
} from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema';
|
||||||
import {
|
import {
|
||||||
|
ProjectFlockApprovalPayload,
|
||||||
CreateProjectFlockPayload,
|
CreateProjectFlockPayload,
|
||||||
PeriodFlock,
|
PeriodFlock,
|
||||||
ProjectFlock,
|
ProjectFlock,
|
||||||
@@ -34,15 +35,20 @@ import { BaseApiResponse } from '@/types/api/api-general';
|
|||||||
import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
|
import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
import ProjectFlockKandangTable from './ProjectFlockKandangTable';
|
||||||
|
|
||||||
interface ProjectFlockFormProps {
|
interface ProjectFlockFormProps {
|
||||||
formType?: 'add' | 'edit' | 'detail';
|
formType?: 'add' | 'edit' | 'detail';
|
||||||
initialValues?: ProjectFlock;
|
initialValues?: ProjectFlock;
|
||||||
|
refreshProjectFlocks?: KeyedMutator<
|
||||||
|
BaseApiResponse<ProjectFlock> | undefined
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectFlockForm = ({
|
const ProjectFlockForm = ({
|
||||||
formType = 'add',
|
formType = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
|
refreshProjectFlocks,
|
||||||
}: ProjectFlockFormProps) => {
|
}: ProjectFlockFormProps) => {
|
||||||
// State
|
// State
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -70,6 +76,34 @@ const ProjectFlockForm = ({
|
|||||||
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
const [isApprovedDisabled, setIsApprovedDisabled] = useState(
|
||||||
|
initialValues?.approval.step_name == 'Pengajuan' ? false : true
|
||||||
|
);
|
||||||
|
const [isRejectedDisabled, setIsRejectedDisabled] = useState(
|
||||||
|
!isApprovedDisabled
|
||||||
|
);
|
||||||
|
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
|
||||||
|
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
|
||||||
|
);
|
||||||
|
|
||||||
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(
|
||||||
|
() =>
|
||||||
|
Object.fromEntries(
|
||||||
|
(initialValues?.kandangs ?? []).map((k: Kandang) => [
|
||||||
|
k.id.toString(),
|
||||||
|
true,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialValues?.approval?.step_name) {
|
||||||
|
const approvedDisabled = initialValues.approval.step_name !== 'Pengajuan';
|
||||||
|
setIsApprovedDisabled(approvedDisabled);
|
||||||
|
setIsRejectedDisabled(!approvedDisabled);
|
||||||
|
setApprovalAction(!approvedDisabled ? 'APPROVED' : 'REJECTED');
|
||||||
|
}
|
||||||
|
}, [initialValues]);
|
||||||
|
|
||||||
// Fetch Data
|
// Fetch Data
|
||||||
const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({
|
const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({
|
||||||
@@ -109,7 +143,7 @@ const ProjectFlockForm = ({
|
|||||||
search: '',
|
search: '',
|
||||||
location_id: selectedLocation == '' ? '0' : selectedLocation,
|
location_id: selectedLocation == '' ? '0' : selectedLocation,
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
const { data: kandang, isLoading: isLoadingKandang } = useSWR(
|
const { data: kandang, isLoading: isLoadingKandang, mutate: refreshKandang} = useSWR(
|
||||||
kandangUrl,
|
kandangUrl,
|
||||||
KandangApi.getAllFetcher
|
KandangApi.getAllFetcher
|
||||||
);
|
);
|
||||||
@@ -167,6 +201,20 @@ const ProjectFlockForm = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [kandang]);
|
}, [kandang]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialValues?.kandangs) {
|
||||||
|
refreshKandang();
|
||||||
|
setOpenSelectKandangs(true);
|
||||||
|
|
||||||
|
const newRowSelection = Object.fromEntries(
|
||||||
|
initialValues.kandangs.map((k: Kandang) => [
|
||||||
|
k.id.toString(),
|
||||||
|
true,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
setRowSelection(newRowSelection);
|
||||||
|
}
|
||||||
|
}, [initialValues, refreshKandang]);
|
||||||
|
|
||||||
// Options Handler
|
// Options Handler
|
||||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
@@ -211,38 +259,6 @@ const ProjectFlockForm = ({
|
|||||||
formik.setFieldTouched('category', true);
|
formik.setFieldTouched('category', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const kandangChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const { value, checked } = event.target;
|
|
||||||
if (checked) {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'kandang_ids',
|
|
||||||
formik.values.kandang_ids.concat(parseInt(value))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'kandang_ids',
|
|
||||||
formik.values.kandang_ids.filter((id) => id !== parseInt(value))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const kandangCheckAll = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const { checked } = event.target;
|
|
||||||
if (checked) {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'kandang_ids',
|
|
||||||
optionsKandang
|
|
||||||
.filter(
|
|
||||||
(kandang) =>
|
|
||||||
kandang.status === 'NON_ACTIVE' ||
|
|
||||||
formik.values.kandang_ids.includes(kandang.id)
|
|
||||||
)
|
|
||||||
.map((kandang) => kandang.id)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
formik.setFieldValue('kandang_ids', []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Submit Handler
|
// Submit Handler
|
||||||
const createProjectFlockHandler = async (
|
const createProjectFlockHandler = async (
|
||||||
payload: CreateProjectFlockPayload
|
payload: CreateProjectFlockPayload
|
||||||
@@ -400,11 +416,21 @@ const ProjectFlockForm = ({
|
|||||||
}, [formik.values]);
|
}, [formik.values]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(isResponseSuccess(periodFlocks)){
|
if (isResponseSuccess(periodFlocks)) {
|
||||||
formik.setFieldValue('period', periodFlocks.data.next_period);
|
formik.setFieldValue('period', periodFlocks.data.next_period);
|
||||||
}
|
}
|
||||||
}, [periodFlocks]);
|
}, [periodFlocks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const selectedRowIds = Object.keys(rowSelection)
|
||||||
|
.filter((id) => rowSelection[id])
|
||||||
|
.map((id) => parseInt(id));
|
||||||
|
formikSetValues({
|
||||||
|
...formik.values,
|
||||||
|
kandang_ids: selectedRowIds,
|
||||||
|
});
|
||||||
|
}, [rowSelection, formikSetValues]);
|
||||||
|
|
||||||
// Actions handler
|
// Actions handler
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
@@ -422,23 +448,42 @@ const ProjectFlockForm = ({
|
|||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmationModalApproveClickHandler = async () => {
|
const confirmationModalClickHandler = async ({
|
||||||
|
action = 'APPROVED',
|
||||||
|
}: {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
|
}) => {
|
||||||
|
if (initialValues?.id === undefined) return;
|
||||||
setIsApproveLoading(true);
|
setIsApproveLoading(true);
|
||||||
const approveProjectFlockRes = await ProjectFlockApi.customRequest<
|
const approveProjectFlockRes = await ProjectFlockApi.customRequest<
|
||||||
BaseApiResponse<ProjectFlock>,
|
BaseApiResponse<ProjectFlock>,
|
||||||
'POST'
|
ProjectFlockApprovalPayload
|
||||||
>(`/${initialValues?.id}/approve`, {
|
>(`/approvals`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
payload: {
|
||||||
|
action: action,
|
||||||
|
approvable_ids: [initialValues.id],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isResponseSuccess(approveProjectFlockRes)) {
|
if (isResponseSuccess(approveProjectFlockRes)) {
|
||||||
toast.success('Project Flock berhasil di-approve!');
|
if (refreshProjectFlocks) {
|
||||||
confirmModal.closeModal();
|
await refreshProjectFlocks();
|
||||||
|
}
|
||||||
|
// if (action == 'APPROVED') {
|
||||||
|
// setIsApprovedDisabled(true);
|
||||||
|
// setIsRejectedDisabled(false);
|
||||||
|
// }
|
||||||
|
// if (action == 'REJECTED') {
|
||||||
|
// setIsRejectedDisabled(true);
|
||||||
|
// setIsApprovedDisabled(false);
|
||||||
|
// }
|
||||||
|
toast.success(approveProjectFlockRes.message as string);
|
||||||
}
|
}
|
||||||
if (isResponseError(approveProjectFlockRes)) {
|
if (isResponseError(approveProjectFlockRes)) {
|
||||||
toast.error(approveProjectFlockRes?.message as string);
|
toast.error(approveProjectFlockRes?.message as string);
|
||||||
confirmModal.closeModal();
|
|
||||||
}
|
}
|
||||||
|
confirmModal.closeModal();
|
||||||
setIsApproveLoading(false);
|
setIsApproveLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -481,21 +526,37 @@ const ProjectFlockForm = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{formType == 'detail' && (
|
{formType == 'detail' && (
|
||||||
<div className='w-full py-4'>
|
<div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='success'
|
color='success'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (initialValues?.id) {
|
if (initialValues?.id) {
|
||||||
|
setApprovalAction('APPROVED');
|
||||||
confirmModal.openModal();
|
confirmModal.openModal();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!initialValues?.id}
|
disabled={!initialValues?.id || isApprovedDisabled}
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
Approve
|
Approve
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='error'
|
||||||
|
onClick={() => {
|
||||||
|
if (initialValues?.id) {
|
||||||
|
setApprovalAction('REJECTED');
|
||||||
|
confirmModal.openModal();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!initialValues?.id || isRejectedDisabled}
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:times' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<form
|
<form
|
||||||
@@ -505,9 +566,7 @@ const ProjectFlockForm = ({
|
|||||||
>
|
>
|
||||||
<div className='card bg-base-100 shadow w-full mb-6'>
|
<div className='card bg-base-100 shadow w-full mb-6'>
|
||||||
<div className='card-body'>
|
<div className='card-body'>
|
||||||
<div className='card-title mb-4'>
|
<div className='card-title mb-4'>Informasi Umum</div>
|
||||||
Informasi Umum
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='grid sm:grid-cols-2 gap-4'>
|
<div className='grid sm:grid-cols-2 gap-4'>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
@@ -614,7 +673,7 @@ const ProjectFlockForm = ({
|
|||||||
variant='link'
|
variant='link'
|
||||||
className={`text-primary rotate-${
|
className={`text-primary rotate-${
|
||||||
openSelectKandangs ? '180' : '0'
|
openSelectKandangs ? '180' : '0'
|
||||||
} transition-transform hover:text-inherit`}
|
} transition-transform hover:text-inherit me-3`}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:keyboard-arrow-down'
|
icon='material-symbols:keyboard-arrow-down'
|
||||||
@@ -631,102 +690,15 @@ const ProjectFlockForm = ({
|
|||||||
>
|
>
|
||||||
<div className='overflow-x-auto'>
|
<div className='overflow-x-auto'>
|
||||||
{isLoadingKandang && (
|
{isLoadingKandang && (
|
||||||
<span className="loading loading-dots loading-xl"></span>
|
<span className='loading loading-dots loading-xl'></span>
|
||||||
)}
|
)}
|
||||||
<table className='table'>
|
<ProjectFlockKandangTable
|
||||||
{/* head */}
|
listKandang={optionsKandang}
|
||||||
<thead>
|
rowSelection={rowSelection}
|
||||||
<tr>
|
setRowSelection={setRowSelection}
|
||||||
<th>
|
selectedIds={formik.values.kandang_ids}
|
||||||
<label>
|
formType={formType}
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
checked={
|
|
||||||
optionsKandang
|
|
||||||
.filter(
|
|
||||||
(k) =>
|
|
||||||
k.status === 'NON_ACTIVE' ||
|
|
||||||
formik.values.kandang_ids.includes(k.id)
|
|
||||||
)
|
|
||||||
.every((k) =>
|
|
||||||
formik.values.kandang_ids.includes(k.id)
|
|
||||||
) &&
|
|
||||||
optionsKandang.filter(
|
|
||||||
(k) =>
|
|
||||||
k.status === 'NON_ACTIVE' ||
|
|
||||||
formik.values.kandang_ids.includes(k.id)
|
|
||||||
).length > 0
|
|
||||||
}
|
|
||||||
className='checkbox transition-none'
|
|
||||||
disabled={
|
|
||||||
formType === 'detail' ||
|
|
||||||
optionsKandang.filter(
|
|
||||||
(k) => k.status === 'NON_ACTIVE'
|
|
||||||
).length == 0
|
|
||||||
}
|
|
||||||
onChange={
|
|
||||||
formType === 'detail'
|
|
||||||
? () => {}
|
|
||||||
: kandangCheckAll
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<th>Kandang</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Penanggung Jawab</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{/* rows */}
|
|
||||||
{selectedLocation != '' &&
|
|
||||||
optionsKandang.map((kandang) => (
|
|
||||||
<tr key={kandang.id}>
|
|
||||||
<th>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
value={kandang.id}
|
|
||||||
type='checkbox'
|
|
||||||
className='checkbox transition-none'
|
|
||||||
checked={formik.values.kandang_ids.includes(
|
|
||||||
kandang.id
|
|
||||||
)}
|
|
||||||
onChange={
|
|
||||||
formType === 'detail'
|
|
||||||
? () => {}
|
|
||||||
: kandangChangeHandler
|
|
||||||
}
|
|
||||||
disabled={
|
|
||||||
formType === 'detail' ||
|
|
||||||
kandang.status != 'NON_ACTIVE'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<td>{kandang.name}</td>
|
|
||||||
<td>{kandang.status}</td>
|
|
||||||
<td>{kandang.pic?.name}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
{selectedLocation == '' && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={3} className='text-center text-muted'>
|
|
||||||
Data tidak tersedia
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
{/* foot */}
|
|
||||||
{selectedLocation != '' && (
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Kandang</th>
|
|
||||||
<th>Penanggung Jawab</th>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
)}
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
@@ -795,16 +767,24 @@ const ProjectFlockForm = ({
|
|||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={confirmModal.ref}
|
ref={confirmModal.ref}
|
||||||
type='success'
|
type={approvalAction == 'APPROVED' ? 'success' : 'error'}
|
||||||
text={`Apakah anda yakin ingin approve Project Flock berikut? (${initialValues?.flock?.name} - ${initialValues?.area?.name})?`}
|
text={`Apakah anda yakin ingin ${
|
||||||
|
approvalAction == 'APPROVED' ? 'approve' : 'reject'
|
||||||
|
} Project Flock berikut? (${initialValues?.flock?.name} - ${
|
||||||
|
initialValues?.area?.name
|
||||||
|
})?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: 'Ya',
|
text: 'Ya',
|
||||||
color: 'success',
|
color: approvalAction == 'APPROVED' ? 'success' : 'error',
|
||||||
isLoading: isApproveLoading,
|
isLoading: isApproveLoading,
|
||||||
onClick: confirmationModalApproveClickHandler,
|
onClick: () => {
|
||||||
|
confirmationModalClickHandler({
|
||||||
|
action: approvalAction,
|
||||||
|
});
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
|
import PillBadge from '@/components/PillBadge';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import { cn } from '@/lib/helper';
|
||||||
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
|
import { OnChangeFn } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
const ProjectFlockKandangTable = ({
|
||||||
|
listKandang,
|
||||||
|
rowSelection,
|
||||||
|
setRowSelection,
|
||||||
|
selectedIds,
|
||||||
|
formType = 'add',
|
||||||
|
}: {
|
||||||
|
listKandang: Kandang[];
|
||||||
|
rowSelection: Record<string, boolean>;
|
||||||
|
setRowSelection: OnChangeFn<Record<string, boolean>>;
|
||||||
|
selectedIds: (number | undefined)[];
|
||||||
|
formType: 'add' | 'edit' | 'detail';
|
||||||
|
}) => {
|
||||||
|
console.log('selectedIds');
|
||||||
|
console.log(selectedIds);
|
||||||
|
return (
|
||||||
|
<Table<Kandang>
|
||||||
|
data={listKandang}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => {
|
||||||
|
const allRows = table.getRowModel().rows;
|
||||||
|
const selectableRows = allRows.filter(
|
||||||
|
(row) =>
|
||||||
|
row.original.status == 'NON_ACTIVE' ||
|
||||||
|
row.original.status == 'PENGAJUAN'
|
||||||
|
);
|
||||||
|
|
||||||
|
const allSelected =
|
||||||
|
selectableRows.every((row) => row.getIsSelected()) &&
|
||||||
|
selectableRows.length != 0 && formType != 'detail';
|
||||||
|
|
||||||
|
const someSelected =
|
||||||
|
selectableRows.some((row) => row.getIsSelected()) && !allSelected && formType != 'detail';
|
||||||
|
|
||||||
|
const toggleSelectableRows = () => {
|
||||||
|
const shouldSelect = !allSelected;
|
||||||
|
selectableRows.forEach((row) => row.toggleSelected(shouldSelect));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex flex-row justify-center'>
|
||||||
|
<CheckboxInput
|
||||||
|
name='allRow'
|
||||||
|
checked={allSelected}
|
||||||
|
indeterminate={someSelected}
|
||||||
|
onChange={toggleSelectableRows}
|
||||||
|
disabled={
|
||||||
|
listKandang.filter(
|
||||||
|
(kandang) =>
|
||||||
|
kandang.status == 'NON_ACTIVE' ||
|
||||||
|
kandang.status == 'PENGAJUAN'
|
||||||
|
).length == 0 || formType == 'detail'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<CheckboxInput
|
||||||
|
name='row'
|
||||||
|
checked={
|
||||||
|
(row.getIsSelected() &&
|
||||||
|
(row.original.status == 'NON_ACTIVE' ||
|
||||||
|
row.original.status == 'PENGAJUAN')) ||
|
||||||
|
selectedIds.includes(row.original.id)
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
!row.getCanSelect() ||
|
||||||
|
(row.original.status != 'NON_ACTIVE' &&
|
||||||
|
row.original.status != 'PENGAJUAN') ||
|
||||||
|
formType == 'detail'
|
||||||
|
}
|
||||||
|
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.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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectFlockKandangTable;
|
||||||
@@ -942,9 +942,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={feed.feed_stock}
|
value={feed.feed_stock}
|
||||||
onChange={handleFeedStockChangeWrapper(idx)}
|
onChange={handleFeedStockChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='number'
|
|
||||||
decimals={0}
|
|
||||||
min={0}
|
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator=''
|
decimalSeparator=''
|
||||||
isError={
|
isError={
|
||||||
@@ -1120,10 +1117,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={weight.chicken_weight}
|
value={weight.chicken_weight}
|
||||||
onChange={handleChickenWeightChangeWrapper(idx)}
|
onChange={handleChickenWeightChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='weight'
|
|
||||||
weightUnit='gram'
|
|
||||||
decimals={2}
|
|
||||||
min={0}
|
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
isError={
|
isError={
|
||||||
@@ -1153,9 +1146,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={weight.chicken_count}
|
value={weight.chicken_count}
|
||||||
onChange={handleChickenCountChangeWrapper(idx)}
|
onChange={handleChickenCountChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='number'
|
|
||||||
decimals={0}
|
|
||||||
min={0}
|
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
'body_weight',
|
'body_weight',
|
||||||
@@ -1184,10 +1174,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={weight.average_chicken_weight || ''}
|
value={weight.average_chicken_weight || ''}
|
||||||
onChange={handleAverageWeightChangeWrapper(idx)}
|
onChange={handleAverageWeightChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='weight'
|
|
||||||
weightUnit='gram'
|
|
||||||
decimals={2}
|
|
||||||
min={0}
|
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
isError={
|
isError={
|
||||||
@@ -1450,9 +1436,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={vaccine.used_stock}
|
value={vaccine.used_stock}
|
||||||
onChange={handleVaccinationStockChangeWrapper(idx)}
|
onChange={handleVaccinationStockChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='number'
|
|
||||||
decimals={0}
|
|
||||||
min={0}
|
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator=''
|
decimalSeparator=''
|
||||||
isError={
|
isError={
|
||||||
@@ -1660,9 +1643,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={mortality.count}
|
value={mortality.count}
|
||||||
onChange={handleMortalityCountChangeWrapper(idx)}
|
onChange={handleMortalityCountChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
maskType='number'
|
|
||||||
decimals={0}
|
|
||||||
min={0}
|
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator=''
|
decimalSeparator=''
|
||||||
isError={
|
isError={
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ export const cn = (...inputs: ClassValue[]) => {
|
|||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatNumber = (
|
||||||
|
value: number | bigint | Intl.StringNumericLiteral,
|
||||||
|
locale = 'en-US',
|
||||||
|
minimumFractionDigits = 0,
|
||||||
|
maximumFractionDigits = 2
|
||||||
|
) => {
|
||||||
|
return new Intl.NumberFormat(locale, {
|
||||||
|
minimumFractionDigits,
|
||||||
|
maximumFractionDigits,
|
||||||
|
}).format(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const formatCurrency = (
|
export const formatCurrency = (
|
||||||
value: number | bigint | Intl.StringNumericLiteral,
|
value: number | bigint | Intl.StringNumericLiteral,
|
||||||
currency = 'USD',
|
currency = 'USD',
|
||||||
|
|||||||
Vendored
+9
@@ -104,3 +104,12 @@ export type ApprovalsLine = {
|
|||||||
role?: string;
|
role?: string;
|
||||||
status: 'approved' | 'rejected' | 'waiting';
|
status: 'approved' | 'rejected' | 'waiting';
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
export type BaseApproval = {
|
||||||
|
step_number: number;
|
||||||
|
step_name: string;
|
||||||
|
action: string;
|
||||||
|
notes: string | null;
|
||||||
|
action_by: CreatedUser;
|
||||||
|
action_at: string;
|
||||||
|
};
|
||||||
|
|||||||
+11
-2
@@ -1,4 +1,4 @@
|
|||||||
import { BaseMetadata } from "@/types/api/api-general";
|
import { BaseApproval, BaseMetadata } from "@/types/api/api-general";
|
||||||
import { ProjectFlockKandang } from "@/types/api/production/project-flock-kandang";
|
import { ProjectFlockKandang } from "@/types/api/production/project-flock-kandang";
|
||||||
|
|
||||||
export type BaseChickin = {
|
export type BaseChickin = {
|
||||||
@@ -7,6 +7,7 @@ export type BaseChickin = {
|
|||||||
quantity?: number;
|
quantity?: number;
|
||||||
note?: string;
|
note?: string;
|
||||||
project_flock_kandang?: ProjectFlockKandang;
|
project_flock_kandang?: ProjectFlockKandang;
|
||||||
|
approval: BaseApproval;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Chickin = BaseMetadata & BaseChickin;
|
export type Chickin = BaseMetadata & BaseChickin;
|
||||||
@@ -14,7 +15,15 @@ export type Chickin = BaseMetadata & BaseChickin;
|
|||||||
export type CreateChickinPayload = {
|
export type CreateChickinPayload = {
|
||||||
project_flock_kandang_id: number;
|
project_flock_kandang_id: number;
|
||||||
chick_in_date: string;
|
chick_in_date: string;
|
||||||
|
note: string;
|
||||||
|
quantity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateChickinPayload = CreateChickinPayload;
|
export type UpdateChickinPayload = CreateChickinPayload & {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChickinApprovalPayload = {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
|
approvable_ids: number[];
|
||||||
|
};
|
||||||
@@ -7,6 +7,12 @@ export type BaseProjectFlockKandang = {
|
|||||||
kandang_id: number;
|
kandang_id: number;
|
||||||
kandang: Kandang;
|
kandang: Kandang;
|
||||||
project_flock: ProjectFlock;
|
project_flock: ProjectFlock;
|
||||||
|
available_quantity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectFlockKandang = BaseProjectFlockKandang;
|
export type ProjectFlockKandang = BaseProjectFlockKandang;
|
||||||
|
|
||||||
|
export type LookupProjectFlockKandangPayload = {
|
||||||
|
project_flock_id: number;
|
||||||
|
kandang_id: number;
|
||||||
|
}
|
||||||
+7
-1
@@ -3,7 +3,7 @@ import { Fcr } from '@/types/api/master-data/fcr';
|
|||||||
import { Flock } from '@/types/api/master-data/flock';
|
import { Flock } from '@/types/api/master-data/flock';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { Location } from '@/types/api/master-data/location';
|
import { Location } from '@/types/api/master-data/location';
|
||||||
import { BaseMetadata } from '@/types/api/api-general';
|
import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
|
||||||
|
|
||||||
export type BaseProjectFlock = {
|
export type BaseProjectFlock = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -21,6 +21,7 @@ export type BaseProjectFlock = {
|
|||||||
period: number;
|
period: number;
|
||||||
kandang_ids: number[];
|
kandang_ids: number[];
|
||||||
kandangs: Kandang[];
|
kandangs: Kandang[];
|
||||||
|
approval: BaseApproval;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PeriodFlock = {
|
export type PeriodFlock = {
|
||||||
@@ -41,3 +42,8 @@ export type CreateProjectFlockPayload = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateProjectFlockPayload = CreateProjectFlockPayload;
|
export type UpdateProjectFlockPayload = CreateProjectFlockPayload;
|
||||||
|
|
||||||
|
export type ProjectFlockApprovalPayload = {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
|
approvable_ids: number[];
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user