refactor(FE-106-91-339-238): Slicing UI Chickin DOC Refactored

This commit is contained in:
randy-ar
2025-11-04 13:24:10 +07:00
parent 219cbedbcd
commit d8637923bd
24 changed files with 780 additions and 709 deletions
@@ -1,15 +0,0 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
export default function AddChickinKandang() {
const router = useRouter();
const searchParams = useSearchParams();
const kandangId = searchParams.get('kandangId');
return (
<div>
<h1>Tambah Chickin untuk Kandang ID: {kandangId}</h1>
</div>
);
}
-426
View File
@@ -1,426 +0,0 @@
'use client';
import Badge from '@/components/Badge';
import Button from '@/components/Button';
import Card from '@/components/Card';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import Modal, { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
import PillBadge from '@/components/PillBadge';
import Table from '@/components/Table';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
import { ProjectFlockApi } from '@/services/api/production';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { BaseApiResponse } from '@/types/api/api-general';
import { Kandang } from '@/types/api/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { Icon } from '@iconify/react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { is } from 'react-day-picker/locale';
import useSWR from 'swr';
const AddChickin = () => {
const router = useRouter();
const searchParams = useSearchParams();
const projectFlockId = searchParams.get('projectFlockId');
// Tables Props
const { state: tableFilterState } = useTableFilter({
initial: { search: '' },
paramMap: { page: 'page', pageSize: 'limit' },
});
// States
const [selectedKandang, setSelectedKandang] = useState<Kandang | undefined>(
undefined
);
const [projectFlockKandang, setProjectFlockKandang] =
useState<BaseApiResponse<ProjectFlockKandang>>();
const [isLoadingProjectFlockKandang, setIsLoadingProjectFlockKandang] =
useState(false);
const [searchProjectFlock, setSearchProjectFlock] = useState('');
const [selectedProjectFlock, setSelectedProjectFlock] =
useState<OptionType | null>(null);
// Fetch Data
const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
projectFlockId ?? selectedProjectFlock?.value.toString(),
(id: number) => ProjectFlockApi.getSingle(id)
);
const { data: listProjectFlock, isLoading: isLoadingListProjectFlock } =
useSWR(
`${ProjectFlockApi.basePath}?${new URLSearchParams({
search: searchProjectFlock,
}).toString()}`,
ProjectFlockApi.getAllFetcher
);
const getProjectFlockKandangUrl = `/kandangs/lookup`;
// Mapping Options
const options = isResponseSuccess(listProjectFlock)
? listProjectFlock?.data.map((projectFlock) => {
return {
value: projectFlock.id,
label: `${projectFlock?.flock?.name} - Periode ${projectFlock?.period}`,
};
})
: [];
const chickinModal = useModal();
const alertModal = useModal();
// Handle Function
const handleChickinClick = async (kandang: Kandang) => {
setIsLoadingProjectFlockKandang(true);
setSelectedKandang(kandang);
const ProjectFlockKandangRes = await ProjectFlockApi.customRequest<
BaseApiResponse<ProjectFlockKandang>,
'GET'
>(getProjectFlockKandangUrl, {
method: 'GET',
params: {
project_flock_id: projectFlockId ?? selectedProjectFlock?.value ?? 0,
kandang_id: kandang.id,
},
});
if (isResponseSuccess(ProjectFlockKandangRes)) {
setProjectFlockKandang(ProjectFlockKandangRes);
setIsLoadingProjectFlockKandang(false);
if (
ProjectFlockKandangRes.data.available_quantity &&
ProjectFlockKandangRes.data.available_quantity > 0
) {
chickinModal.openModal();
} else {
alertModal.openModal();
}
}
};
const handleAfterSubmit = () => {
chickinModal.closeModal();
router.push('/production/chickin');
};
const handleChangeProjectFlock = (val: OptionType | null) => {
setSelectedProjectFlock(val);
if (projectFlockId) {
router.push('/production/chickin/add');
}
};
return (
<>
<>
<section className='w-full p-4'>
<header className='flex flex-col gap-4'>
<div className='flex flex-row justify-between items-center'>
<Button
href='/production/project-flock'
variant='link'
className='w-fit p-0 text-primary'
>
<Icon icon='uil:arrow-left' width={24} height={24} />
Kembali
</Button>
<h1 className='text-2xl font-semibold text-center'>
Daftar Kandang Project Flock
</h1>
<div></div>
</div>
<div className='flex flex-col gap-4 w-full my-4'>
<div className='max-w-full sm:max-w-1/2 md:max-w-3/5 lg:max-w-2/5'>
<SelectInput
required
label='Ganti Project Flock'
placeholder='Pilih Project Flock'
options={options}
onInputChange={(val) => {
setSearchProjectFlock(val);
}}
isLoading={isLoadingListProjectFlock}
value={
isResponseSuccess(projectFlock)
? {
label: `${projectFlock.data?.flock?.name}`,
value: projectFlock.data?.id,
}
: undefined
}
onChange={(val) => {
handleChangeProjectFlock(val as OptionType);
}}
isSearchable
isClearable
startAdornment={
isResponseSuccess(projectFlock) && (
<Badge
variant='soft'
color='success'
size='sm'
className={{
badge: 'whitespace-nowrap font-semibold',
}}
>
Periode {projectFlock.data?.period}
</Badge>
)
}
/>
</div>
</div>
</header>
<Card
title='Informasi Flock'
className={{
wrapper: 'w-full bg-white mb-3',
}}
>
<Table<ProjectFlock>
emptyContent={
<div className='w-full p-5 text-center'>
{projectFlockId && isResponseError(projectFlock) ? (
<span className='text-lg opacity-50'>
{projectFlock.message}
</span>
) : (
<span className='text-lg opacity-50'>
Pilih project flock terlebih dahulu...
</span>
)}
</div>
}
data={isResponseSuccess(projectFlock) ? [projectFlock.data] : []}
columns={[
{
header: 'Area',
accessorKey: 'area.name',
},
{
header: 'Lokasi',
accessorKey: 'location.name',
},
{
header: 'Nama Flock',
accessorKey: 'flock.name',
},
{
header: 'Kategori',
accessorKey: 'category',
},
{
header: 'Status',
accessorKey: 'status',
cell: (props) => {
return props.row.original.approval.step_name ? (
<PillBadge
color={(() => {
switch (
props.row.original.approval.step_name.toUpperCase()
) {
case 'AKTIF':
return 'red';
case 'PENGAJUAN':
return 'green';
default:
return 'gray';
}
})()}
content={props.row.original.approval.step_name
.toLowerCase()
.replace(/_/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase())}
/>
) : (
'-'
);
},
},
{
header: 'Periode',
accessorKey: 'period',
},
{
header: 'FCR Layer',
accessorKey: 'fcr.name',
},
]}
page={undefined}
className={{
containerClassName: cn({
'mb-20':
isResponseSuccess(projectFlock) &&
projectFlock.data?.kandangs?.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',
}}
/>
</Card>
<Card
title='Daftar Chickin'
className={{
wrapper: 'w-full bg-white',
}}
>
<Table<Kandang>
emptyContent={
<div className='w-full p-5 text-center'>
{projectFlockId && isResponseError(projectFlock) ? (
<span className='text-lg opacity-50'>
{projectFlock.message}
</span>
) : (
<span className='text-lg opacity-50'>
Pilih project flock terlebih dahulu...
</span>
)}
</div>
}
data={
isResponseSuccess(projectFlock)
? projectFlock.data?.kandangs
: []
}
columns={[
{
header: '#',
cell: (props) =>
tableFilterState.pageSize * (tableFilterState.page - 1) +
props.row.index +
1,
},
{
accessorFn: () =>
isResponseSuccess(projectFlock)
? projectFlock.data.area.name
: '',
header: 'Area',
},
{
accessorFn: () =>
isResponseSuccess(projectFlock)
? projectFlock.data.location.name
: '',
header: 'Lokasi',
},
{
accessorKey: 'name',
header: 'Kandang',
},
{
accessorKey: 'capacity',
header: 'Kapasitas',
},
{
accessorKey: 'pic.name',
header: 'Penanggung Jawab',
},
{
header: 'Aksi',
cell: (props) => {
return (
<>
<Button
color='success'
variant='outline'
onClick={() => {
handleChickinClick(props.row.original);
}}
disabled={isLoadingProjectFlockKandang}
>
<Icon
icon='mdi:home-import-outline'
width={24}
height={24}
/>
Chickin
</Button>
</>
);
},
},
]}
page={undefined}
className={{
containerClassName: cn({
'mb-20':
isResponseSuccess(projectFlock) &&
projectFlock.data?.kandangs?.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',
}}
/>
</Card>
</section>
<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 - {selectedKandang?.name}
</h1>
<Button
color='error'
variant='link'
onClick={chickinModal.closeModal}
>
<Icon
className='text-black'
icon='uil:times'
width={24}
height={24}
/>
</Button>
</div>
{isResponseSuccess(projectFlockKandang) &&
isResponseSuccess(projectFlock) &&
!isLoadingProjectFlockKandang && (
<ChickinForm
initialValues={{
project_flock_kandang: projectFlockKandang.data,
created_user: projectFlock.data?.created_user,
created_at: projectFlock.data?.created_at,
updated_at: projectFlock.data?.updated_at,
approval: projectFlock.data?.approval,
}}
afterSubmit={handleAfterSubmit}
/>
)}
</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();
},
}}
/>
</>
</>
);
};
export default AddChickin;
@@ -0,0 +1,46 @@
'use client';
import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
import { isResponseSuccess } from '@/lib/api-helper';
import { ProjectFlockKandangApi } from '@/services/api/production';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
export default function AddChickinKandang() {
const searchParams = useSearchParams();
const projectFlockKandangId = searchParams.get('projectFlockKandangId');
const projectFlockId = searchParams.get('projectFlockId');
const router = useRouter();
const { data: projectFlockKandang, isLoading: isLoading } = useSWR(
projectFlockKandangId,
(id: number) => ProjectFlockKandangApi.getSingle(id)
);
if (!projectFlockKandangId) {
router.push(`/production/chickin/add?projectFlockId=${projectFlockId}`);
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoading && !projectFlockKandang) {
router.replace('/404');
return;
}
return (
<>
<section className='w-full p-4'>
{isLoading && <span className='loading loading-spinner loading-xl' />}
{!isLoading &&
isResponseSuccess(projectFlockKandang) &&
projectFlockId && (
<ChickinForm initialValues={projectFlockKandang.data} />
)}
</section>
</>
);
}
@@ -0,0 +1,24 @@
'use client';
import { FormHeader } from '@/components/helper/form/FormHeader';
import ProjectFlockChickinDetail from '@/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail';
import { useSearchParams } from 'next/navigation';
const AddChickin = () => {
const searchParams = useSearchParams();
const projectFlockId = searchParams.get('projectFlockId');
return (
<>
<section className='w-full p-4'>
<FormHeader
title='Daftar Kandang Project Flock'
backUrl='/production/project-flock'
/>
<ProjectFlockChickinDetail projectFlockId={Number(projectFlockId)} />
</section>
</>
);
};
export default AddChickin;
@@ -312,14 +312,6 @@ const DetailChickin = () => {
/>
</Button>
</div>
<ChickinForm
initialValues={chickin?.data}
formType='edit'
afterSubmit={() => {
refreshChickin();
chickinModal.closeModal();
}}
/>
</Modal>
<ConfirmationModal
+17 -4
View File
@@ -2,15 +2,27 @@ import Button from '@/components/Button';
import { Icon } from '@iconify/react';
interface FormHeaderProps {
type: 'add' | 'edit' | 'detail';
type?: 'add' | 'edit' | 'detail';
title: string;
backUrl: string;
backUrl?: string;
onBackClick?: () => void;
}
export const FormHeader = ({ type, title, backUrl }: FormHeaderProps) => {
export const FormHeader = ({
type,
title,
backUrl,
onBackClick,
}: FormHeaderProps) => {
return (
<header className='flex flex-col gap-4'>
<Button href={backUrl} variant='link' className='w-fit p-0 text-primary'>
<Button
type='button'
href={!onBackClick ? backUrl : undefined}
onClick={onBackClick}
variant='link'
className='w-fit p-0 text-primary'
>
<Icon icon='uil:arrow-left' width={24} height={24} />
Kembali
</Button>
@@ -18,6 +30,7 @@ export const FormHeader = ({ type, title, backUrl }: FormHeaderProps) => {
{type === 'add' && `Tambah ${title}`}
{type === 'edit' && `Edit ${title}`}
{type === 'detail' && `Detail ${title}`}
{!type && title}
</h1>
</header>
);
@@ -7,7 +7,8 @@ import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import { TableToolbar } from '@/components/table/TableToolbar';
import { ROWS_OPTIONS } from '@/config/constant';
import { cn } from '@/lib/helper';
import { SalesOrder } from '@/types/api/marketing/marketing';
import { Marketing, MarketingProducts } from '@/types/api/marketing/marketing';
import { Customer } from '@/types/api/master-data/customer';
import { Icon } from '@iconify/react';
import { CellContext } from '@tanstack/react-table';
import { useCallback, useState } from 'react';
@@ -18,7 +19,7 @@ const RowsOptionsMenu = ({
deleteClickHandler,
}: {
type: 'dropdown' | 'collapse';
props: CellContext<SalesOrder, unknown>;
props: CellContext<Marketing, unknown>;
deleteClickHandler: () => void;
}) => {
return (
@@ -111,7 +112,59 @@ const SalesOrderTable = () => {
/>
</div>
<Table
data={[]}
data={[
{
id: 1,
so_number: 'SO-001',
so_date: '2024-01-01',
so_docs: 'Dokumen SO 1.pdf',
customer: {
id: 1,
name: 'Customer A',
} as Customer,
sales_person: {
id: 1,
name: 'Sales Person A',
},
notes: 'Catatan untuk SO 1',
grand_total: 1000000,
approval: {
step_name: 'Approved',
},
marketing_products: [
{
id: 1,
qty: 10,
unit_price: 100000,
avg_weigth: 1.5,
total_weight: 15,
total_price: 1000000,
product_warehouse: {
id: 1,
product: {
id: 1,
name: 'Product A',
},
},
} as MarketingProducts,
{
id: 2,
qty: 5,
unit_price: 200000,
avg_weigth: 2.0,
total_weight: 10,
total_price: 1000000,
product_warehouse: {
id: 2,
product: {
id: 2,
name: 'Product B',
},
},
} as MarketingProducts,
],
} as Marketing,
]}
columns={[
{
header: '#',
@@ -122,7 +175,7 @@ const SalesOrderTable = () => {
header: 'No. Order',
},
{
accessorKey: 'tanggal',
accessorKey: 'so_date',
header: 'Tanggal',
},
{
@@ -130,7 +183,7 @@ const SalesOrderTable = () => {
header: 'Status',
},
{
accessorKey: 'customer',
accessorKey: 'customer.name',
header: 'Customer',
},
{
@@ -138,8 +191,18 @@ const SalesOrderTable = () => {
header: 'Grand Total',
},
{
accessorKey: 'product_details',
accessorKey: 'marketing_products.length',
header: 'Product Details',
cell: (props) => (
<ul className='list-disc list-inside'>
{props.row.original.marketing_products?.map((product) => (
<li key={product.id}>
{product.product_warehouse.product.name} - Qty:{' '}
{product.qty}
</li>
))}
</ul>
),
},
{
header: 'Aksi',
@@ -87,7 +87,7 @@ const ChickinTable = () => {
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<Button
href='/production/chickin/add?projectFlockId=1'
href='/production/project-flock/chickin/add?projectFlockId=1'
variant='outline'
color='primary'
className='w-full sm:w-fit'
@@ -260,14 +260,14 @@ const ChickinTable = () => {
/>
</Button>
</div>
<ChickinForm
{/* <ChickinForm
initialValues={selectedChickin}
formType='edit'
afterSubmit={() => {
refreshChickins();
chickinModal.closeModal();
}}
/>
/> */}
</Modal>
</>
);
@@ -287,7 +287,7 @@ const RowOptionsMenu = ({
return (
<RowOptionsMenuWrapper type={type}>
<Button
href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
href={`/production/project-flock/chickin/detail?chickinId=${props.row.original.id}`}
variant='ghost'
color='primary'
className='justify-start text-sm'
@@ -1,13 +1,3 @@
import * as Yup from 'yup';
export const ChickinFormSchema = Yup.object({
chick_in_date: Yup.string().required('Tanggal masuk wajib diisi!'),
note: Yup.string().required('Catatan wajib diisi!'),
quantity: Yup.number()
.min(1, 'Jumlah wajib diisi!')
.required('Jumlah wajib diisi!'),
});
export type ChickinFormValues = Yup.InferType<typeof ChickinFormSchema>;
export const UpdateChickinFormSchema = ChickinFormSchema;
import { Product } from '@/types/api/master-data/product';
import { Supplier } from '@/types/api/master-data/supplier';
@@ -1,220 +1,227 @@
'use client';
import Button from '@/components/Button';
import {
Chickin,
CreateChickinPayload,
UpdateChickinPayload,
} from '@/types/api/production/chickin';
import {
ChickinFormSchema,
ChickinFormValues,
UpdateChickinFormSchema,
} from '@/components/pages/production/chickin/form/ChickinForm.schema';
import { use, useCallback, useEffect, useMemo, useState } from 'react';
import { useFormik } from 'formik';
import { ChickinApi } from '@/services/api/production';
import Card from '@/components/Card';
import { FormHeader } from '@/components/helper/form/FormHeader';
import DateInput from '@/components/input/DateInput';
import { isResponseError } from '@/lib/api-helper';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
import TextArea from '@/components/input/TextArea';
import TextInput from '@/components/input/TextInput';
import FileInput from '@/components/input/FileInput';
import NumberInput from '@/components/input/NumberInput';
import SelectInput from '@/components/input/SelectInput';
import TextInput from '@/components/input/TextInput';
import Table from '@/components/Table';
import { formatNumber } from '@/lib/helper';
import { Kandang } from '@/types/api/master-data/kandang';
import {
AvailableQty,
ProjectFlockKandang,
} from '@/types/api/production/project-flock-kandang';
import { useRouter } from 'next/navigation';
interface ChickinFormProps {
formType?: 'add' | 'detail' | 'edit';
initialValues?: Chickin;
afterSubmit?: () => void;
}
const ChickinForm = ({
const ChickinFormKandang = ({
formType = 'add',
initialValues,
afterSubmit,
}: ChickinFormProps) => {
// Helper Function
const formatDateForInput = (dateString?: string): string => {
if (!dateString) return '';
return new Date(dateString).toISOString().split('T')[0];
};
// State
const [chickinFormErrorMessage, setChickinFormErrorMessage] = useState('');
// Initial Value
const formikInitialValue = useMemo<ChickinFormValues>(() => {
return {
chick_in_date: formatDateForInput(initialValues?.chick_in_date) ?? '',
note: initialValues?.note ?? '',
quantity:
initialValues?.quantity ??
initialValues?.project_flock_kandang?.available_quantity ??
0,
};
}, [initialValues]);
// Handle Submit Function
const handleCreate = useCallback(
async (
payload: CreateChickinPayload,
afterSubmit: (() => void) | undefined
) => {
const res = await ChickinApi.create(payload);
if (isResponseError(res)) {
setChickinFormErrorMessage(res.message);
return;
}
toast.success(res?.message as string);
afterSubmit?.();
},
[]
);
const handleUpdate = useCallback(
async (
payload: UpdateChickinPayload,
afterSubmit: (() => void) | undefined
) => {
const res = await ChickinApi.update(payload.id, payload);
if (isResponseError(res)) {
setChickinFormErrorMessage(res.message);
return;
}
toast.success(res?.message as string);
afterSubmit?.();
},
[]
);
// Formik
const formik = useFormik<ChickinFormValues>({
initialValues: formikInitialValue,
enableReinitialize: true,
validationSchema:
formType === 'edit' ? UpdateChickinFormSchema : ChickinFormSchema,
onSubmit: async (values) => {
// reset error message
setChickinFormErrorMessage('');
if (
initialValues?.project_flock_kandang?.id == undefined ||
(formType == 'edit' && initialValues?.id == undefined)
) {
return;
}
// create payload
const payload = {
chick_in_date: values.chick_in_date,
project_flock_kandang_id: initialValues?.project_flock_kandang?.id,
note: values.note,
quantity: values.quantity,
id: initialValues.id ?? 0,
};
// cek type form yang disubmit
console.log(formType);
switch (formType) {
case 'add':
handleCreate(payload, afterSubmit);
break;
case 'edit':
handleUpdate(payload, afterSubmit);
break;
default:
break;
}
},
});
// Initialize Formik
const { setValues: formikSetValues } = formik;
useEffect(() => {
formikSetValues(formikInitialValue);
}, [formikSetValues, formikInitialValue]);
}: {
formType?: 'add' | 'detail' | 'edit';
initialValues: ProjectFlockKandang;
afterSubmit?: () => void;
}) => {
const router = useRouter();
return (
<>
<form
className='min-h-48 flex flex-col gap-4'
onSubmit={formik.handleSubmit}
onReset={formik.handleReset}
<div className='flex flex-col gap-4'>
<FormHeader
type='add'
title='Chick In DOC'
backUrl={`/production/project-flock/chickin/add?projectFlockId=${initialValues.project_flock.id}`}
/>
<Card
title='Informasi Kandang'
className={{
wrapper: 'w-full bg-white mt-4',
}}
>
<DateInput
value={formik.values.chick_in_date}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
name='chick_in_date'
label='Tanggal Chickin'
required
isError={
formik.touched.chick_in_date && Boolean(formik.errors.chick_in_date)
<Table<Kandang>
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Informasi Kandang belum tersedia...
</span>
</div>
}
errorMessage={formik.errors.chick_in_date}
data={[initialValues.kandang]}
columns={[
{
header: 'Area',
accessorFn: () => initialValues.project_flock?.area.name || '-',
},
{
header: 'Lokasi',
accessorFn: () =>
initialValues.project_flock?.location.name || '-',
},
{
header: 'Flock',
accessorFn: () => initialValues.project_flock?.flock.name || '-',
},
{
header: 'Kandang',
accessorFn: (row) => row?.name || '-',
},
{
header: 'Kapasitas',
accessorFn: (row) =>
(row?.capacity && formatNumber(row?.capacity)) || '-',
},
{
header: 'Penanggung Jawab',
accessorFn: (row) => row?.pic?.name || '-',
},
]}
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-6 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
/>
<NumberInput
value={formik.values.quantity}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
name='quantity'
label='Jumlah (Ekor)'
required
isError={
(formik.touched.quantity && Boolean(formik.errors.quantity)) ||
formik.values.quantity == 0
</Card>
<Card
title='Informasi Chick In DOC'
className={{
wrapper: 'w-full bg-white',
}}
>
<Table<AvailableQty>
data={initialValues.available_qtys || []}
columns={[
{
accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chick In',
cell(props) {
return (
<DateInput
name='chick_in_date[]'
value={props.row.original.chick_in_date}
onChange={(e) => {
props.row.original.chick_in_date = e.target.value;
}}
/>
);
},
},
{
accessorFn: (row) => row.po_number,
header: 'No. Surat Jalan',
cell(props) {
return (
<TextInput
name='po_number[]'
value={props.row.original.po_number}
onChange={(e) => {
props.row.original.po_number = e.target.value;
}}
/>
);
},
},
{
header: 'Dokumen Surat Jalan',
cell(props) {
return (
<FileInput
name='document_path[]'
onChange={(e) => {
props.row.original.document_path = e.target.value;
}}
/>
);
},
},
{
accessorFn: (row) => row.supplier?.name,
header: 'Supplier',
cell(props) {
return (
<SelectInput
value={
props.row.original.supplier?.name &&
props.row.original.supplier?.id
? {
label: props.row.original.supplier.name,
value: props.row.original.supplier.id,
}
: undefined
}
options={[]}
isDisabled
/>
);
},
},
{
accessorFn: (row) => row.product_warehouse.product.name,
header: 'Produk',
cell(props) {
return (
<SelectInput
value={{
label: props.row.original.product_warehouse.product.name,
value: props.row.original.product_warehouse.product.id,
}}
options={[]}
isDisabled
/>
);
},
},
{
accessorFn: (row) => row.product_warehouse.quantity,
header: 'Jumlah (ekor)',
cell(props) {
return (
<NumberInput
name='qty[]'
value={props.row.original.product_warehouse.quantity}
/>
);
},
},
]}
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-2 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-2 py-2 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Isi persediaan DOC untuk kandang belum tersedia...
</span>
</div>
}
errorMessage={
formik.values.quantity == 0
? 'Masukan Persediaan Day Old Chick terlebih dahulu.'
: formik.errors.quantity
}
readOnly
/>
<TextArea
required
label='Catatan'
name='note'
placeholder='Masukan catatan chickin'
value={formik.values.note}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
isError={formik.touched.note && Boolean(formik.errors.note)}
errorMessage={formik.errors.note}
/>
{initialValues?.project_flock_kandang?.id == undefined && (
<p className='text-error'>Project Flock Kandang tidak ditemukan.</p>
)}
{chickinFormErrorMessage && (
<div
role='alert'
className='alert alert-error'
onClick={() => {
setChickinFormErrorMessage('');
}}
>
<Icon icon='mdi:times' />
<span>{chickinFormErrorMessage}</span>
</div>
)}
<div className='flex justify-center mt-auto gap-2'>
<Button color='warning' type='reset'>
Reset
</Button>
<Button
type='submit'
isLoading={formik.isSubmitting}
disabled={
!formik.isValid ||
formik.isSubmitting ||
!initialValues?.project_flock_kandang?.id
}
>
Submit
</Button>
</div>
</form>
</>
</Card>
<div className='flex flex-row justify-center gap-3'>
<Button type='reset' color='warning'>
Reset
</Button>
<Button type='submit' color='primary'>
Submit
</Button>
</div>
</div>
);
};
export default ChickinForm;
export default ChickinFormKandang;
@@ -9,7 +9,6 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
@@ -60,7 +59,7 @@ const RowOptionsMenu = ({
</Button>
{props.row.original.approval.step_name === 'Aktif' && (
<Button
href={`/production/chickin/add?projectFlockId=${props.row.original.id}`}
href={`/production/project-flock/chickin/add?projectFlockId=${props.row.original.id}`}
variant='ghost'
color='success'
className='justify-start text-sm'
@@ -84,13 +83,12 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
color='error'
className='text-error hover:text-inherit'
className='text-error hover:text-inherit justify-start text-sm'
>
<Icon
icon='material-symbols:delete-outline-rounded'
width={16}
height={16}
className='justify-start text-sm'
/>
Delete
</Button>
@@ -499,7 +497,7 @@ const ProjectFlockTable = () => {
return (
<>
{currentPageSize > 1 && (
{currentPageSize > 2 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu
type='dropdown'
@@ -509,7 +507,7 @@ const ProjectFlockTable = () => {
</RowDropdownOptions>
)}
{currentPageSize <= 1 && (
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='collapse'
@@ -576,18 +574,6 @@ const ProjectFlockTable = () => {
ref={confirmModal.ref}
type='success'
text={`Apakah anda yakin ingin reject data transfer ke laying ini (${selectedRowIds.length} data)?`}
// text={
// selectedFlocks.length > 0
// ? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks
// .map(
// (flock) =>
// `${flock.flock?.name ?? '(Tanpa nama)'} - ${
// flock.area?.name ?? '-'
// }`
// )
// .join(', ')})`
// : 'Tidak ada Project Flock yang dipilih.'
// }
secondaryButton={{
text: 'Tidak',
}}
@@ -0,0 +1,308 @@
'use client';
import Badge from '@/components/Badge';
import Button from '@/components/Button';
import Card from '@/components/Card';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import PillBadge from '@/components/PillBadge';
import Table from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
import { ProjectFlockApi } from '@/services/api/production';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Kandang } from '@/types/api/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import useSWR from 'swr';
const ProjectFlockChickinDetail = ({
projectFlockId,
}: {
projectFlockId: number | undefined;
}) => {
const router = useRouter();
// Tables Props
const { state: tableFilterState } = useTableFilter({
initial: { search: '' },
paramMap: { page: 'page', pageSize: 'limit' },
});
// States
const [searchProjectFlock, setSearchProjectFlock] = useState('');
const [selectedProjectFlock, setSelectedProjectFlock] =
useState<OptionType | null>(null);
const [projectFlock, setProjectFlock] = useState<ProjectFlock>();
// Fetch Data
const { data: listProjectFlock, isLoading: isLoadingListProjectFlock } =
useSWR(
`${ProjectFlockApi.basePath}?${new URLSearchParams({
search: searchProjectFlock,
}).toString()}`,
ProjectFlockApi.getAllFetcher
);
// Mapping Options
const options = isResponseSuccess(listProjectFlock)
? listProjectFlock?.data.map((projectFlock) => {
return {
value: projectFlock.id,
label: `${projectFlock?.flock?.name} - Periode ${projectFlock?.period}`,
};
})
: [];
// Handle Function
const handleChickinClick = async (kandang: Kandang) => {
router.push(
`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${kandang.project_flock_kandang_id}&projectFlockId=${projectFlockId ?? selectedProjectFlock?.value}`
);
};
const handleChangeProjectFlock = (val: OptionType | null) => {
setSelectedProjectFlock(val);
if (isResponseSuccess(listProjectFlock) && val) {
const selected = listProjectFlock.data.find(
(pf) => pf.id === Number(val.value)
);
setProjectFlock(selected);
} else {
setProjectFlock(undefined);
}
if (projectFlockId) {
router.push('/production/project-flock/chickin/add');
}
if (!val && projectFlockId) {
router.push('/production/project-flock/chickin/add');
}
};
useEffect(() => {
if (projectFlockId && isResponseSuccess(listProjectFlock)) {
setProjectFlock(
listProjectFlock.data.find((pf) => pf.id === Number(projectFlockId))
);
}
}, [projectFlockId, listProjectFlock]);
return (
<>
<div className='flex flex-col gap-4 w-full my-4'>
<div className='max-w-full sm:max-w-1/2 md:max-w-3/5 lg:max-w-2/5'>
<SelectInput
required
label='Ganti Project Flock'
placeholder='Pilih Project Flock'
options={options}
onInputChange={(val) => {
setSearchProjectFlock(val);
}}
isLoading={isLoadingListProjectFlock}
value={
projectFlock
? {
label: `${projectFlock?.flock?.name}`,
value: projectFlock?.id,
}
: null
}
onChange={(val) => {
handleChangeProjectFlock(val as OptionType | null);
}}
isSearchable
isClearable
startAdornment={
projectFlock && (
<Badge
variant='soft'
color='success'
size='sm'
className={{
badge: 'whitespace-nowrap font-semibold',
}}
>
Periode {projectFlock?.period}
</Badge>
)
}
/>
</div>
</div>
<Card
title='Informasi Flock'
className={{
wrapper: 'w-full bg-white mb-3',
}}
>
<Table<ProjectFlock>
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Pilih project flock terlebih dahulu...
</span>
</div>
}
data={projectFlock ? [projectFlock] : []}
columns={[
{
header: 'Area',
accessorKey: 'area.name',
},
{
header: 'Lokasi',
accessorKey: 'location.name',
},
{
header: 'Nama Flock',
accessorKey: 'flock.name',
},
{
header: 'Kategori',
accessorKey: 'category',
},
{
header: 'Status',
accessorKey: 'status',
cell: (props) => {
return props.row.original.approval?.step_name ? (
<PillBadge
color={(() => {
switch (
props.row.original.approval?.step_name.toUpperCase()
) {
case 'AKTIF':
return 'red';
case 'PENGAJUAN':
return 'green';
default:
return 'gray';
}
})()}
content={props.row.original.approval?.step_name
.toLowerCase()
.replace(/_/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase())}
/>
) : (
'-'
);
},
},
{
header: 'Periode',
accessorKey: 'period',
},
{
header: 'FCR Layer',
accessorKey: 'fcr.name',
},
]}
page={undefined}
className={{
containerClassName: cn({
'mb-20': projectFlock && projectFlock.kandangs?.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',
}}
/>
</Card>
<Card
title='Daftar Kandang'
className={{
wrapper: 'w-full bg-white',
}}
>
<Table<Kandang>
emptyContent={
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
Pilih project flock terlebih dahulu...
</span>
</div>
}
data={projectFlock ? projectFlock.kandangs : []}
columns={[
{
header: '#',
cell: (props) =>
tableFilterState.pageSize * (tableFilterState.page - 1) +
props.row.index +
1,
},
{
accessorFn: () => (projectFlock ? projectFlock.area.name : ''),
header: 'Area',
},
{
accessorFn: () =>
projectFlock ? projectFlock.location.name : '',
header: 'Lokasi',
},
{
accessorKey: 'name',
header: 'Kandang',
},
{
accessorKey: 'capacity',
header: 'Kapasitas',
},
{
accessorKey: 'pic.name',
header: 'Penanggung Jawab',
},
{
header: 'Aksi',
cell: (props) => {
return (
<>
<Button
color='success'
variant='outline'
onClick={() => {
handleChickinClick(props.row.original);
}}
>
<Icon
icon='mdi:home-import-outline'
width={24}
height={24}
/>
Chickin
</Button>
</>
);
},
},
]}
page={undefined}
className={{
containerClassName: cn({
'mb-20': projectFlock && projectFlock.kandangs?.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',
}}
/>
</Card>
</>
);
};
export default ProjectFlockChickinDetail;
@@ -77,7 +77,7 @@ const ProjectFlockForm = ({
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isApprovedDisabled, setIsApprovedDisabled] = useState(
initialValues?.approval.step_name == 'Pengajuan' ? false : true
initialValues?.approval?.step_name == 'Pengajuan' ? false : true
);
const [isRejectedDisabled, setIsRejectedDisabled] =
useState(!isApprovedDisabled);
@@ -726,7 +726,25 @@ const ProjectFlockForm = ({
</div>
</form>
{formType != 'add' && (
<div className='w-full'>
<div className='flex flex-row gap-2 mb-6'>
{formType != 'edit' && (
<Button
onClick={() => {
router.push(
`/production/project-flock/detail/edit?projectFlockId=${initialValues?.id}`
);
}}
color='warning'
>
<Icon
icon='mdi:pencil-outline'
width={16}
height={16}
className='justify-start text-sm'
/>
Edit
</Button>
)}
<Button
onClick={() => {
if (initialValues?.id) {
@@ -121,6 +121,10 @@ const ProjectFlockKandangTable = ({
);
},
},
{
accessorFn: (row) => row.capacity,
header: 'Kapasitas',
},
{
accessorFn: (row) => row.pic?.name,
header: 'Penanggung Jawab',
+5 -5
View File
@@ -22,11 +22,11 @@ export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [
link: '/production/project-flock',
icon: 'material-symbols:list-alt-add-outline-rounded',
},
{
title: 'Chick In',
link: '/production/chickin',
icon: 'mdi:home-import-outline',
},
// { // DI HILANGKAN PADA VERSI REFACTORING
// title: 'Chick In',
// link: '/production/chickin',
// icon: 'mdi:home-import-outline',
// },
{
title: 'Recording',
link: '/production/recording',
+7 -1
View File
@@ -1,4 +1,4 @@
import { BaseApiService } from './base';
import { BaseApiService } from '@/services/api/base';
import {
CreateProjectFlockPayload,
ProjectFlock,
@@ -14,12 +14,18 @@ import {
CreateChickinPayload,
UpdateChickinPayload,
} from '@/types/api/production/chickin';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
export const ProjectFlockApi = new BaseApiService<
ProjectFlock,
CreateProjectFlockPayload,
UpdateProjectFlockPayload
>('/production/project_flocks');
export const ProjectFlockKandangApi = new BaseApiService<
ProjectFlockKandang,
unknown,
unknown
>('/production/project-flock-kandangs');
export const RecordingApi = new BaseApiService<
Recording,
CreateRecordingPayload,
@@ -0,0 +1,30 @@
import {
CreateProjectFlockPayload,
ProjectFlock,
UpdateProjectFlockPayload,
} from '@/types/api/production/project-flock';
import { BaseApiService } from '../base';
import { BaseApiResponse } from '@/types/api/api-general';
import { sleep } from '@/lib/helper';
import { httpClient } from '@/services/http/client';
import axios from 'axios';
export class ProjectFlockService extends BaseApiService<
ProjectFlock,
CreateProjectFlockPayload,
UpdateProjectFlockPayload
> {
constructor(basePath: string = '') {
super(basePath);
}
async getSingleProjectFlockKandang(): Promise<BaseApiResponse | undefined> {
try {
} catch (error) {
if (axios.isAxiosError<BaseApiResponse>(error)) {
return error.response?.data;
}
return undefined;
}
}
}
+19 -5
View File
@@ -15,6 +15,9 @@ export type BaseMarketing = {
so_date: string;
sales_person: CreatedUser;
notes: string;
grand_total: number;
approval: BaseApproval;
marketing_products?: MarketingProducts[];
};
export type MarketingProducts = {
@@ -22,18 +25,29 @@ export type MarketingProducts = {
qty: number;
unit_price: number;
avg_weigth: number;
total_weight: number;
total_price: number;
product_warehouse: ProductWarehouse;
delivery_date?: string;
vehicle_number?: string;
marketing_delivery_products?: MarketingDeliveryProducts;
};
export type SalesOrder = BaseMetadata & BaseMarketing;
export type MarketingDeliveryProducts = {
id: number;
qty: number;
unit_price: number;
avg_weigth: number;
total_weight: number;
total_price: number;
delivery_date: string;
vehicle_number: string;
};
export type CreateSalesOrderPayload = {
export type Marketing = BaseMetadata & BaseMarketing;
export type CreateMarketingPayload = {
customer_id: number;
date: string;
notes: string;
};
export type UpdateSalesOrderPayload = CreateSalesOrderPayload;
export type UpdateMarketingPayload = CreateMarketingPayload;
+1
View File
@@ -9,6 +9,7 @@ export type BaseKandang = {
location: BaseLocation;
capacity: number;
pic: BaseUser;
project_flock_kandang_id?: number;
};
export type Kandang = BaseMetadata & BaseKandang;
+11 -1
View File
@@ -1,5 +1,7 @@
import { Kandang } from '@/type/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { Supplier } from '../master-data/supplier';
export type BaseProjectFlockKandang = {
id: number;
@@ -7,7 +9,15 @@ export type BaseProjectFlockKandang = {
kandang_id: number;
kandang: Kandang;
project_flock: ProjectFlock;
available_quantity?: number;
available_qtys?: AvailableQty[];
};
export type AvailableQty = {
chick_in_date?: string;
po_number?: string;
document_path?: string;
supplier?: Supplier;
product_warehouse: ProductWarehouse;
};
export type ProjectFlockKandang = BaseProjectFlockKandang;