From e0c347c3d5e938ae13e3c9c2dadd2efefc14383d Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 10 Nov 2025 04:08:08 +0700 Subject: [PATCH] refactor(FE-87-106): refactor api integration untuk project flock dan project flock kandang --- .../project-flock/chickin/detail/page.tsx | 12 +- .../production/project-flock/detail/page.tsx | 38 +- .../sales-orders/SalesOrderTable.tsx | 103 ++++- .../sales-orders/detail/SalesOrderDetail.tsx | 82 +++- .../marketing/sales-orders/form/SalesForm.tsx | 359 +++++++++------ .../chickin/form/ChickinForm.schema.ts | 38 +- .../production/chickin/form/ChickinForm.tsx | 429 ++++++++++-------- .../project-flock/ProjectFlockTable.tsx | 2 +- .../chickin/ProjectFlockChickinDetail.tsx | 103 +++-- .../form/ProjectFlockForm.schema.ts | 4 +- .../project-flock/form/ProjectFlockForm.tsx | 139 +++--- .../recording/form/RecordingForm.tsx | 2 +- src/dummy/marketing.dummy.ts | 93 +++- src/services/api/marketing/marketing.ts | 38 +- src/services/api/production.ts | 2 +- src/types/api/marketing/marketing.d.ts | 1 + src/types/api/production/chickin.d.ts | 8 +- .../api/production/project-flock-kandang.d.ts | 7 +- src/types/api/production/project-flock.d.ts | 7 +- 19 files changed, 961 insertions(+), 506 deletions(-) diff --git a/src/app/production/project-flock/chickin/detail/page.tsx b/src/app/production/project-flock/chickin/detail/page.tsx index d77e5949..da0c8466 100644 --- a/src/app/production/project-flock/chickin/detail/page.tsx +++ b/src/app/production/project-flock/chickin/detail/page.tsx @@ -170,8 +170,8 @@ const DetailChickin = () => {
Flock
{ - chickin.data.project_flock_kandang?.project_flock.flock - .name + chickin?.data?.project_flock_kandang?.project_flock?.flock + ?.name }
@@ -225,8 +225,8 @@ const DetailChickin = () => {
Flock Kandang
{ - chickin.data.project_flock_kandang?.project_flock.flock - .name + chickin?.data?.project_flock_kandang?.project_flock?.flock + ?.name }{' '} - {chickin.data.project_flock_kandang?.kandang.name}
@@ -280,7 +280,7 @@ const DetailChickin = () => { { 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?.project_flock?.flock?.name } - ${chickin?.data.project_flock_kandang?.kandang.name})?`} secondaryButton={{ text: 'Tidak', diff --git a/src/app/production/project-flock/detail/page.tsx b/src/app/production/project-flock/detail/page.tsx index 6cf694c0..811aed54 100644 --- a/src/app/production/project-flock/detail/page.tsx +++ b/src/app/production/project-flock/detail/page.tsx @@ -2,8 +2,11 @@ import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { FlockApi } from '@/services/api/master-data'; import { ProjectFlockApi } from '@/services/api/production'; +import { ProjectFlock } from '@/types/api/production/project-flock'; import { useRouter, useSearchParams } from 'next/navigation'; +import { useState } from 'react'; import useSWR from 'swr'; const ProjectFlockDetail = () => { @@ -12,12 +15,21 @@ const ProjectFlockDetail = () => { const projectFlockId = searchParams.get('projectFlockId'); + const [projectName, setProjectName] = useState(); + const { data: projectFlock, isLoading: isLoadingProjectFlock, mutate: refreshProjectFlock, } = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id)); + const flockUrl = `${FlockApi.basePath}`; + const { + data: flock, + isLoading: isLoadingFlock, + mutate: refreshFlock, + } = useSWR(flockUrl, FlockApi.getAllFetcher); + if (!projectFlockId) { router.back(); @@ -36,15 +48,37 @@ const ProjectFlockDetail = () => { return; } + // Attach flock id to project flock + let projectFlockAttached: ProjectFlock | undefined; + + if (isResponseSuccess(projectFlock) && isResponseSuccess(flock)) { + projectFlockAttached = { + ...projectFlock.data, + flock: flock.data.find( + (flock) => + flock.name == + projectFlock.data.flock_name + .trim() + .split(/\s+/) + .slice(0, -1) + .join(' ') + ), + }; + console.log('projectFlockAttached'); + console.log(projectFlockAttached); + console.log('flocks'); + console.log(flock.data); + } + return (
{isLoadingProjectFlock && ( )} - {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && ( + {projectFlockAttached && ( )} diff --git a/src/components/pages/marketing/sales-orders/SalesOrderTable.tsx b/src/components/pages/marketing/sales-orders/SalesOrderTable.tsx index dd5b03aa..1e8edaba 100644 --- a/src/components/pages/marketing/sales-orders/SalesOrderTable.tsx +++ b/src/components/pages/marketing/sales-orders/SalesOrderTable.tsx @@ -3,7 +3,7 @@ import Button from '@/components/Button'; import CheckboxInput from '@/components/input/CheckboxInput'; import { OptionType } from '@/components/input/SelectInput'; -import { useModal } from '@/components/Modal'; +import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import Table from '@/components/Table'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; @@ -12,7 +12,7 @@ import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector'; import { TableToolbar } from '@/components/table/TableToolbar'; import { ROWS_OPTIONS } from '@/config/constant'; import { isResponseSuccess } from '@/lib/api-helper'; -import { cn } from '@/lib/helper'; +import { cn, formatCurrency, formatVechicleNumber } from '@/lib/helper'; import { MarketingApi } from '@/services/api/marketing/marketing'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { Marketing, MarketingProduct } from '@/types/api/marketing/marketing'; @@ -83,6 +83,7 @@ const SalesOrderTable = () => { const [approveAction, setApproveAction] = useState< 'approve' | 'reject' | null >(null); + const [selectedItem, setSelectedItem] = useState(null); const [rowSelection, setRowSelection] = useState>({}); const selectedRowIds = Object.keys(rowSelection).filter( (id) => rowSelection[id] @@ -96,6 +97,7 @@ const SalesOrderTable = () => { const deleteModal = useModal(); const confirmationModal = useModal(); + const productsModal = useModal(); const searchChangeHandler = useCallback( (e: React.ChangeEvent) => { @@ -123,6 +125,11 @@ const SalesOrderTable = () => { confirmationModal.openModal(); }; + const productsClickHandler = (item: Marketing) => { + setSelectedItem(item); + productsModal.openModal(); + }; + const { state: tableFilterState, updateFilter, @@ -162,6 +169,7 @@ const SalesOrderTable = () => { color='success' onClick={approveClickHandler} className='justify-start text-sm' + disabled={!selectedRowIds.length} > Approve @@ -171,6 +179,7 @@ const SalesOrderTable = () => { color='error' onClick={rejectClickHandler} className='justify-start text-sm' + disabled={!selectedRowIds.length} > Reject @@ -229,16 +238,28 @@ const SalesOrderTable = () => { { accessorKey: 'marketing_products.length', header: 'Product Details', - cell: (props) => ( -
    - {props.row.original.marketing_products?.map((product) => ( -
  • - {product.product_warehouse.product.name} - Qty:{' '} - {product.qty} -
  • - ))} -
- ), + cell: (props) => { + if (props?.row?.original?.marketing_products?.length) { + if (props?.row?.original?.marketing_products?.length > 1) { + return ( + + ); + } else { + const product = props?.row?.original?.marketing_products[0]; + return <>{product?.product_warehouse?.product?.name}; + } + } + }, }, { header: 'Aksi', @@ -321,6 +342,64 @@ const SalesOrderTable = () => { color: approveAction === 'approve' ? 'success' : 'error', }} /> + + +
+

Daftar Produk

+ +
+ + data={ + isResponseSuccess(marketing) && selectedItem + ? (selectedItem?.marketing_products ?? []) + : [] + } + columns={[ + { + header: 'Kandang', + accessorFn(row) { + return row.product_warehouse.warehouse.name; + }, + }, + { + header: 'Produk', + accessorFn(row) { + return row.product_warehouse.product.name; + }, + }, + { + header: 'Harga Satuan (Rp)', + accessorFn(row) { + return formatCurrency(row.unit_price); + }, + }, + ]} + 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', + }} + /> +
); }; diff --git a/src/components/pages/marketing/sales-orders/detail/SalesOrderDetail.tsx b/src/components/pages/marketing/sales-orders/detail/SalesOrderDetail.tsx index d477384e..e67a0644 100644 --- a/src/components/pages/marketing/sales-orders/detail/SalesOrderDetail.tsx +++ b/src/components/pages/marketing/sales-orders/detail/SalesOrderDetail.tsx @@ -6,7 +6,6 @@ import { FormHeader } from '@/components/helper/form/FormHeader'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import Table from '@/components/Table'; -import { isResponseSuccess } from '@/lib/api-helper'; import { cn, formatCurrency, @@ -33,6 +32,7 @@ const SalesOrderDetail = ({ const deleteModal = useModal(); const confirmationModal = useModal(); + const deliveryModal = useModal(); const approveClickHandler = () => { setApprovalAction('approve'); @@ -44,6 +44,10 @@ const SalesOrderDetail = ({ confirmationModal.openModal(); }; + const deliveryClickHandler = () => { + deliveryModal.openModal(); + }; + const deleteClickHandler = () => { deleteModal.openModal(); }; @@ -59,16 +63,25 @@ const SalesOrderDetail = ({ const confirmationModalApproveClickHandler = async () => { setIsLoading(true); - await MarketingApi.singleApproval( - initialValues?.id as number, - approvalAction - ); + // await MarketingApi.singleApproval( + // initialValues?.id as number, + // approvalAction + // ); setIsLoading(false); confirmationModal.closeModal(); toast.success('Successfully approved Sales Order!'); refreshValues?.(); }; + const confirmationModalDeliveryClickHandler = async () => { + setIsLoading(true); + // await MarketingApi.delivery(initialValues?.id as number); + setIsLoading(false); + deliveryModal.closeModal(); + toast.success('Successfully delivered Sales Order!'); + refreshValues?.(); + }; + return ( <>
@@ -77,14 +90,32 @@ const SalesOrderDetail = ({ backUrl='/marketing/sales-orders' />
- - + {initialValues?.approval?.step_number != 3 && ( + <> + + + + )} + {initialValues?.approval?.step_number == 2 && ( + + )}
Status : - {initialValues?.status} + {initialValues?.approval?.step_name} Tanggal Penjualan @@ -214,7 +245,11 @@ const SalesOrderDetail = ({ )}
- @@ -235,12 +270,13 @@ const SalesOrderDetail = ({ text: 'Ya', color: 'error', isLoading: isLoading, + onClick: confirmationModalDeleteClickHandler, }} /> + ); }; diff --git a/src/components/pages/marketing/sales-orders/form/SalesForm.tsx b/src/components/pages/marketing/sales-orders/form/SalesForm.tsx index 32d03766..2a771833 100644 --- a/src/components/pages/marketing/sales-orders/form/SalesForm.tsx +++ b/src/components/pages/marketing/sales-orders/form/SalesForm.tsx @@ -10,7 +10,8 @@ import SelectInput, { } from '@/components/input/SelectInput'; import TextArea from '@/components/input/TextArea'; import Modal, { useModal } from '@/components/Modal'; -import Table from '@/components/Table'; +import * as TanStack from '@tanstack/react-table'; +import Table from '@/components/Table'; // Keep this import import { cn, formatCurrency, formatNumber } from '@/lib/helper'; import { CreateMarketingPayload, @@ -19,7 +20,7 @@ import { MarketingProduct, } from '@/types/api/marketing/marketing'; import { Icon } from '@iconify/react'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import MarketingProductForm from './repeater/MarketingProductForm'; import CheckboxInput from '@/components/input/CheckboxInput'; import { Customer } from '@/types/api/master-data/customer'; @@ -29,6 +30,9 @@ import { MarketingFormValues, MarketingSchema } from './SalesForm.schema'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { MarketingApi } from '@/services/api/marketing/marketing'; import { MarketingProductFormValues } from './repeater/MarketingProduct.schema'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import toast from 'react-hot-toast'; +import { useRouter } from 'next/navigation'; const SalesForm = ({ formType = 'add', @@ -37,11 +41,14 @@ const SalesForm = ({ formType?: 'add' | 'edit'; initialValues?: Marketing; }) => { + const router = useRouter(); const addProductModal = useModal(); + const deleteModal = useModal(); + const [isLoading, setIsLoading] = useState(false); const [selectedMarketingProduct, setSelectedMarketingProduct] = useState(null); - const [marketingProducts, setMarketingProducts] = useState< + const [rawMarketingProducts, setRawMarketingProducts] = useState< MarketingProduct[] >(initialValues?.marketing_products || []); const [selectedCustomer, setSelectedCustomer] = useState( @@ -53,7 +60,13 @@ const SalesForm = ({ const selectedRowIds = Object.keys(rowSelection).map((item) => parseInt(item) ); - const [grandTotal, setGrandTotal] = useState(0); + const [grandTotal, setGrandTotal] = useState( + initialValues?.grand_total ?? 0 + ); + const marketingProducts = useMemo( + () => rawMarketingProducts, + [rawMarketingProducts] + ); const { options: customerOptions, @@ -61,55 +74,66 @@ const SalesForm = ({ isLoadingOptions: isLoadingCustomerOptions, } = useSelect(CustomerApi.basePath, 'id', 'name'); - const handleAddProduct = () => { + const handleAddProduct = useCallback(() => { addProductModal.openModal(); - }; - const handleDeleteProduct = (id: number) => { - setMarketingProducts((prev) => prev.filter((product) => product.id !== id)); - }; + }, [addProductModal]); + const handleDeleteProduct = useCallback((id: number) => { + setRawMarketingProducts((prev) => prev.filter((p) => p.id !== id)); + }, []); const handleBulkDeleteProduct = () => { - setMarketingProducts((prev) => + setRawMarketingProducts((prev) => prev.filter((product) => !selectedRowIds.includes(product.id)) ); }; - const handleAddSubmitProduct = async ( - tableValue: CreateMarketingProductPayload, - fieldValues: MarketingProductFormValues - ) => { - const newMarketingProduct: MarketingProduct = { - id: marketingProducts.length + 1, - product_warehouse: tableValue.product_warehouse!, - unit_price: tableValue.unit_price as number, - total_weight: tableValue.total_weight as number, - qty: tableValue.qty as number, - avg_weight: tableValue.avg_weight as number, - total_price: tableValue.total_price as number, - marketing_delivery_products: { - id: marketingProducts.length + 1, - vehicle_number: tableValue.vehicle_number as string, - delivery_date: tableValue.delivery_date as string, + const handleDelete = () => { + deleteModal.openModal(); + }; + + const handleAddSubmitProduct = useCallback( + async ( + tableValue: CreateMarketingProductPayload, + fieldValues: MarketingProductFormValues + ) => { + const newMarketingProduct: MarketingProduct = { + id: rawMarketingProducts.length + 1, + product_warehouse: tableValue.product_warehouse!, unit_price: tableValue.unit_price as number, total_weight: tableValue.total_weight as number, qty: tableValue.qty as number, avg_weight: tableValue.avg_weight as number, total_price: tableValue.total_price as number, - }, - }; + marketing_delivery_products: { + id: rawMarketingProducts.length + 1, + vehicle_number: tableValue.vehicle_number as string, + delivery_date: tableValue.delivery_date as string, + unit_price: tableValue.unit_price as number, + total_weight: tableValue.total_weight as number, + qty: tableValue.qty as number, + avg_weight: tableValue.avg_weight as number, + total_price: tableValue.total_price as number, + }, + }; - setMarketingProducts((prev) => [...prev, newMarketingProduct]); - formik.setValues({ - ...formik.values, - marketing_products: [...formik.values.marketing_products, fieldValues], - }); - setGrandTotal((prev) => prev + (tableValue.total_price as number)); - addProductModal.closeModal(); - }; - const handleChangeCustomer = (val: OptionType | OptionType[] | null) => { - setSelectedCustomer(val as OptionType); - formik.setFieldValue('customer_id', (val as OptionType)?.value); - }; + setRawMarketingProducts((prev) => [...prev, newMarketingProduct]); + formik.setValues({ + ...formik.values, + marketing_products: [...formik.values.marketing_products, fieldValues], + }); + setGrandTotal((prev) => prev + (tableValue.total_price as number)); + addProductModal.closeModal(); + }, + [rawMarketingProducts.length, addProductModal] + ); + const handleChangeCustomer = useCallback( + (val: OptionType | OptionType[] | null) => { + setSelectedCustomer(val as OptionType); + formik.setFieldValue('customer_id', (val as OptionType)?.value); + formik.setFieldValue('customer', val as OptionType); + }, + [selectedCustomer, setSelectedCustomer] + ); - const createProjectFlockHandler = async (values: CreateMarketingPayload) => { + const createMarketingHandler = async (values: CreateMarketingPayload) => { console.log(values); const createMarketingRes = await MarketingApi.create(values); if (isResponseSuccess(createMarketingRes)) { @@ -118,8 +142,10 @@ const SalesForm = ({ if (isResponseError(createMarketingRes)) { console.log(createMarketingRes); } + toast.success('Successfully created Sales Order!'); + router.push('/marketing/sales-orders'); }; - const updateProjectFlockHandler = async (values: CreateMarketingPayload) => { + const updateMarketingHandler = async (values: CreateMarketingPayload) => { console.log(values); const createMarketingRes = await MarketingApi.update( initialValues?.id as number, @@ -131,6 +157,50 @@ const SalesForm = ({ if (isResponseError(createMarketingRes)) { console.log(createMarketingRes); } + toast.success('Successfully updated Sales Order!'); + router.push('/marketing/sales-orders'); + }; + const deleteMarketingHandler = async () => { + setIsLoading(true); + console.log(initialValues?.id); + const deleteMarketingRes = await MarketingApi.delete( + initialValues?.id as number + ); + if (isResponseSuccess(deleteMarketingRes)) { + console.log(deleteMarketingRes); + } + if (isResponseError(deleteMarketingRes)) { + console.log(deleteMarketingRes); + } + toast.success('Successfully deleted Sales Order!'); + setIsLoading(false); + deleteModal.closeModal(); + router.push('/marketing/sales-orders'); + }; + + const MarketingProductToFieldValues = ( + product: MarketingProduct + ): MarketingProductFormValues => { + return { + vehicle_number: product.marketing_delivery_products?.vehicle_number, + kandang_id: product.product_warehouse.warehouse.id, + kandang: { + value: product.product_warehouse.warehouse.id, + label: product.product_warehouse.warehouse.name, + }, + product_warehouse: { + value: product.product_warehouse.product.id, + label: product.product_warehouse.product.name, + }, + product_warehouse_id: product.product_warehouse.product.id, + unit_price: product.unit_price, + total_weight: product.total_weight, + qty: product.qty, + uom: product.product_warehouse?.product?.uom?.name, + avg_weight: product.avg_weight, + total_price: product.total_price, + delivery_date: product.marketing_delivery_products?.delivery_date, + }; }; const formik = useFormik({ @@ -143,7 +213,10 @@ const SalesForm = ({ value: initialValues?.customer?.id as number, label: initialValues?.customer?.name as string, }, - marketing_products: [], + marketing_products: + initialValues?.marketing_products?.map((product) => + MarketingProductToFieldValues(product) + ) ?? [], }, validationSchema: MarketingSchema, onSubmit: async (values) => { @@ -155,10 +228,10 @@ const SalesForm = ({ } as CreateMarketingPayload; switch (formType) { case 'add': - createProjectFlockHandler(payload); + createMarketingHandler(payload); break; case 'edit': - updateProjectFlockHandler(payload); + updateMarketingHandler(payload); break; default: break; @@ -172,6 +245,85 @@ const SalesForm = ({ formikSetValues(formik.initialValues); }, [formikSetValues, formik.initialValues]); + const columns = useMemo( + () => [ + { + id: 'select', + header: ({ table }: { table: TanStack.Table }) => ( +
+ +
+ ), + cell: ({ row }: { row: TanStack.Row }) => ( +
+ +
+ ), + }, + { + accessorFn: (row: MarketingProduct) => + row.marketing_delivery_products?.vehicle_number, + header: 'No. Polisi', + }, + { + accessorFn: (row: MarketingProduct) => + row.product_warehouse.warehouse.name, + header: 'Kandang', + }, + { + accessorFn: (row: MarketingProduct) => + row.product_warehouse.product.name, + header: 'Produk', + }, + { + accessorFn: (row: MarketingProduct) => formatCurrency(row.unit_price), + header: 'Harga Satuan (Rp)', + }, + { + accessorFn: (row: MarketingProduct) => formatNumber(row.total_weight), + header: 'Total Bobot (Kg)', + }, + { + accessorFn: (row: MarketingProduct) => formatNumber(row.qty), + header: 'Kuantitas', + }, + { + accessorFn: (row: MarketingProduct) => formatNumber(row.avg_weight), + header: 'Avg. Bobot (Kg)', + }, + { + accessorFn: (row: MarketingProduct) => formatCurrency(row.total_price), + header: 'Total Penjualan (Rp)', + }, + { + header: 'Aksi', + cell: (props: TanStack.CellContext) => ( +
+ +
+ ), + }, + ], + [handleDeleteProduct] // dependensi tunggal + ); + return ( <>
( -
- -
- ), - cell: ({ row }) => ( -
- -
- ), - }, - { - accessorFn(row) { - return row.marketing_delivery_products?.vehicle_number; - }, - header: 'No. Polisi', - }, - { - accessorFn(row) { - return row.product_warehouse.warehouse.name; - }, - header: 'Kandang', - }, - { - accessorFn(row) { - return row.product_warehouse.product.name; - }, - header: 'Produk', - }, - { - accessorFn(row) { - return formatCurrency(row.unit_price); - }, - header: 'Harga Satuan (Rp)', - }, - { - accessorFn(row) { - return formatNumber(row.total_weight); - }, - header: 'Total Bobot (Kg)', - }, - { - accessorFn(row) { - return formatNumber(row.qty); - }, - header: 'Kuantitas', - }, - { - accessorFn(row) { - return formatNumber(row.avg_weight); - }, - header: 'Avg. Bobot (Kg)', - }, - { - accessorFn(row) { - return formatCurrency(row.total_price); - }, - header: 'Total Penjualan (Rp)', - }, - { - header: 'Aksi', - cell: (props) => { - return ( -
- -
- ); - }, - }, - ]} + columns={columns} className={{ tableWrapperClassName: 'overflow-x-auto min-h-full!', tableClassName: 'font-inter w-full table-auto min-h-full!', @@ -395,7 +456,16 @@ const SalesForm = ({ Submit
+ {JSON.stringify(formik.errors)} + {formType == 'edit' && ( +
+ +
+ )}
+ ); }; diff --git a/src/components/pages/production/chickin/form/ChickinForm.schema.ts b/src/components/pages/production/chickin/form/ChickinForm.schema.ts index b8abf232..b9da9256 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.schema.ts +++ b/src/components/pages/production/chickin/form/ChickinForm.schema.ts @@ -1,3 +1,37 @@ import * as Yup from 'yup'; -import { Product } from '@/types/api/master-data/product'; -import { Supplier } from '@/types/api/master-data/supplier'; + +type ChickinRequestSchemaType = { + chick_in_date: string; + note?: string | undefined | null; + product_warehouse_id: number; +}; + +type ChickinSchemaType = { + project_flock_kandang_id: number; + chickin_requests: ChickinRequestSchemaType[]; +}; + +export const ChickinRequestSchema: Yup.ObjectSchema = + Yup.object({ + chick_in_date: Yup.string().nullable().required('Tanggal wajib diisi!'), + note: Yup.string().nullable(), + product_warehouse_id: Yup.number() + .min(1, 'Produk wajib diisi!') + .required('Produk wajib diisi!'), + }); + +export const ChickinSchema: Yup.ObjectSchema = Yup.object({ + project_flock_kandang_id: Yup.number() + .min(1, 'Project Flock Kandang wajib diisi!') + .required('Project Flock Kandang wajib diisi!'), + chickin_requests: Yup.array() + .of(ChickinRequestSchema) + .min(1, 'Minimal harus ada 1 produk!') + .required('Produk wajib diisi!'), +}); + +export type ChickinRequestFormValues = Yup.InferType< + typeof ChickinRequestSchema +>; + +export type ChickinFormValues = Yup.InferType; diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx index dc83eddb..af9ccd66 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.tsx +++ b/src/components/pages/production/chickin/form/ChickinForm.tsx @@ -6,7 +6,7 @@ import { FormHeader } from '@/components/helper/form/FormHeader'; import DateInput from '@/components/input/DateInput'; import FileInput from '@/components/input/FileInput'; import NumberInput from '@/components/input/NumberInput'; -import SelectInput from '@/components/input/SelectInput'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; import TextInput from '@/components/input/TextInput'; import Table from '@/components/Table'; import { formatNumber } from '@/lib/helper'; @@ -15,8 +15,19 @@ import { AvailableQty, ProjectFlockKandang, } from '@/types/api/production/project-flock-kandang'; +import { useFormik } from 'formik'; +import { + ChickinFormValues, + ChickinRequestFormValues, + ChickinSchema, +} from './ChickinForm.schema'; +import { useCallback, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; - +import { CreateChickinPayload } from '@/types/api/production/chickin'; +import { ChickinApi } from '@/services/api/production'; +import { isResponseError } from '@/lib/api-helper'; +import toast from 'react-hot-toast'; +import { flushSync } from 'react-dom'; const ChickinFormKandang = ({ formType = 'add', initialValues, @@ -27,199 +38,263 @@ const ChickinFormKandang = ({ afterSubmit?: () => void; }) => { const router = useRouter(); + const [chickinErrorMessage, setChickinErrorMessage] = useState(''); + + const createChickin = useCallback( + async (payload: CreateChickinPayload) => { + const createChickinRes = await ChickinApi.create(payload); + if (isResponseError(createChickinRes)) { + setChickinErrorMessage(createChickinRes.message); + return; + } + + toast.success(createChickinRes?.message as string); + router.push( + `/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}` + ); + }, + [router] + ); + + const handleReset = async () => { + flushSync(() => { + formik.resetForm({ + values: { + project_flock_kandang_id: initialValues?.id, + chickin_requests: initialValues?.available_qtys + ? initialValues.available_qtys.map((availableQty) => ({ + chick_in_date: '', + product_warehouse_id: availableQty.product_warehouse.id, + available_qty: availableQty.available_qty, + note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`, + })) + : [], + }, + }); + }); + + formik.setTouched({ + chickin_requests: initialValues?.available_qtys?.map(() => ({ + chick_in_date: true, + })), + }); + + const errors = await formik.validateForm(); + formik.setErrors(errors); + }; + + const formik = useFormik({ + enableReinitialize: true, + validationSchema: ChickinSchema, + initialValues: { + project_flock_kandang_id: initialValues?.id, + chickin_requests: initialValues?.available_qtys + ? initialValues.available_qtys.map((availableQty) => ({ + chick_in_date: '', + product_warehouse_id: availableQty.product_warehouse.id, + available_qty: availableQty.available_qty, + note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`, + })) + : [], + }, + onSubmit: (values) => { + setChickinErrorMessage(''); + createChickin(values as CreateChickinPayload); + if (afterSubmit) { + afterSubmit(); + } + }, + }); + + const { setValues: formikSetValues } = formik; + + useEffect(() => { + formikSetValues({ + project_flock_kandang_id: initialValues?.id, + chickin_requests: initialValues?.available_qtys + ? initialValues.available_qtys.map((availableQty) => ({ + chick_in_date: '', + product_warehouse_id: availableQty.product_warehouse.id, + available_qty: availableQty.available_qty, + note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`, + })) + : [], + }); + }, [formikSetValues, initialValues]); + return (
- { + handleReset(); }} + onSubmit={formik.handleSubmit} > - - emptyContent={ -
- - Informasi Kandang belum tersedia... - -
- } - 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 || '-', - }, - ]} + - - - - data={initialValues.available_qtys || []} - columns={[ - { - accessorFn: (row) => row.chick_in_date, - header: 'Tanggal Chick In', - cell(props) { - return ( - { - props.row.original.chick_in_date = e.target.value; - }} - /> - ); + > + + emptyContent={ +
+ + Informasi Kandang belum tersedia... + +
+ } + data={[initialValues?.kandang]} + columns={[ + { + header: 'Area', + accessorFn: () => + initialValues?.project_flock?.area.name || '-', }, - }, - { - accessorFn: (row) => row.po_number, - header: 'No. Surat Jalan', - cell(props) { - return ( - { - props.row.original.po_number = e.target.value; - }} - /> - ); + { + header: 'Lokasi', + accessorFn: () => + initialValues?.project_flock?.location.name || '-', }, - }, - { - header: 'Dokumen Surat Jalan', - cell(props) { - return ( - { - props.row.original.document_path = e.target.value; - }} - /> - ); + { + header: 'Flock', + accessorFn: () => + initialValues?.project_flock?.flock_name || '-', }, - }, - { - accessorFn: (row) => row.supplier?.name, - header: 'Supplier', - cell(props) { - return ( - - ); + { + header: 'Kandang', + accessorFn: (row) => row?.name || '-', }, - }, - { - accessorFn: (row) => row.product_warehouse.product.name, - header: 'Produk', - cell(props) { - return ( - - ); + { + header: 'Kapasitas', + accessorFn: (row) => + (row?.capacity && formatNumber(row?.capacity)) || '-', }, - }, - { - accessorFn: (row) => row.product_warehouse.quantity, - header: 'Jumlah (ekor)', - cell(props) { - return ( - - ); + { + 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', + }} + /> +
+ - - Isi persediaan DOC untuk kandang belum tersedia... - -
- } - /> - -
- - -
+ > + + data={formik.values.chickin_requests || []} + columns={[ + { + accessorFn: (row) => row.chick_in_date, + header: 'Tanggal Chick In', + cell(props) { + return ( + + ); + }, + }, + { + accessorFn: (row) => row.product_warehouse_id, + header: 'Produk', + cell(props) { + const availableQty = initialValues?.available_qtys?.find( + (availableQty) => + availableQty.product_warehouse.id === + props.row.original.product_warehouse_id + ); + return ( + + ); + }, + }, + { + accessorFn: (row) => row.product_warehouse_id, + header: 'Jumlah (ekor)', + cell(props) { + const availableQty = initialValues?.available_qtys?.find( + (availableQty) => + availableQty.product_warehouse.id === + props.row.original.product_warehouse_id + ); + return ( + + ); + }, + }, + ]} + 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={ +
+ + Isi persediaan DOC untuk kandang belum tersedia... + +
+ } + /> + + {JSON.stringify(formik.values)} +
+ + +
+ {JSON.stringify(formik.errors)} + ); }; diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index b1a60ac5..fb876acb 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -426,7 +426,7 @@ const ProjectFlockTable = () => { }, { - accessorKey: 'flock.name', + accessorKey: 'flock_name', header: 'Flock', }, { diff --git a/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx b/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx index eeac108d..e965fb97 100644 --- a/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx +++ b/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx @@ -3,15 +3,22 @@ import Badge from '@/components/Badge'; import Button from '@/components/Button'; import Card from '@/components/Card'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import SelectInput, { + OptionType, + useSelect, +} 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 { + ProjectFlockApi, + ProjectFlockKandangApi, +} 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 { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Icon } from '@iconify/react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -37,28 +44,30 @@ const ProjectFlockChickinDetail = ({ const [projectFlock, setProjectFlock] = useState(); // Fetch Data - const { data: listProjectFlock, isLoading: isLoadingListProjectFlock } = - useSWR( - `${ProjectFlockApi.basePath}?${new URLSearchParams({ - search: searchProjectFlock, - }).toString()}`, - ProjectFlockApi.getAllFetcher - ); + const { + data: listProjectFlockKandang, + isLoading: isLoadingListProjectFlockKandang, + } = useSWR( + `${ProjectFlockKandangApi.basePath}?${new URLSearchParams({ + search: searchProjectFlock, + project_flock_id: + projectFlock?.id?.toString() ?? projectFlockId?.toString() ?? '', + }).toString()}`, + ProjectFlockKandangApi.getAllFetcher + ); - // Mapping Options - const options = isResponseSuccess(listProjectFlock) - ? listProjectFlock?.data.map((projectFlock) => { - return { - value: projectFlock.id, - label: `${projectFlock?.flock?.name} - Periode ${projectFlock?.period}`, - }; - }) - : []; + const { + options: options, + isLoadingOptions: isLoadingListProjectFlock, + rawData: listProjectFlock, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name'); // Handle Function - const handleChickinClick = async (kandang: Kandang) => { + const handleChickinClick = async ( + projectFlockKandang: ProjectFlockKandang + ) => { router.push( - `/production/project-flock/chickin/add/kandang?projectFlockKandangId=${kandang.project_flock_kandang_id}&projectFlockId=${projectFlockId ?? selectedProjectFlock?.value}` + `/production/project-flock/chickin/add/kandang?projectFlockKandangId=${projectFlockKandang.id}&projectFlockId=${projectFlockId ?? selectedProjectFlock?.value}` ); }; @@ -146,6 +155,10 @@ const ProjectFlockChickinDetail = ({ } data={projectFlock ? [projectFlock] : []} columns={[ + { + header: 'ID', + accessorKey: 'id', + }, { header: 'Area', accessorKey: 'area.name', @@ -222,7 +235,7 @@ const ProjectFlockChickinDetail = ({ wrapper: 'w-full bg-white', }} > - + emptyContent={
@@ -230,7 +243,11 @@ const ProjectFlockChickinDetail = ({
} - data={projectFlock ? projectFlock.kandangs : []} + data={ + isResponseSuccess(listProjectFlockKandang) + ? listProjectFlockKandang.data + : [] + } columns={[ { header: '#', @@ -240,25 +257,48 @@ const ProjectFlockChickinDetail = ({ 1, }, { - accessorFn: () => (projectFlock ? projectFlock.area.name : ''), + accessorFn: (row) => row?.project_flock?.area?.name, header: 'Area', }, { - accessorFn: () => - projectFlock ? projectFlock.location.name : '', + accessorFn: (row) => row?.project_flock?.location?.name, header: 'Lokasi', }, { - accessorKey: 'name', + accessorKey: 'kandang.name', header: 'Kandang', }, { - accessorKey: 'capacity', + accessorKey: 'kandang.capacity', header: 'Kapasitas', }, { - accessorKey: 'pic.name', - header: 'Penanggung Jawab', + accessorKey: 'approval.step_name', + header: 'Status', + cell: (props) => { + return props.row.original.approval?.step_name ? ( + { + switch ( + props.row.original.approval?.step_name.toUpperCase() + ) { + case 'DISETUJUI': + return 'green'; + case 'DITOLAK': + return 'red'; + default: + return 'gray'; + } + })()} + content={props.row.original.approval?.step_name + .toLowerCase() + .replace(/_/g, ' ') + .replace(/\b\w/g, (char) => char.toUpperCase())} + /> + ) : ( + + ); + }, }, { header: 'Aksi', @@ -271,11 +311,12 @@ const ProjectFlockChickinDetail = ({ onClick={() => { handleChickinClick(props.row.original); }} + className='p-1' > Chickin diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts index 8607a7e6..da5deccb 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts @@ -6,9 +6,7 @@ export const ProjectFlockFormSchema = Yup.object({ value: Yup.number().required('ID Flock wajib diisi!'), label: Yup.string().required('Nama Flock wajib diisi!'), }).nullable(), - flock_id: Yup.number() - .min(1, 'Flock wajib diisi!') - .required('Flock wajib diisi!'), + flock_name: Yup.string().required('Nama Flock wajib diisi!'), // Area area: Yup.object({ diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index e0ce5778..6136aaa7 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -1,7 +1,10 @@ 'use client'; import Button from '@/components/Button'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { AreaApi, @@ -13,7 +16,7 @@ import { import { Icon } from '@iconify/react'; import { useFormik } from 'formik'; import { useRouter } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { use, useEffect, useMemo, useState } from 'react'; import useSWR, { KeyedMutator } from 'swr'; import { ProjectFlockFormSchema, @@ -58,7 +61,6 @@ const ProjectFlockForm = ({ const [selectedLocation, setSelectedLocation] = useState(''); const [disabledLocation, setDisabledLocation] = useState(true); - const [optionsLocation, setOptionsLocation] = useState([]); const [openSelectKandangs, setOpenSelectKandangs] = useState( initialValues?.kandangs && initialValues?.kandangs?.length > 0 @@ -105,38 +107,29 @@ const ProjectFlockForm = ({ }, [initialValues]); // Fetch Data - const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({ - search: '', - }).toString()}`; - const { data: flocks, isLoading: isLoadingFlocks } = useSWR( - flockUrl, - FlockApi.getAllFetcher - ); + const { + rawData: flocks, + isLoadingOptions: isLoadingFlocks, + options: optionsFlock, + } = useSelect(FlockApi.basePath, 'id', 'name'); - const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({ - search: '', - }).toString()}`; - const { data: areas, isLoading: isLoadingAreas } = useSWR( - areaUrl, - AreaApi.getAllFetcher - ); + const { + options: optionsArea, + isLoadingOptions: isLoadingAreas, + rawData: areas, + } = useSelect(AreaApi.basePath, 'id', 'name'); - const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ - search: '', - area_id: selectedArea, - }).toString()}`; - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationUrl, - LocationApi.getAllFetcher - ); + const { + options: optionsLocation, + isLoadingOptions: isLoadingLocations, + rawData: locations, + } = useSelect(LocationApi.basePath, 'id', 'name'); - const fcrUrl = `${FcrApi.basePath}?${new URLSearchParams({ - search: '', - }).toString()}`; - const { data: fcrs, isLoading: isLoadingFcrs } = useSWR( - fcrUrl, - FcrApi.getAllFetcher - ); + const { + options: optionsFcr, + isLoadingOptions: isLoadingFcrs, + rawData: fcrs, + } = useSelect(FcrApi.basePath, 'id', 'name'); const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ search: '', @@ -159,36 +152,6 @@ const ProjectFlockForm = ({ ) ); - // Map Data to Options - const optionsArea = isResponseSuccess(areas) - ? areas?.data.map((area) => ({ - value: area.id, - label: area.name, - })) - : []; - const optionsFcr = isResponseSuccess(fcrs) - ? fcrs?.data.map((fcr) => ({ - value: fcr.id, - label: fcr.name, - })) - : []; - const optionsFlock = isResponseSuccess(flocks) - ? flocks?.data.map((flock) => ({ - value: flock.id, - label: flock.name, - })) - : []; - - useEffect(() => { - if (isResponseSuccess(locations)) { - const options = locations.data.map((location) => ({ - value: location.id, - label: location.name, - })); - setOptionsLocation(options); - } - }, [locations, setSelectedLocation]); - useEffect(() => { if (isResponseSuccess(kandang)) { if (selectedLocation) { @@ -246,8 +209,13 @@ const ProjectFlockForm = ({ `${inputName}_id`, val ? (val as OptionType)?.value : 0 ); + formik.setFieldValue( + `${inputName}_name`, + val ? (val as OptionType)?.label : 0 + ); formik.setFieldTouched(`${inputName}_id`, true); + formik.setFieldTouched(`${inputName}_name`, true); }; const categoryChangeHandler = (val: OptionType | OptionType[] | null) => { @@ -293,15 +261,15 @@ const ProjectFlockForm = ({ const formikInitialValues = useMemo(() => { return { name: initialValues?.name ?? '', - flock: initialValues?.flock + flock: initialValues?.flock_name ? { - value: initialValues.flock.id, - label: initialValues.flock.name, + value: initialValues?.flock?.id ?? 0, + label: initialValues?.flock_name, } : null, area: initialValues?.area ? { - value: initialValues.area.id, + value: initialValues.area?.id, label: initialValues.area.name, } : null, @@ -313,30 +281,31 @@ const ProjectFlockForm = ({ : null, fcr: initialValues?.fcr ? { - value: initialValues.fcr.id, + value: initialValues.fcr?.id, label: initialValues.fcr.name, } : null, location: initialValues?.location ? { - value: initialValues.location.id, + value: initialValues.location?.id, label: initialValues.location.name, } : null, flock_id: initialValues?.flock?.id ?? 0, + flock_name: initialValues?.flock_name ?? '', area_id: initialValues?.area?.id ?? 0, category: initialValues?.category as NonNullable< 'GROWING' | 'LAYING' | undefined >, fcr_id: initialValues?.fcr?.id ?? 0, location_id: initialValues?.location?.id ?? 0, - period: initialValues?.period ?? 0, + period: initialValues?.period ?? 1, kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) as ( | number | undefined )[], }; - }, [initialValues]); + }, [initialValues, flocks]); // Formik const formik = useFormik({ @@ -350,7 +319,7 @@ const ProjectFlockForm = ({ onSubmit: async (values) => { setProjectFlockFormErrorMessage(''); const payload: CreateProjectFlockPayload = { - flock_id: values.flock_id as number, + flock_name: values.flock_name as string, area_id: values.area_id as number, category: values.category as string, fcr_id: values.fcr_id as number, @@ -377,8 +346,8 @@ const ProjectFlockForm = ({ useEffect(() => { if (formType == 'detail') { formik.setFieldValue('area', { - value: initialValues?.area.id, - label: initialValues?.area.name, + value: initialValues?.area?.id, + label: initialValues?.area?.name, }); formik.setFieldValue('area_id', initialValues?.area_id); if (initialValues?.area_id) { @@ -402,9 +371,11 @@ const ProjectFlockForm = ({ // Set lokasi otomatis berdasarkan initialValues saat formType = 'detail' useEffect(() => { - if (formType != 'add' && initialValues?.location?.id) { - setSelectedLocation(initialValues.location?.id.toString()); - setDisabledLocation(false); // biar dropdown lokasi aktif juga + if (formType != 'add') { + if (initialValues?.location?.id) { + setSelectedLocation(initialValues.location?.id.toString()); + setDisabledLocation(false); // biar dropdown lokasi aktif juga + } } }, [formType, initialValues]); @@ -459,7 +430,7 @@ const ProjectFlockForm = ({ method: 'POST', payload: { action: action, - approvable_ids: [initialValues.id], + approvable_ids: [initialValues?.id], }, }); @@ -467,14 +438,6 @@ const ProjectFlockForm = ({ if (refreshProjectFlocks) { 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)) { @@ -591,9 +554,10 @@ const ProjectFlockForm = ({ options={optionsFlock} isLoading={isLoadingFlocks} isError={ - formik.touched.flock_id && Boolean(formik.errors.flock_id) + formik.touched.flock_name && + Boolean(formik.errors.flock_name) } - errorMessage={formik.errors.flock_id as string} + errorMessage={formik.errors.flock_name as string} isClearable isDisabled={formType === 'detail'} /> @@ -724,6 +688,7 @@ const ProjectFlockForm = ({ )} + {JSON.stringify(formik.errors)} {formType != 'add' && (
diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 2f08aaba..68a24ef4 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -215,7 +215,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const flockOptions = isResponseSuccess(projectFlocks) ? projectFlocks.data.map((flock) => ({ value: flock.id, - label: flock.flock.name, + label: flock.flock?.name || '', })) : []; diff --git a/src/dummy/marketing.dummy.ts b/src/dummy/marketing.dummy.ts index 6eab9067..7cbf4317 100644 --- a/src/dummy/marketing.dummy.ts +++ b/src/dummy/marketing.dummy.ts @@ -144,7 +144,7 @@ export const dummyProductWarehouses: ProductWarehouse[] = [ name: 'Pakan Ayam Premium', sku: 'PAK-001', category: 'PAKAN', - } as unknown as Product, // bisa diganti sesuai tipe Product + } as unknown as Product, warehouse: dummyWarehouses[0], created_user: createdUser, created_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), @@ -160,7 +160,7 @@ export const dummyProductWarehouses: ProductWarehouse[] = [ name: 'Vitamin Ayam Super', sku: 'VIT-002', category: 'VITAMIN', - } as unknown as Product, // bisa diganti sesuai tipe Product + } as unknown as Product, warehouse: dummyWarehouses[1], created_user: createdUser, created_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), @@ -172,6 +172,7 @@ export const dummyProductWarehouses: ProductWarehouse[] = [ // 💼 Marketing Dummy // ====================== export const dummyMarketings: Marketing[] = [ + // Step 1: Pengajuan Order { id: 1, status: 'APPROVED', @@ -197,7 +198,7 @@ export const dummyMarketings: Marketing[] = [ grand_total: 7500000, approval: { step_number: 1, - step_name: 'Manager Approval', + step_name: 'Pengajuan Order', action: 'APPROVED', action_by: createdUser, action_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), @@ -227,9 +228,11 @@ export const dummyMarketings: Marketing[] = [ created_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), updated_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), }, + + // Step 2: Sales Order { id: 2, - status: 'PENDING', + status: 'APPROVED', so_number: 'SO-002-2025', so_docs: 'https://example.com/docs/so002.pdf', so_date: format(new Date(), 'yyyy-MM-dd'), @@ -251,9 +254,9 @@ export const dummyMarketings: Marketing[] = [ notes: 'Pesanan kedua untuk stok akhir tahun.', grand_total: 3750000, approval: { - step_number: 1, - step_name: 'Manager Approval', - action: 'PENDING', + step_number: 2, + step_name: 'Sales Order', + action: 'APPROVED', action_by: createdUser, action_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), }, @@ -282,4 +285,80 @@ export const dummyMarketings: Marketing[] = [ created_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), updated_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), }, + + // Step 3: Delivery Order + { + id: 3, + status: 'APPROVED', + so_number: 'SO-003-2025', + so_docs: 'https://example.com/docs/so003.pdf', + so_date: format(new Date(), 'yyyy-MM-dd'), + customer: { + id: 3, + name: 'UD Ternak Sejahtera', + pic_id: 3, + pic: createdUser, + type: 'Reseller', + address: 'Jl. Pasteur No. 88', + phone: '083333333333', + email: 'halo@ternaksejahtera.com', + account_number: '1122334455', + created_user: createdUser, + created_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), + updated_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), + }, + sales_person: createdUser, + notes: 'Order untuk pengiriman ke luar kota.', + grand_total: 5600000, + approval: { + step_number: 3, + step_name: 'Delivery Order', + action: 'APPROVED', + action_by: createdUser, + action_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), + }, + marketing_products: [ + { + id: 3, + qty: 80, + unit_price: 70000, + avg_weight: 2.4, + total_weight: 192, + total_price: 5600000, + product_warehouse: dummyProductWarehouses[0], + marketing_delivery_products: { + id: 3, + qty: 80, + unit_price: 70000, + avg_weight: 2.4, + total_weight: 192, + total_price: 5600000, + delivery_date: format(new Date(), 'yyyy-MM-dd'), + vehicle_number: 'D 9090 ZZ', + }, + }, + { + id: 4, + qty: 80, + unit_price: 70000, + avg_weight: 2.4, + total_weight: 192, + total_price: 5600000, + product_warehouse: dummyProductWarehouses[0], + marketing_delivery_products: { + id: 3, + qty: 80, + unit_price: 70000, + avg_weight: 2.4, + total_weight: 192, + total_price: 5600000, + delivery_date: format(new Date(), 'yyyy-MM-dd'), + vehicle_number: 'D 9090 ZZ', + }, + }, + ], + created_user: createdUser, + created_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), + updated_at: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), + }, ]; diff --git a/src/services/api/marketing/marketing.ts b/src/services/api/marketing/marketing.ts index 1285a2e4..a5c3b8fb 100644 --- a/src/services/api/marketing/marketing.ts +++ b/src/services/api/marketing/marketing.ts @@ -47,20 +47,32 @@ export class MarketingService extends BaseApiService< // simulasi delay await new Promise((res) => setTimeout(res, 500)); - // misalnya fetch dari dummy - return { - code: 200, - status: 'success', - message: 'Data marketing berhasil diambil.', - data: dummyMarketings[0], - }; + const marketing = dummyMarketings.find((marketing) => { + console.log('marketing', marketing); + console.log('id-m', marketing.id); + console.log('id-p', id); + console.log('id', marketing.id == id); + return marketing.id == id; + }); + console.log('marketings', dummyMarketings); + console.log('marketing', marketing); - // jika tidak ditemukan - throw { - code: 404, - status: 'error', - message: 'Data marketing tidak ditemukan.', - }; + if (marketing) { + // misalnya fetch dari dummy + return { + code: 200, + status: 'success', + message: 'Data marketing berhasil diambil.', + data: marketing, + }; + } else { + // jika tidak ditemukan + throw { + code: 404, + status: 'error', + message: 'Data marketing tidak ditemukan.', + }; + } } /** diff --git a/src/services/api/production.ts b/src/services/api/production.ts index c4434f16..a69818e6 100644 --- a/src/services/api/production.ts +++ b/src/services/api/production.ts @@ -20,7 +20,7 @@ export const ProjectFlockApi = new BaseApiService< ProjectFlock, CreateProjectFlockPayload, UpdateProjectFlockPayload ->('/production/project_flocks'); +>('/production/project-flocks'); export const ProjectFlockKandangApi = new BaseApiService< ProjectFlockKandang, unknown, diff --git a/src/types/api/marketing/marketing.d.ts b/src/types/api/marketing/marketing.d.ts index 1ff52fbb..331d95d3 100644 --- a/src/types/api/marketing/marketing.d.ts +++ b/src/types/api/marketing/marketing.d.ts @@ -41,6 +41,7 @@ export type MarketingDeliveryProducts = { total_price: number; delivery_date: string; vehicle_number: string; + do_number?: string | undefined; }; export type Marketing = BaseMetadata & BaseMarketing; diff --git a/src/types/api/production/chickin.d.ts b/src/types/api/production/chickin.d.ts index 3f5263a6..8b1dcb67 100644 --- a/src/types/api/production/chickin.d.ts +++ b/src/types/api/production/chickin.d.ts @@ -14,9 +14,11 @@ export type Chickin = BaseMetadata & BaseChickin; export type CreateChickinPayload = { project_flock_kandang_id: number; - chick_in_date: string; - note: string; - quantity?: number; + chickin_requests: { + chick_in_date: string; + note?: string; + product_warehouse_id: number; + }[]; }; export type UpdateChickinPayload = CreateChickinPayload & { diff --git a/src/types/api/production/project-flock-kandang.d.ts b/src/types/api/production/project-flock-kandang.d.ts index 2cb2503d..222096f4 100644 --- a/src/types/api/production/project-flock-kandang.d.ts +++ b/src/types/api/production/project-flock-kandang.d.ts @@ -2,6 +2,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'; +import { BaseApproval } from '../api-general'; export type BaseProjectFlockKandang = { id: number; @@ -10,14 +11,14 @@ export type BaseProjectFlockKandang = { kandang: Kandang; project_flock: ProjectFlock; available_qtys?: AvailableQty[]; + approval: BaseApproval; }; export type AvailableQty = { chick_in_date?: string; - po_number?: string; - document_path?: string; - supplier?: Supplier; + available_qty: number; product_warehouse: ProductWarehouse; + note?: string; }; export type ProjectFlockKandang = BaseProjectFlockKandang; diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index be14302d..15f60af9 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -9,8 +9,9 @@ export type BaseProjectFlock = { id: number; name: string; status: string; - flock: Flock; - flock_id: number; + flock?: Flock; + flock_i?: number; + flock_name: string; area: Area; area_id: number; category: string; @@ -32,7 +33,7 @@ export type PeriodFlock = { export type ProjectFlock = BaseMetadata & BaseProjectFlock; export type CreateProjectFlockPayload = { - flock_id: number; + flock_name: string; area_id: number; category: string; fcr_id: number;