+ data={delivery.deliveries}
+ columns={[
+ {
+ header: 'Tanggal Pengiriman',
+ accessorFn() {
+ return formatDate(
+ delivery.delivery_date,
+ 'DD MMM yyyy'
+ );
+ },
+ },
+ {
+ header: 'No. Polisi',
+ accessorFn(row) {
+ return formatVechicleNumber(row.vehicle_number);
+ },
+ },
+ {
+ 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);
+ },
+ },
+ {
+ header: 'Total Bobot (Kg)',
+ accessorFn(row) {
+ return formatNumber(row.total_weight);
+ },
+ },
+ {
+ header: 'Kuantitas',
+ accessorFn(row) {
+ return formatNumber(row.qty);
+ },
+ },
+ {
+ header: 'Avg. Bobot (Kg)',
+ accessorFn(row) {
+ return formatNumber(row.avg_weight);
+ },
+ },
+ {
+ header: 'Total Penjualan (Rp)',
+ accessorFn(row) {
+ return formatCurrency(row.total_price);
+ },
+ },
+ ]}
+ className={{
+ containerClassName: cn({
+ 'mb-20':
+ initialValues?.sales_order &&
+ initialValues?.sales_order?.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',
+ }}
+ />
+
+
+
+
+
+ );
+ })}
+
+ )}
- {JSON.stringify(initialValues)}
+ {/* {JSON.stringify(initialValues)}
{JSON.stringify(formik.values)}
- {JSON.stringify(formik.errors)}
+ {JSON.stringify(formik.errors)}
*/}
+ {formType == 'deliver' &&
+ initialValues?.sales_order &&
+ initialValues?.sales_order.length > 0 && (
+
+ {/* {JSON.stringify(memoSalesOrder)}
+ {JSON.stringify(memoDeliveryOrder)} */}
+
+
+ )}
+
Total Penjualan
@@ -362,7 +592,7 @@ const SalesForm = ({
)}
+
+
+
+
+ {selectedDeliveryProduct ? 'Edit' : 'Tambah'} Pengiriman
+
+
+
+
+
+
+
+
=
+ Yup.object({
+ id: Yup.number(),
+ marketing_product_id: Yup.number()
+ .min(1, 'Produk wajib diisi!')
+ .required('Produk wajib diisi!'),
+ marketing_product: Yup.object().nullable().optional(),
+ unit_price: Yup.number()
+ .min(1, 'Harga Satuan wajib diisi!')
+ .required('Harga Satuan wajib diisi!'),
+ total_weight: Yup.number()
+ .min(1, 'Total Bobot wajib diisi!')
+ .required('Total Bobot wajib diisi!'),
+ qty: Yup.number()
+ .min(1, 'Kuantitas wajib diisi!')
+ .required('Kuantitas wajib diisi!'),
+ avg_weight: Yup.number()
+ .min(0, 'Avg. Bobot wajib diisi!')
+ .required('Avg. Bobot wajib diisi!'),
+ total_price: Yup.number()
+ .min(1, 'Total Penjualan wajib diisi!')
+ .required('Total Penjualan wajib diisi!'),
+ vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
+ delivery_date: Yup.string().required('Tanggal Pengiriman wajib diisi!'),
+ do_number: Yup.string().nullable().optional(),
+ });
+
+export type DeliveryOrderProductFormValues = Yup.InferType<
+ typeof DeliveryOrderProductSchema
+>;
+
+// "marketing_product_id": 3,
+// "qty": 20,
+// "unit_price": 1000,
+// "avg_weight": 1.1,
+// "total_weight": 220,
+// "total_price": 20000,
+// "delivery_date": "2025-11-09",
+// "vehicle_number": "D 4321 XXX"
diff --git a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx
index e69de29b..7c9b788a 100644
--- a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx
+++ b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx
@@ -0,0 +1,345 @@
+import { useEffect, useState } from 'react';
+import {
+ DeliveryOrderProductFormValues,
+ DeliveryOrderProductSchema,
+} from './DeliverOrderProduct.schema';
+import { useFormik } from 'formik';
+import Alert from '@/components/Alert';
+import Button from '@/components/Button';
+import NumberInput from '@/components/input/NumberInput';
+import PatternInput from '@/components/input/PatternInput';
+import { formatVechicleNumber } from '@/lib/helper';
+import DateInput from '@/components/input/DateInput';
+import TextInput from '@/components/input/TextInput';
+import SelectInput, { OptionType } from '@/components/input/SelectInput';
+import { SalesOrderProductFormValues } from '../sales-order/SalesOrderProduct.schema';
+import { BaseSalesOrder } from '@/types/api/marketing/marketing';
+import Badge from '@/components/Badge';
+
+const DeliveryOrderProductForm = ({
+ salesOrders,
+ initialValues,
+ onSubmitForm,
+ onUpdateForm,
+}: {
+ salesOrders: BaseSalesOrder[];
+ initialValues?: DeliveryOrderProductFormValues;
+ onSubmitForm?: (value: DeliveryOrderProductFormValues) => Promise;
+ onUpdateForm?: (
+ id: number,
+ value: DeliveryOrderProductFormValues
+ ) => Promise;
+}) => {
+ const [formikErrorMessage, setFormErrorMessage] = useState('');
+ const [selectedProduct, setSelectedProduct] = useState(
+ null
+ );
+
+ const formik = useFormik({
+ enableReinitialize: true,
+ initialValues: {
+ delivery_date: initialValues?.delivery_date || undefined,
+ vehicle_number: initialValues?.vehicle_number || undefined,
+ marketing_product_id: initialValues?.marketing_product_id || undefined,
+ unit_price: initialValues?.unit_price || undefined,
+ total_weight: initialValues?.total_weight || undefined,
+ qty: initialValues?.qty || undefined,
+ avg_weight: initialValues?.avg_weight || undefined,
+ total_price: initialValues?.total_price || undefined,
+ marketing_product: initialValues?.marketing_product || undefined,
+ },
+ validationSchema: DeliveryOrderProductSchema,
+ validateOnBlur: false,
+ validateOnChange: true,
+ onSubmit: async (values) => {
+ setFormErrorMessage('');
+ if (initialValues?.id) {
+ await onUpdateForm?.(initialValues.id, values);
+ } else {
+ await onSubmitForm?.(values);
+ }
+ handleResetForm();
+ },
+ });
+
+ const handleResetForm = () => {
+ setFormErrorMessage('');
+ formik.resetForm({
+ values: {
+ delivery_date: '',
+ vehicle_number: '',
+ marketing_product_id: undefined,
+ unit_price: '',
+ total_weight: '',
+ qty: '',
+ avg_weight: '',
+ total_price: '',
+ marketing_product: undefined,
+ },
+ });
+ setSelectedProduct(null);
+ };
+
+ const handleBlurField = (field: string) => {
+ const { qty, unit_price, total_price, avg_weight, total_weight } =
+ formik.values;
+
+ if (field === 'unit_price' || field === 'total_price' || field === 'qty') {
+ if (qty && unit_price && (field === 'unit_price' || field === 'qty')) {
+ formik.setFieldValue('total_price', Number(qty) * Number(unit_price));
+ } else if (qty && total_price && field === 'total_price') {
+ formik.setFieldValue('unit_price', Number(total_price) / Number(qty));
+ }
+ }
+
+ if (field === 'avg_weight' || field === 'total_weight' || field === 'qty') {
+ if (qty && avg_weight && (field === 'avg_weight' || field === 'qty')) {
+ formik.setFieldValue('total_weight', Number(qty) * Number(avg_weight));
+ } else if (qty && total_weight && field === 'total_weight') {
+ formik.setFieldValue('avg_weight', Number(total_weight) / Number(qty));
+ }
+ }
+ };
+
+ const MarketingProductToFieldValues = (
+ product: BaseSalesOrder
+ ): SalesOrderProductFormValues => {
+ return {
+ id: product.id,
+ vehicle_number: product.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.id,
+ label: product.product_warehouse.product.name,
+ },
+ product_warehouse_id: product.product_warehouse.id,
+ unit_price: product.unit_price,
+ total_weight: product.total_weight,
+ qty: product.qty,
+ avg_weight: product.avg_weight,
+ total_price: product.total_price,
+ };
+ };
+
+ const options = salesOrders.map((item) => ({
+ value: item.id,
+ label: `${item.product_warehouse.product.name} - ${item.product_warehouse.warehouse.name}`,
+ }));
+
+ const { setValues: setFormikValues } = formik;
+
+ useEffect(() => {
+ if (initialValues) {
+ setFormikValues(initialValues);
+ const value = salesOrders.find(
+ (item) => item.id === initialValues.marketing_product_id
+ );
+ setSelectedProduct({
+ value: value?.id,
+ label: `${value?.product_warehouse.product.name} - ${value?.product_warehouse.warehouse.name}`,
+ } as OptionType);
+ }
+ }, [initialValues]);
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default DeliveryOrderProductForm;
diff --git a/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx b/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx
new file mode 100644
index 00000000..8dbbe34f
--- /dev/null
+++ b/src/components/pages/marketing/form/table-view/DeliveryOrderProductTable.tsx
@@ -0,0 +1,233 @@
+import Table from '@/components/Table';
+import { DeliveryOrderProductFormValues } from '../repeater/delivery-order/DeliverOrderProduct.schema';
+import Button from '@/components/Button';
+import { Icon } from '@iconify/react';
+import * as TanStack from '@tanstack/react-table';
+import { useMemo, useRef } from 'react';
+import CheckboxInput from '@/components/input/CheckboxInput';
+import {
+ cn,
+ formatCurrency,
+ formatDate,
+ formatNumber,
+ formatVechicleNumber,
+} from '@/lib/helper';
+import { SalesOrderProductFormValues } from '../repeater/sales-order/SalesOrderProduct.schema';
+
+type DeliveryOrderProductTableProps = {
+ data: DeliveryOrderProductFormValues[];
+ salesOrder: SalesOrderProductFormValues[];
+ formType?: 'add' | 'edit' | 'deliver';
+ rowSelection: Record;
+ setRowSelection: React.Dispatch<
+ React.SetStateAction>
+ >;
+ selectedRowIds: number[];
+ onDelete: (id: number) => void;
+ onEdit: (id: number) => void;
+ onBulkDelete: () => void;
+ onAddProductClick: () => void;
+};
+
+const DeliveryOrderProductTable = ({
+ data,
+ salesOrder,
+ formType,
+ rowSelection,
+ setRowSelection,
+ selectedRowIds,
+ onDelete,
+ onEdit,
+ onBulkDelete,
+ onAddProductClick,
+}: DeliveryOrderProductTableProps) => {
+ const onDeleteRef = useRef(onDelete);
+ const onEditRef = useRef(onDelete);
+ onDeleteRef.current = onDelete;
+ onEditRef.current = onEdit;
+
+ const canAddData = salesOrder.reduce((acc, curr) => {
+ const deliveredQty = data.filter(
+ (deliveryItem) => deliveryItem.marketing_product_id == curr.id
+ );
+ return acc && deliveredQty.length != salesOrder.length;
+ }, true);
+
+ const columns = useMemo(
+ () => [
+ {
+ id: 'select',
+ header: ({
+ table,
+ }: {
+ table: TanStack.Table;
+ }) => (
+
+
+
+ ),
+ cell: ({
+ row,
+ }: {
+ row: TanStack.Row;
+ }) => (
+
+
+
+ ),
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) => row.do_number,
+ header: 'No. Pengiriman',
+ cell: (
+ props: TanStack.CellContext
+ ) => props.row.original.do_number ?? '-',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatDate(row.delivery_date as string, 'DD MMM YYYY'),
+ header: 'Tanggal Delivery',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatVechicleNumber(row.vehicle_number as string),
+ header: 'No. Polisi',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ row.marketing_product?.kandang?.label,
+ header: 'Kandang',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ row.marketing_product?.product_warehouse?.label,
+ header: 'Produk',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatCurrency(parseFloat(row.unit_price as string)),
+ header: 'Harga Satuan (Rp)',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatNumber(parseFloat(row.total_weight as string)),
+ header: 'Total Bobot (Kg)',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatNumber(parseFloat(row.qty as string)),
+ header: 'Kuantitas',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatNumber(parseFloat(row.avg_weight as string)),
+ header: 'Avg. Bobot (Kg)',
+ },
+ {
+ accessorFn: (row: DeliveryOrderProductFormValues) =>
+ formatCurrency(parseFloat(row.total_price as string)),
+ header: 'Total Penjualan (Rp)',
+ },
+ {
+ header: 'Aksi',
+ cell: (
+ props: TanStack.CellContext
+ ) => (
+
+
+
+
+ ),
+ },
+ ],
+ []
+ );
+
+ return (
+ <>
+
+ rowSelection={rowSelection}
+ setRowSelection={setRowSelection}
+ data={data}
+ columns={columns}
+ 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-2 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end first:flex first:flex-row first:justify-start',
+ bodyRowClassName: 'border-b border-b-gray-200',
+ bodyColumnClassName:
+ 'px-2 py-2 last:flex last:flex-row last:justify-end first:flex first:flex-row first:justify-start',
+ paginationClassName: 'hidden',
+ }}
+ emptyContent={
+
+ Belum ada data pengiriman
+
+ }
+ />
+
+
+ {selectedRowIds.length > 0 && (
+
+ )}
+
+ >
+ );
+};
+
+export default DeliveryOrderProductTable;
diff --git a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx
index 38fab584..be86dd6c 100644
--- a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx
+++ b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx
@@ -16,6 +16,7 @@ import CheckboxInput from '@/components/input/CheckboxInput';
type SalesOrderProductTableProps = {
data: SalesOrderProductFormValues[];
+ formType?: 'add' | 'edit' | 'deliver';
rowSelection: Record;
setRowSelection: React.Dispatch<
React.SetStateAction>
@@ -28,6 +29,7 @@ type SalesOrderProductTableProps = {
const SalesOrderProductTable = ({
data,
+ formType,
rowSelection,
setRowSelection,
selectedRowIds,
@@ -137,7 +139,13 @@ const SalesOrderProductTable = ({
rowSelection={rowSelection}
setRowSelection={setRowSelection}
data={data}
- columns={columns}
+ columns={
+ formType == 'deliver'
+ ? columns.filter(
+ (col) => col.header != 'Aksi' && col.id != 'select'
+ )
+ : columns
+ }
className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
@@ -159,33 +167,35 @@ const SalesOrderProductTable = ({
}
/>
-
-
- {selectedRowIds.length > 0 && (
+ {formType != 'deliver' && (
+
- )}
-
+ {selectedRowIds.length > 0 && (
+
+ )}
+
+ )}
>
);
};
diff --git a/src/components/pages/production/chickin/ChickinTable.tsx b/src/components/pages/production/chickin/ChickinTable.tsx
index d76115c5..732923d4 100644
--- a/src/components/pages/production/chickin/ChickinTable.tsx
+++ b/src/components/pages/production/chickin/ChickinTable.tsx
@@ -20,7 +20,6 @@ import { Icon } from '@iconify/react';
import { CellContext, SortingState } from '@tanstack/react-table';
import { useState } from 'react';
import useSWR from 'swr';
-import ChickinForm from './form/ChickinForm';
const ChickinTable = () => {
const {
diff --git a/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx b/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx
index 7c12cc60..0b05e15f 100644
--- a/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx
+++ b/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx
@@ -8,8 +8,6 @@ import {
ChickinSchema,
} from '../ChickinForm.schema';
import DateInput from '@/components/input/DateInput';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import NumberInput from '@/components/input/NumberInput';
import Button from '@/components/Button';
import { useCallback, useEffect, useState } from 'react';
import { useFormik } from 'formik';
@@ -24,7 +22,6 @@ import Alert from '@/components/Alert';
import { formatNumber } from '@/lib/helper';
const ChickinFormView = ({
- formType = 'add',
initialValues,
afterSubmit,
}: {
@@ -122,7 +119,7 @@ const ChickinFormView = ({
return (