mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
feat(FE-92-94): Slicing UI detail chickin & refactor number input chickin form
This commit is contained in:
Generated
+11
@@ -18,6 +18,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",
|
||||||
@@ -5811,6 +5812,16 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-number-format": {
|
||||||
|
"version": "5.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz",
|
||||||
|
"integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-select": {
|
"node_modules/react-select": {
|
||||||
"version": "5.10.2",
|
"version": "5.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,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",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const AddChickin = () => {
|
|||||||
? 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}`,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
@@ -242,6 +242,7 @@ const AddChickin = () => {
|
|||||||
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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,24 +1,51 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
|
import Button from '@/components/Button';
|
||||||
import { ChickinApi } from "@/services/api/production";
|
import Card from '@/components/Card';
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
import useSWR from "swr";
|
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';
|
||||||
|
|
||||||
const DetailChickin = () => {
|
const DetailChickin = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const chickinId = searchParams.get('chickinId');
|
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 {
|
const {
|
||||||
data: chickin,
|
data: chickin,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useSWR(
|
mutate: refreshChickin,
|
||||||
chickinId,
|
} = useSWR(chickinId, (id: number) => ChickinApi.getSingle(id));
|
||||||
(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){
|
if (!chickinId) {
|
||||||
router.back();
|
router.back();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -28,46 +55,291 @@ const DetailChickin = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!isLoading && (!chickin || isResponseError(chickin))) {
|
||||||
!isLoading &&
|
|
||||||
(!chickin || isResponseError(chickin))
|
|
||||||
) {
|
|
||||||
router.replace('/404');
|
router.replace('/404');
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
setIsDeleteLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full p-4 flex flex-col justify-center gap-4">
|
<div className='w-full p-4 flex flex-col justify-center gap-4'>
|
||||||
{isLoading && <span className='loading loading-spinner loading-xl' />}
|
{isLoading && <span className='loading loading-spinner loading-xl' />}
|
||||||
{!isLoading && isResponseSuccess(chickin) && (
|
{!isLoading && isResponseSuccess(chickin) && (
|
||||||
<>
|
<>
|
||||||
<div className="card shadow">
|
{/* <div className='w-full flex flex-col sm:flex-row gap-2'>
|
||||||
<div className="card-body">
|
<Button
|
||||||
<div className="card-title">
|
variant='outline'
|
||||||
Informasi Project Flock
|
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>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className='font-semibold text-sm'>Area</div>
|
||||||
<div>
|
<div className='text-sm'>
|
||||||
<div className="font-semibold">Flock</div>
|
{
|
||||||
<div >{chickin.data.project_flock_kandang?.project_flock.flock.name}</div>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
<div className="card shadow">
|
<Card
|
||||||
<div className="card-body">
|
title='Detail Chickin'
|
||||||
<div className="card-title">
|
variant='bordered'
|
||||||
Informasi Chickin
|
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>
|
||||||
</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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</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;
|
export default DetailChickin;
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { HTMLAttributes, ReactNode } from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/helper";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export interface CardProps
|
||||||
|
extends Omit<HTMLAttributes<HTMLDivElement>, "className"> {
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
image?: string;
|
||||||
|
imageAlt?: string;
|
||||||
|
imageWidth?: number;
|
||||||
|
imageHeight?: number;
|
||||||
|
actions?: ReactNode;
|
||||||
|
footer?: ReactNode;
|
||||||
|
className?: {
|
||||||
|
wrapper?: string;
|
||||||
|
image?: string;
|
||||||
|
body?: string;
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
actions?: string;
|
||||||
|
footer?: string;
|
||||||
|
};
|
||||||
|
variant?: "default" | "compact" | "bordered" | "shadow" | "image-full";
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
}
|
||||||
|
|
||||||
|
const Card = ({
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
image,
|
||||||
|
imageAlt,
|
||||||
|
imageWidth,
|
||||||
|
imageHeight,
|
||||||
|
actions,
|
||||||
|
footer,
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
size = "md",
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: CardProps) => {
|
||||||
|
const getCardClasses = () => {
|
||||||
|
const baseClasses = "card bg-base-100";
|
||||||
|
|
||||||
|
const variantClasses = {
|
||||||
|
default: "",
|
||||||
|
compact: "card-compact",
|
||||||
|
bordered: "border border-base-300",
|
||||||
|
shadow: "shadow-xl",
|
||||||
|
"image-full": "card-side card-compact shadow-xl",
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: "w-64",
|
||||||
|
md: "w-96",
|
||||||
|
lg: "w-[28rem]",
|
||||||
|
};
|
||||||
|
|
||||||
|
return cn(
|
||||||
|
baseClasses,
|
||||||
|
variantClasses[variant],
|
||||||
|
variant !== "image-full" ? sizeClasses[size] : "",
|
||||||
|
className?.wrapper,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageDimensions = () => {
|
||||||
|
if (variant === "image-full") {
|
||||||
|
return {
|
||||||
|
width: imageWidth || 128,
|
||||||
|
height: imageHeight || 128,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardWidths = {
|
||||||
|
sm: 256, // w-64
|
||||||
|
md: 384, // w-96
|
||||||
|
lg: 448, // w-[28rem]
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: imageWidth || cardWidths[size],
|
||||||
|
height: imageHeight || 192,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageClasses = () => {
|
||||||
|
if (variant === "image-full") {
|
||||||
|
return cn("object-cover", className?.image);
|
||||||
|
}
|
||||||
|
return cn("w-full object-cover", className?.image);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBodyClasses = () => {
|
||||||
|
const baseClasses = "card-body";
|
||||||
|
|
||||||
|
if (variant === "compact" || variant === "image-full") {
|
||||||
|
return cn(baseClasses, "p-4", className?.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn(baseClasses, "p-6", className?.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTitleClasses = () => {
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: "text-lg",
|
||||||
|
md: "text-xl",
|
||||||
|
lg: "text-2xl",
|
||||||
|
};
|
||||||
|
|
||||||
|
return cn("card-title font-bold", sizeClasses[size], className?.title);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSubtitleClasses = () => {
|
||||||
|
return cn("text-base-content/70 text-sm mt-1", className?.subtitle);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActionsClasses = () => {
|
||||||
|
return cn("card-actions justify-end mt-4", className?.actions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFooterClasses = () => {
|
||||||
|
return cn("border-t border-base-300 mt-4 pt-4", className?.footer);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (variant === "image-full" && image) {
|
||||||
|
const imageDimensions = getImageDimensions();
|
||||||
|
return (
|
||||||
|
<div className={getCardClasses()} {...props}>
|
||||||
|
<figure>
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
alt={imageAlt || title || "Card image"}
|
||||||
|
width={imageDimensions.width}
|
||||||
|
height={imageDimensions.height}
|
||||||
|
className={getImageClasses()}
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
<div className={getBodyClasses()}>
|
||||||
|
{title && <h2 className={getTitleClasses()}>{title}</h2>}
|
||||||
|
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
|
||||||
|
{children}
|
||||||
|
{actions && <div className={getActionsClasses()}>{actions}</div>}
|
||||||
|
</div>
|
||||||
|
{footer && <div className={getFooterClasses()}>{footer}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={getCardClasses()} {...props}>
|
||||||
|
{image && (
|
||||||
|
<figure>
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
alt={imageAlt || title || "Card image"}
|
||||||
|
width={getImageDimensions().width}
|
||||||
|
height={getImageDimensions().height}
|
||||||
|
className={getImageClasses()}
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
)}
|
||||||
|
<div className={getBodyClasses()}>
|
||||||
|
{title && <h2 className={getTitleClasses()}>{title}</h2>}
|
||||||
|
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
|
||||||
|
{children}
|
||||||
|
{actions && <div className={getActionsClasses()}>{actions}</div>}
|
||||||
|
</div>
|
||||||
|
{footer && <div className={getFooterClasses()}>{footer}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Card;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ChangeEvent } from 'react';
|
||||||
|
import { NumericFormat, OnValueChange } from 'react-number-format';
|
||||||
|
import TextInput, { TextInputProps } from '@/components/input/TextInput';
|
||||||
|
|
||||||
|
interface NumberInputProps extends Omit<TextInputProps, 'type'> {
|
||||||
|
thousandSeparator?: string;
|
||||||
|
decimalSeparator?: string;
|
||||||
|
decimalScale?: number;
|
||||||
|
allowNegative?: boolean;
|
||||||
|
prefix?: string;
|
||||||
|
suffix?: string;
|
||||||
|
fixedDecimalScale?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NumberInput = ({
|
||||||
|
thousandSeparator = ',',
|
||||||
|
decimalSeparator = '.',
|
||||||
|
decimalScale = 5,
|
||||||
|
allowNegative = true,
|
||||||
|
onChange,
|
||||||
|
...restProps
|
||||||
|
}: NumberInputProps) => {
|
||||||
|
const valueChangeHandler: OnValueChange = (
|
||||||
|
numberFormatValues,
|
||||||
|
sourceInfo
|
||||||
|
) => {
|
||||||
|
const newChangeEvent = sourceInfo.event as
|
||||||
|
| ChangeEvent<HTMLInputElement>
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (newChangeEvent) {
|
||||||
|
newChangeEvent.target.value = numberFormatValues.value;
|
||||||
|
|
||||||
|
onChange?.(newChangeEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NumericFormat
|
||||||
|
thousandSeparator={thousandSeparator}
|
||||||
|
decimalSeparator={decimalSeparator}
|
||||||
|
customInput={TextInput}
|
||||||
|
onValueChange={valueChangeHandler}
|
||||||
|
decimalScale={decimalScale}
|
||||||
|
allowNegative={allowNegative}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NumberInput;
|
||||||
@@ -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';
|
||||||
@@ -124,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,
|
||||||
@@ -159,7 +166,7 @@ const ChickinTable = () => {
|
|||||||
deleteModal.openModal();
|
deleteModal.openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const editClickHandler = () => {
|
const editClickHandler = () => {
|
||||||
setSelectedChickin(props.row.original);
|
setSelectedChickin(props.row.original);
|
||||||
chickinModal.openModal();
|
chickinModal.openModal();
|
||||||
};
|
};
|
||||||
@@ -279,7 +286,7 @@ const RowOptionsMenu = ({
|
|||||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* <Button
|
<Button
|
||||||
href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
|
href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='primary'
|
color='primary'
|
||||||
@@ -287,7 +294,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>
|
||||||
<Button
|
<Button
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='warning'
|
color='warning'
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -46,7 +47,10 @@ const ChickinForm = ({
|
|||||||
return {
|
return {
|
||||||
chick_in_date: formatDateForInput(initialValues?.chick_in_date) ?? '',
|
chick_in_date: formatDateForInput(initialValues?.chick_in_date) ?? '',
|
||||||
note: initialValues?.note ?? '',
|
note: initialValues?.note ?? '',
|
||||||
quantity: initialValues?.quantity ?? initialValues?.project_flock_kandang?.available_quantity ?? 0,
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +109,11 @@ const ChickinForm = ({
|
|||||||
project_flock_kandang_id: initialValues?.project_flock_kandang?.id,
|
project_flock_kandang_id: initialValues?.project_flock_kandang?.id,
|
||||||
note: values.note,
|
note: values.note,
|
||||||
quantity: values.quantity,
|
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);
|
||||||
@@ -146,12 +152,12 @@ 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={
|
isError={
|
||||||
(formik.touched.quantity && Boolean(formik.errors.quantity)) ||
|
(formik.touched.quantity && Boolean(formik.errors.quantity)) ||
|
||||||
@@ -162,7 +168,6 @@ const ChickinForm = ({
|
|||||||
? 'Masukan Persediaan Day Old Chick terlebih dahulu.'
|
? 'Masukan Persediaan Day Old Chick terlebih dahulu.'
|
||||||
: formik.errors.quantity
|
: formik.errors.quantity
|
||||||
}
|
}
|
||||||
type='number'
|
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ const ProjectFlockTable = () => {
|
|||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const confirmModal = useModal();
|
const confirmModal = useModal();
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]);
|
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
|
||||||
// Fetch Data
|
// Fetch Data
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
+9
-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;
|
||||||
@@ -18,5 +19,11 @@ export type CreateChickinPayload = {
|
|||||||
quantity?: number;
|
quantity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateChickinPayload = CreateChickinPayload;
|
export type UpdateChickinPayload = CreateChickinPayload & {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChickinApprovalPayload = {
|
||||||
|
action: 'APPROVED' | 'REJECTED';
|
||||||
|
approvable_ids: number[];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user