mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'fix/cleanup-warning-and-adjust-form-field' into 'development'
[FIX/FE] Cleanup Warning and Refactoring All Form Field (No Warning on Linter) See merge request mbugroup/lti-web-client!341
This commit is contained in:
@@ -108,7 +108,9 @@ const Drawer = ({
|
||||
if (closeOnBackdropClick) {
|
||||
setOpen(false);
|
||||
}
|
||||
onBackdropClick && onBackdropClick();
|
||||
if (onBackdropClick) {
|
||||
onBackdropClick();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,7 +31,11 @@ export const useModal = (isNestingModal = false) => {
|
||||
}, []);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
open ? closeModal() : openModal();
|
||||
if (open) {
|
||||
closeModal();
|
||||
} else {
|
||||
openModal();
|
||||
}
|
||||
}, [open, closeModal, openModal]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -308,7 +308,16 @@ const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => {
|
||||
// },
|
||||
// },
|
||||
],
|
||||
[]
|
||||
[
|
||||
summary,
|
||||
totals.avgActualPrice,
|
||||
totals.avgSalesPrice,
|
||||
totals.avgWeight,
|
||||
totals.totalActualPrice,
|
||||
totals.totalQuantity,
|
||||
totals.totalSalesPrice,
|
||||
totals.totalWeight,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -150,33 +150,39 @@ const DashboardProduction = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { resetForm } = formik;
|
||||
|
||||
const handleResetFilter = useCallback(() => {
|
||||
formik.resetForm();
|
||||
resetForm();
|
||||
resetFilterValues(); // Clear stored filter values
|
||||
setAnalysisMode('OVERVIEW');
|
||||
setEndpointUrl('/dashboards');
|
||||
setSelectedLocationIds([]);
|
||||
}, [resetFilterValues, filterValues, selectedLocationIds]);
|
||||
}, [resetForm, resetFilterValues]);
|
||||
|
||||
const handleApplyFilter = (values: DashboardFilter) => {
|
||||
// Build query params object, only include non-empty values
|
||||
const params: Record<string, string> = {};
|
||||
const handleApplyFilter = useCallback(
|
||||
(values: DashboardFilter) => {
|
||||
// Build query params object, only include non-empty values
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (values.start_date) params.start_date = values.start_date;
|
||||
if (values.end_date) params.end_date = values.end_date;
|
||||
if (values.analysis_mode) params.analysis_mode = values.analysis_mode;
|
||||
if (values.location_ids.length > 0)
|
||||
params.location_ids = values.location_ids.toString();
|
||||
if (values.flock_ids.length > 0)
|
||||
params.flock_ids = values.flock_ids.toString();
|
||||
if (values.kandang_ids.length > 0)
|
||||
params.kandang_ids = values.kandang_ids.toString();
|
||||
if (values.comparison_type) params.comparison_type = values.comparison_type;
|
||||
if (values.start_date) params.start_date = values.start_date;
|
||||
if (values.end_date) params.end_date = values.end_date;
|
||||
if (values.analysis_mode) params.analysis_mode = values.analysis_mode;
|
||||
if (values.location_ids.length > 0)
|
||||
params.location_ids = values.location_ids.toString();
|
||||
if (values.flock_ids.length > 0)
|
||||
params.flock_ids = values.flock_ids.toString();
|
||||
if (values.kandang_ids.length > 0)
|
||||
params.kandang_ids = values.kandang_ids.toString();
|
||||
if (values.comparison_type)
|
||||
params.comparison_type = values.comparison_type;
|
||||
|
||||
setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`);
|
||||
filterModal.closeModal();
|
||||
refreshDashboardProductionData();
|
||||
};
|
||||
setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`);
|
||||
filterModal.closeModal();
|
||||
refreshDashboardProductionData();
|
||||
},
|
||||
[filterModal, refreshDashboardProductionData]
|
||||
);
|
||||
|
||||
// ===== Load filter from store on mount =====
|
||||
useEffect(() => {
|
||||
@@ -190,20 +196,20 @@ const DashboardProduction = () => {
|
||||
kandang_ids: normalizeToArray(filterValues.kandang),
|
||||
comparison_type: filterValues.comparisonType,
|
||||
});
|
||||
}, [filterValues]);
|
||||
}, [filterValues, handleApplyFilter]);
|
||||
|
||||
// ===== Formik Error List =====
|
||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
||||
|
||||
// ===== Export PDF =====
|
||||
const handleExportPDF = async () => {
|
||||
const handleExportPDF = useCallback(async () => {
|
||||
await generateDashboardPDF({
|
||||
filterValues: formik.values,
|
||||
allStatsRef,
|
||||
allChartsRef,
|
||||
setExporting,
|
||||
});
|
||||
};
|
||||
}, [formik.values]);
|
||||
|
||||
// ===== Register Navbar Actions =====
|
||||
const openFilterModalRef = useRef(filterModal.openModal);
|
||||
@@ -253,7 +259,7 @@ const DashboardProduction = () => {
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [formik.values, exporting, setNavbarActions]);
|
||||
}, [formik.values, exporting, setNavbarActions, handleExportPDF]);
|
||||
|
||||
// Cleanup only on unmount
|
||||
useEffect(() => {
|
||||
|
||||
@@ -409,14 +409,14 @@ const DashboardLineChart = ({
|
||||
axisLine={{ stroke: '#C1C1C180', opacity: 0.5 }}
|
||||
domain={(() => {
|
||||
// Calculate dynamic domain based on visible data
|
||||
let seriesData: DashboardChartsSeries[] = [];
|
||||
// let seriesData: DashboardChartsSeries[] = [];
|
||||
let dataset: DashboardChartsDataset[] = [];
|
||||
|
||||
if (
|
||||
analysisMode === 'OVERVIEW' &&
|
||||
isOverviewCharts(data.charts)
|
||||
) {
|
||||
seriesData = data.charts[chartData]?.series || [];
|
||||
// seriesData = data.charts[chartData]?.series || [];
|
||||
dataset = data.charts[chartData]?.dataset || [];
|
||||
} else if (
|
||||
analysisMode === 'COMPARISON' &&
|
||||
@@ -426,7 +426,7 @@ const DashboardLineChart = ({
|
||||
data.charts.farm ||
|
||||
data.charts.flock ||
|
||||
data.charts.kandang;
|
||||
seriesData = comparisonChart?.series || [];
|
||||
// seriesData = comparisonChart?.series || [];
|
||||
dataset = comparisonChart?.dataset || [];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -75,6 +75,12 @@ const ExpenseKandangsTable = ({
|
||||
.filter((id): id is number => id !== undefined)
|
||||
)
|
||||
);
|
||||
const rowSelectionRef = useRef(rowSelection);
|
||||
const prevRowSelectionRef = useRef<Record<string, boolean>>({});
|
||||
|
||||
useEffect(() => {
|
||||
rowSelectionRef.current = rowSelection;
|
||||
}, [rowSelection]);
|
||||
|
||||
const kandangsColumns: ColumnDef<Kandang>[] = [
|
||||
{
|
||||
@@ -133,33 +139,43 @@ const ExpenseKandangsTable = ({
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(isResponseSuccess(kandangs) ? kandangs.data.length > 0 : false);
|
||||
}, [kandangs, isResponseSuccess]);
|
||||
}, [kandangs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(rowSelection).length !== 0 && isResponseSuccess(kandangs)) {
|
||||
const formattedSelectedKandangs = Object.keys(rowSelection).map(
|
||||
(item) => {
|
||||
const selectedKandang = kandangs.data.find(
|
||||
(kandang) => kandang.id === parseInt(item)
|
||||
);
|
||||
const currentKeys = Object.keys(rowSelection).sort().join(',');
|
||||
const prevKeys = Object.keys(prevRowSelectionRef.current).sort().join(',');
|
||||
|
||||
return {
|
||||
id: parseInt(item),
|
||||
name: selectedKandang?.name ?? 'Kandang tidak ditemukan!',
|
||||
};
|
||||
}
|
||||
);
|
||||
if (currentKeys !== prevKeys) {
|
||||
prevRowSelectionRef.current = { ...rowSelection };
|
||||
|
||||
onChange(formattedSelectedKandangs);
|
||||
} else {
|
||||
onChange([]);
|
||||
if (
|
||||
Object.keys(rowSelection).length !== 0 &&
|
||||
isResponseSuccess(kandangs)
|
||||
) {
|
||||
const formattedSelectedKandangs = Object.keys(rowSelection).map(
|
||||
(item) => {
|
||||
const selectedKandang = kandangs.data.find(
|
||||
(kandang) => kandang.id === parseInt(item)
|
||||
);
|
||||
|
||||
return {
|
||||
id: parseInt(item),
|
||||
name: selectedKandang?.name ?? 'Kandang tidak ditemukan!',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
onChange(formattedSelectedKandangs);
|
||||
} else if (Object.keys(rowSelection).length === 0) {
|
||||
onChange([]);
|
||||
}
|
||||
}
|
||||
}, [rowSelection]);
|
||||
}, [rowSelection, kandangs, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
selectedKandangs.length === 0 &&
|
||||
Object.keys(rowSelection).length !== 0
|
||||
Object.keys(rowSelectionRef.current).length !== 0
|
||||
) {
|
||||
setRowSelection({});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useFormik } from 'formik';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -90,6 +90,7 @@ const ExpenseRealizationForm = ({
|
||||
|
||||
const formik = useFormik<ExpenseRealizationFormValues>({
|
||||
initialValues: getExpenseRealizationFormInitialValues(initialValues),
|
||||
enableReinitialize: true,
|
||||
validationSchema:
|
||||
type === 'edit'
|
||||
? UpdateExpenseRealizationFormSchema
|
||||
@@ -143,7 +144,6 @@ const ExpenseRealizationForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { setValues: formikSetValues } = formik;
|
||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
||||
|
||||
const {
|
||||
@@ -254,10 +254,6 @@ const ExpenseRealizationForm = ({
|
||||
formik.setFieldValue('documents', newRequestDocuments);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
formikSetValues(getExpenseRealizationFormInitialValues(initialValues));
|
||||
}, [formikSetValues, getExpenseRealizationFormInitialValues, initialValues]);
|
||||
|
||||
return (
|
||||
<section className='w-full'>
|
||||
<header className='flex flex-col gap-4'>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useFormik } from 'formik';
|
||||
import { toast } from 'react-hot-toast';
|
||||
@@ -102,6 +102,7 @@ const ExpenseRequestForm = ({
|
||||
|
||||
const formik = useFormik<ExpenseRequestFormValues>({
|
||||
initialValues: getExpenseFormInitialValues(initialValues),
|
||||
enableReinitialize: true,
|
||||
validationSchema:
|
||||
type === 'edit'
|
||||
? UpdateExpenseRequestFormSchema
|
||||
@@ -171,7 +172,7 @@ const ExpenseRequestForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { setValues: formikSetValues } = formik;
|
||||
const { setFieldValue, setFieldTouched } = formik;
|
||||
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
@@ -186,8 +187,8 @@ const ExpenseRequestForm = ({
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('category', true);
|
||||
formik.setFieldValue('category', val);
|
||||
setFieldTouched('category', true);
|
||||
setFieldValue('category', val);
|
||||
};
|
||||
|
||||
const locationChangeHandler = useCallback(
|
||||
@@ -195,12 +196,12 @@ const ExpenseRequestForm = ({
|
||||
const location = val as OptionType | null;
|
||||
const locationId = location ? Number(location.value) : 0;
|
||||
|
||||
formik.setFieldTouched('location', true);
|
||||
formik.setFieldValue('location', location);
|
||||
formik.setFieldTouched('location_id', true);
|
||||
formik.setFieldValue('location_id', locationId);
|
||||
setFieldTouched('location', true);
|
||||
setFieldValue('location', location);
|
||||
setFieldTouched('location_id', true);
|
||||
setFieldValue('location_id', locationId);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const kandangsChangeHandler = (
|
||||
@@ -343,10 +344,6 @@ const ExpenseRequestForm = ({
|
||||
formik.handleSubmit(e);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
formikSetValues(getExpenseFormInitialValues(initialValues));
|
||||
}, [formikSetValues, getExpenseFormInitialValues, initialValues]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='w-full'>
|
||||
|
||||
@@ -2,7 +2,6 @@ import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import Table from '@/components/Table';
|
||||
@@ -26,11 +25,13 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
const informasiUmum = [
|
||||
{
|
||||
label: 'ID',
|
||||
value: finance.payment_code,
|
||||
value: finance.payment_code || '-',
|
||||
},
|
||||
{
|
||||
label: 'Jenis Transaksi',
|
||||
value: formatTitleCase(finance.transaction_type.split('_').join(' ')),
|
||||
value: formatTitleCase(
|
||||
(finance.transaction_type || '').split('_').join(' ')
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Pihak',
|
||||
@@ -38,11 +39,13 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
},
|
||||
{
|
||||
label: 'Tanggal',
|
||||
value: formatDate(finance.payment_date, 'DD MMM yyyy'),
|
||||
value: finance.payment_date
|
||||
? formatDate(finance.payment_date, 'DD MMM yyyy')
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
label: 'Metode Pembayaran',
|
||||
value: finance.payment_method,
|
||||
value: finance.payment_method || '-',
|
||||
},
|
||||
{
|
||||
label: 'Catatan',
|
||||
@@ -61,22 +64,22 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
label: `Rekening ${formatTitleCase(finance.party?.type)}`,
|
||||
value: finance.party?.account_number,
|
||||
label: `Rekening ${formatTitleCase(finance.party?.type || '')}`,
|
||||
value: finance.party?.account_number || '-',
|
||||
},
|
||||
{
|
||||
label: 'Nominal',
|
||||
value: formatCurrency(
|
||||
finance.transaction_type === 'INJECTION'
|
||||
? finance.nominal
|
||||
: Math.abs(finance.nominal)
|
||||
? finance.nominal || 0
|
||||
: Math.abs(finance.nominal || 0)
|
||||
),
|
||||
},
|
||||
].filter((item) => {
|
||||
// Hide party account number row if transaction type is INJECTION
|
||||
if (
|
||||
FINANCE_INJECTION_STATUS.includes(finance.transaction_type) &&
|
||||
item.label === `Rekening ${formatTitleCase(finance.party?.type)}`
|
||||
FINANCE_INJECTION_STATUS.includes(finance.transaction_type || '') &&
|
||||
item.label === `Rekening ${formatTitleCase(finance.party?.type || '')}`
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -150,7 +153,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
</Card>
|
||||
|
||||
<div className='flex flex-row gap-2 justify-end'>
|
||||
{FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type) &&
|
||||
{FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type || '') &&
|
||||
finance.party?.type !== 'SUPPLIER' && (
|
||||
<RequirePermission permissions='lti.finance.payments.update'>
|
||||
<Button
|
||||
@@ -163,7 +166,9 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{FINANCE_INITIAL_BALANCE_STATUS.includes(finance.transaction_type) && (
|
||||
{FINANCE_INITIAL_BALANCE_STATUS.includes(
|
||||
finance.transaction_type || ''
|
||||
) && (
|
||||
<RequirePermission permissions='lti.finance.initial_balances.update'>
|
||||
<Button
|
||||
color='warning'
|
||||
@@ -175,7 +180,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{FINANCE_INJECTION_STATUS.includes(finance.transaction_type) && (
|
||||
{FINANCE_INJECTION_STATUS.includes(finance.transaction_type || '') && (
|
||||
<RequirePermission permissions='lti.finance.injections.update'>
|
||||
<Button
|
||||
color='warning'
|
||||
@@ -201,7 +206,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin menghapus data Finance ini (${finance?.payment_code})?`}
|
||||
text={`Apakah anda yakin ingin menghapus data Finance ini (${finance?.payment_code || ''})?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
}}
|
||||
|
||||
@@ -594,7 +594,7 @@ const FinanceTable = () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
[deleteModal]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -282,6 +282,9 @@ const InventoryAdjustmentTable = () => {
|
||||
if (recordingOption) {
|
||||
return recordingOption.label;
|
||||
}
|
||||
if (subtypeValue === 'RECORDING_DEPLETION_OUT') {
|
||||
return 'Recording Depletion';
|
||||
}
|
||||
return subtypeValue || '-';
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export type InventoryAdjustmentFormSchemaType = {
|
||||
label: string;
|
||||
} | null;
|
||||
product_id: number;
|
||||
depletion_product: {
|
||||
value: number;
|
||||
label: string;
|
||||
} | null;
|
||||
depletion_product_id: number;
|
||||
transaction_type: string;
|
||||
transaction_subtype: string;
|
||||
qty: number | string;
|
||||
@@ -80,6 +85,13 @@ export const InventoryAdjustmentFormSchema: Yup.ObjectSchema<InventoryAdjustment
|
||||
.min(1, 'Produk wajib diisi!')
|
||||
.required('Produk wajib diisi!')
|
||||
.typeError('Produk wajib diisi!'),
|
||||
depletion_product: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
depletion_product_id: Yup.number()
|
||||
.default(0)
|
||||
.typeError('Jenis deplesi harus berupa angka'),
|
||||
transaction_type: Yup.string()
|
||||
.min(1, 'Tipe transaksi wajib diisi!')
|
||||
.oneOf(
|
||||
|
||||
@@ -74,6 +74,8 @@ const InventoryAdjustmentForm = ({
|
||||
useState<OptionType | null>(null);
|
||||
const [selectedTransactionSubtype, setSelectedTransactionSubtype] =
|
||||
useState<OptionType | null>(null);
|
||||
const [selectedDepletionProduct, setSelectedDepletionProduct] =
|
||||
useState<OptionType | null>(null);
|
||||
const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
|
||||
useState<string>('');
|
||||
|
||||
@@ -185,6 +187,15 @@ const InventoryAdjustmentForm = ({
|
||||
rawData: products,
|
||||
} = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
setInputValue: setDepletionProductInputValue,
|
||||
options: depletionProductOptions,
|
||||
isLoadingOptions: isLoadingDepletionProductOptions,
|
||||
loadMore: loadMoreDepletionProducts,
|
||||
} = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search', {
|
||||
is_depletion: 'true',
|
||||
});
|
||||
|
||||
const productOptions = useMemo(() => {
|
||||
if (!isResponseSuccess(products)) return [];
|
||||
|
||||
@@ -241,6 +252,8 @@ const InventoryAdjustmentForm = ({
|
||||
project_flock_kandang_id: 0,
|
||||
product: null,
|
||||
product_id: 0,
|
||||
depletion_product: null,
|
||||
depletion_product_id: 0,
|
||||
transaction_type: '',
|
||||
transaction_subtype: '',
|
||||
qty: '',
|
||||
@@ -260,7 +273,10 @@ const InventoryAdjustmentForm = ({
|
||||
setInventoryAdjustmentFormErrorMessage('');
|
||||
const payload: CreateInventoryAdjustmentPayload = {
|
||||
project_flock_kandang_id: values.project_flock_kandang_id,
|
||||
product_id: values.product_id,
|
||||
product_id:
|
||||
values.depletion_product_id > 0
|
||||
? values.depletion_product_id
|
||||
: values.product_id,
|
||||
transaction_subtype: values.transaction_subtype,
|
||||
qty: Number(values.qty),
|
||||
price: Number(values.price),
|
||||
@@ -275,6 +291,8 @@ const InventoryAdjustmentForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { setFieldValue, setFieldTouched, resetForm, setValues } = formik;
|
||||
|
||||
const transactionSubtypeOptions = useMemo(() => {
|
||||
const transactionType = selectedTransactionType?.value;
|
||||
|
||||
@@ -321,19 +339,31 @@ const InventoryAdjustmentForm = ({
|
||||
useEffect(() => {
|
||||
if (selectedTransactionType?.value === 'RECORDING' && selectedProduct) {
|
||||
setSelectedTransactionSubtype(null);
|
||||
formik.setFieldValue('transaction_subtype', '');
|
||||
setFieldValue('transaction_subtype', '');
|
||||
}
|
||||
}, [selectedProduct, selectedTransactionType]);
|
||||
}, [setFieldValue, selectedProduct, selectedTransactionType]);
|
||||
|
||||
const isDepletionProductVisible = useMemo(() => {
|
||||
return selectedTransactionSubtype?.value === 'RECORDING_DEPLETION_IN';
|
||||
}, [selectedTransactionSubtype]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDepletionProductVisible) {
|
||||
setSelectedDepletionProduct(null);
|
||||
setFieldValue('depletion_product', null);
|
||||
setFieldValue('depletion_product_id', 0);
|
||||
}
|
||||
}, [isDepletionProductVisible, setFieldValue]);
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const location = val as OptionType | null;
|
||||
const locationId = location ? Number(location.value) : 0;
|
||||
|
||||
formik.setFieldTouched('location', true);
|
||||
formik.setFieldValue('location', location);
|
||||
formik.setFieldTouched('location_id', true);
|
||||
formik.setFieldValue('location_id', locationId);
|
||||
setFieldTouched('location', true);
|
||||
setFieldValue('location', location);
|
||||
setFieldTouched('location_id', true);
|
||||
setFieldValue('location_id', locationId);
|
||||
|
||||
setSelectedLocation(location);
|
||||
setSelectedProjectFlock(null);
|
||||
@@ -348,10 +378,10 @@ const InventoryAdjustmentForm = ({
|
||||
const projectFlock = val as OptionType | null;
|
||||
const projectFlockId = Number(projectFlock?.value);
|
||||
|
||||
formik.setFieldTouched('project_flock', true);
|
||||
formik.setFieldValue('project_flock', projectFlock);
|
||||
formik.setFieldTouched('project_flock_id', true);
|
||||
formik.setFieldValue('project_flock_id', projectFlockId);
|
||||
setFieldTouched('project_flock', true);
|
||||
setFieldValue('project_flock', projectFlock);
|
||||
setFieldTouched('project_flock_id', true);
|
||||
setFieldValue('project_flock_id', projectFlockId);
|
||||
|
||||
setSelectedProjectFlock(projectFlock);
|
||||
setSelectedKandang(null);
|
||||
@@ -362,44 +392,58 @@ const InventoryAdjustmentForm = ({
|
||||
const kandang = val as OptionType | null;
|
||||
const kandangId = Number(kandang?.value);
|
||||
|
||||
formik.setFieldTouched('kandang', true);
|
||||
formik.setFieldValue('kandang', kandang);
|
||||
formik.setFieldTouched('kandang_id', true);
|
||||
formik.setFieldValue('kandang_id', kandangId);
|
||||
setFieldTouched('kandang', true);
|
||||
setFieldValue('kandang', kandang);
|
||||
setFieldTouched('kandang_id', true);
|
||||
setFieldValue('kandang_id', kandangId);
|
||||
|
||||
setSelectedKandang(kandang);
|
||||
setSelectedProduct(null);
|
||||
formik.setFieldTouched('project_flock_kandang', true);
|
||||
formik.setFieldTouched('project_flock_kandang_id', true);
|
||||
setFieldTouched('project_flock_kandang', true);
|
||||
setFieldTouched('project_flock_kandang_id', true);
|
||||
};
|
||||
|
||||
const productChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const product = val as OptionType | null;
|
||||
const productId = (product?.value as number) ?? 0;
|
||||
|
||||
formik.setFieldTouched('product', true);
|
||||
formik.setFieldValue('product', product);
|
||||
formik.setFieldTouched('product_id', true);
|
||||
formik.setFieldValue('product_id', productId);
|
||||
setFieldTouched('product', true);
|
||||
setFieldValue('product', product);
|
||||
setFieldTouched('product_id', true);
|
||||
setFieldValue('product_id', productId);
|
||||
|
||||
setSelectedProduct(product);
|
||||
};
|
||||
|
||||
const depletionProductChangeHandler = (
|
||||
val: OptionType | OptionType[] | null
|
||||
) => {
|
||||
const depletionProduct = val as OptionType | null;
|
||||
const depletionProductId = (depletionProduct?.value as number) ?? 0;
|
||||
|
||||
setFieldTouched('depletion_product', true);
|
||||
setFieldValue('depletion_product', depletionProduct);
|
||||
setFieldTouched('depletion_product_id', true);
|
||||
setFieldValue('depletion_product_id', depletionProductId);
|
||||
|
||||
setSelectedDepletionProduct(depletionProduct);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const transactionType = formik.values.transaction_type;
|
||||
|
||||
if (!transactionType) {
|
||||
setSelectedTransactionSubtype(null);
|
||||
formik.setFieldValue('transaction_subtype', '');
|
||||
setFieldValue('transaction_subtype', '');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedTransactionSubtype(null);
|
||||
formik.setFieldValue('transaction_subtype', '');
|
||||
formik.setFieldTouched('transaction_subtype', true);
|
||||
setFieldValue('transaction_subtype', '');
|
||||
setFieldTouched('transaction_subtype', true);
|
||||
|
||||
if (transactionType === 'PEMBELIAN') {
|
||||
formik.setFieldValue(
|
||||
setFieldValue(
|
||||
'transaction_subtype',
|
||||
TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.value
|
||||
);
|
||||
@@ -408,7 +452,7 @@ const InventoryAdjustmentForm = ({
|
||||
label: TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.label,
|
||||
});
|
||||
} else if (transactionType === 'PENJUALAN') {
|
||||
formik.setFieldValue(
|
||||
setFieldValue(
|
||||
'transaction_subtype',
|
||||
TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.value
|
||||
);
|
||||
@@ -417,7 +461,7 @@ const InventoryAdjustmentForm = ({
|
||||
label: TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.label,
|
||||
});
|
||||
}
|
||||
}, [formik.values.transaction_type]);
|
||||
}, [setFieldTouched, setFieldValue, formik.values.transaction_type]);
|
||||
|
||||
const transactionTypeChangeHandler = (
|
||||
val: OptionType | OptionType[] | null
|
||||
@@ -425,8 +469,8 @@ const InventoryAdjustmentForm = ({
|
||||
const typeOption = val as OptionType | null;
|
||||
const selectedType = typeOption?.value as string;
|
||||
|
||||
formik.setFieldValue('transaction_type', selectedType);
|
||||
formik.setFieldTouched('transaction_type', true);
|
||||
setFieldValue('transaction_type', selectedType);
|
||||
setFieldTouched('transaction_type', true);
|
||||
|
||||
setSelectedTransactionType(typeOption);
|
||||
};
|
||||
@@ -437,20 +481,21 @@ const InventoryAdjustmentForm = ({
|
||||
const subtypeOption = val as OptionType | null;
|
||||
const selectedSubtype = subtypeOption?.value as string;
|
||||
|
||||
formik.setFieldTouched('transaction_subtype', true);
|
||||
formik.setFieldValue('transaction_subtype', selectedSubtype);
|
||||
setFieldTouched('transaction_subtype', true);
|
||||
setFieldValue('transaction_subtype', selectedSubtype);
|
||||
|
||||
setSelectedTransactionSubtype(subtypeOption);
|
||||
};
|
||||
|
||||
const resetHandler = () => {
|
||||
formik.resetForm();
|
||||
resetForm();
|
||||
setSelectedLocation(null);
|
||||
setSelectedProjectFlock(null);
|
||||
setSelectedKandang(null);
|
||||
setSelectedProduct(null);
|
||||
setSelectedTransactionType(null);
|
||||
setSelectedTransactionSubtype(null);
|
||||
setSelectedDepletionProduct(null);
|
||||
setSelectedProjectFlockLocationId('');
|
||||
};
|
||||
|
||||
@@ -460,14 +505,18 @@ const InventoryAdjustmentForm = ({
|
||||
projectFlockKandangLookup.project_flock_kandang_id;
|
||||
|
||||
if (formik.values.project_flock_kandang_id !== projectFlockKandangId) {
|
||||
formik.setFieldValue('project_flock_kandang_id', projectFlockKandangId);
|
||||
formik.setFieldValue('project_flock_kandang', {
|
||||
setFieldValue('project_flock_kandang_id', projectFlockKandangId);
|
||||
setFieldValue('project_flock_kandang', {
|
||||
value: projectFlockKandangId,
|
||||
label: `${projectFlockKandangLookup.project_flock.flock_name} - ${projectFlockKandangLookup.kandang.name}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [projectFlockKandangLookup, formik.values.project_flock_kandang_id]);
|
||||
}, [
|
||||
projectFlockKandangLookup,
|
||||
formik.values.project_flock_kandang_id,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialValues && type === 'detail') {
|
||||
@@ -519,7 +568,7 @@ const InventoryAdjustmentForm = ({
|
||||
});
|
||||
}
|
||||
|
||||
formik.setValues({
|
||||
setValues({
|
||||
location: initialValues.location
|
||||
? {
|
||||
value: initialValues.location.id,
|
||||
@@ -550,6 +599,8 @@ const InventoryAdjustmentForm = ({
|
||||
}
|
||||
: null,
|
||||
product_id: initialValues.product_warehouse?.product?.id ?? 0,
|
||||
depletion_product: null,
|
||||
depletion_product_id: 0,
|
||||
transaction_type: transactionType,
|
||||
transaction_subtype: transactionSubtype,
|
||||
qty: initialValues.qty ?? '',
|
||||
@@ -557,7 +608,7 @@ const InventoryAdjustmentForm = ({
|
||||
notes: initialValues.notes ?? '',
|
||||
});
|
||||
}
|
||||
}, [formik.setValues, initialValues, type]);
|
||||
}, [setValues, initialValues, type]);
|
||||
|
||||
// ===== Formik Error List =====
|
||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
||||
@@ -729,6 +780,29 @@ const InventoryAdjustmentForm = ({
|
||||
isSearchable
|
||||
/>
|
||||
|
||||
{/* RECORDING_DEPLETION_IN */}
|
||||
{isDepletionProductVisible && (
|
||||
<SelectInput
|
||||
required
|
||||
label='Jenis Deplesi'
|
||||
value={selectedDepletionProduct}
|
||||
onChange={depletionProductChangeHandler}
|
||||
onInputChange={setDepletionProductInputValue}
|
||||
options={depletionProductOptions}
|
||||
onMenuScrollToBottom={loadMoreDepletionProducts}
|
||||
isLoading={isLoadingDepletionProductOptions}
|
||||
isError={
|
||||
formik.touched.depletion_product_id &&
|
||||
Boolean(formik.errors.depletion_product_id)
|
||||
}
|
||||
errorMessage={formik.errors.depletion_product_id as string}
|
||||
isDisabled={type === 'detail'}
|
||||
placeholder='Pilih Jenis Deplesi'
|
||||
isClearable
|
||||
isSearchable
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Number Input Quantity */}
|
||||
<NumberInput
|
||||
className={{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
||||
@@ -112,16 +111,34 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
||||
useState<DeliveryOrderProductFormValues | null>(null);
|
||||
const [deliveryOrderValues, setDeliveryOrderValues] = useState<
|
||||
DeliveryOrderProductFormValues[]
|
||||
>(
|
||||
isResponseSuccess(marketing)
|
||||
? mergeSOwithDO(
|
||||
marketing?.data.sales_order?.map(SalesProductToFieldValues) ?? [],
|
||||
marketing?.data.delivery_order?.flatMap((delivery) =>
|
||||
DeliveryProductToFieldValues(marketing.data.sales_order, delivery)
|
||||
) ?? [],
|
||||
true
|
||||
)
|
||||
: []
|
||||
>([]);
|
||||
|
||||
const getDeliveryOrderValues = useCallback(
|
||||
(marketingData: Marketing): DeliveryOrderProductFormValues[] => {
|
||||
const hasDeliveryOrder =
|
||||
marketingData.delivery_order &&
|
||||
marketingData.delivery_order.length > 0 &&
|
||||
marketingData.delivery_order.some(
|
||||
(doItem) => doItem.deliveries && doItem.deliveries.length > 0
|
||||
);
|
||||
|
||||
if (hasDeliveryOrder) {
|
||||
return (
|
||||
marketingData.delivery_order?.flatMap((delivery) =>
|
||||
DeliveryProductToFieldValues(marketingData.sales_order, delivery)
|
||||
) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
return mergeSOwithDO(
|
||||
marketingData.sales_order?.map(SalesProductToFieldValues) ?? [],
|
||||
marketingData.delivery_order?.flatMap((delivery) =>
|
||||
DeliveryProductToFieldValues(marketingData.sales_order, delivery)
|
||||
) ?? [],
|
||||
true
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// ================== SETUP FORMIK ==================
|
||||
@@ -130,14 +147,8 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
||||
>(() => {
|
||||
if (!isResponseSuccess(marketing))
|
||||
return {} as SalesOrderFormValues & DeliveryOrderFormValues;
|
||||
const deliveryValues = mergeSOwithDO(
|
||||
marketing?.data.sales_order?.map(SalesProductToFieldValues) ?? [],
|
||||
marketing?.data.delivery_order?.flatMap((delivery) =>
|
||||
DeliveryProductToFieldValues(marketing.data.sales_order, delivery)
|
||||
) ?? [],
|
||||
true
|
||||
);
|
||||
|
||||
const deliveryValues = getDeliveryOrderValues(marketing.data);
|
||||
setDeliveryOrderValues(deliveryValues);
|
||||
|
||||
return {
|
||||
@@ -163,7 +174,7 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
||||
) ?? [],
|
||||
delivery_order: deliveryValues,
|
||||
};
|
||||
}, [marketing]);
|
||||
}, [marketing, getDeliveryOrderValues]);
|
||||
|
||||
const formik = useFormik<SalesOrderFormValues & DeliveryOrderFormValues>({
|
||||
enableReinitialize: true,
|
||||
@@ -648,9 +659,8 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Order</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{marketing.data.do_number
|
||||
? marketing.data.do_number
|
||||
: marketing.data.so_number}
|
||||
{marketing.data.do_number ||
|
||||
marketing.data.so_number}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -765,6 +775,7 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
||||
<MemoizedDeliveryOrderProductForm
|
||||
formState={'edit'}
|
||||
salesOrders={marketing?.data?.sales_order ?? []}
|
||||
deliveryOrders={marketing?.data?.delivery_order ?? []}
|
||||
exisitingValues={deliveryOrderValues}
|
||||
onSubmitForm={handleAddSubmitDO}
|
||||
initialValues={selectedDeliveryProduct ?? undefined}
|
||||
|
||||
+53
-8
@@ -10,7 +10,10 @@ import NumberInput from '@/components/input/NumberInput';
|
||||
import PatternInput from '@/components/input/PatternInput';
|
||||
import { formatTitleCase, formatVechicleNumber } from '@/lib/helper';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import { BaseSalesOrder } from '@/types/api/marketing/marketing';
|
||||
import {
|
||||
BaseSalesOrder,
|
||||
BaseDeliveryOrder,
|
||||
} from '@/types/api/marketing/marketing';
|
||||
import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm.schema';
|
||||
import * as Yup from 'yup';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
@@ -32,6 +35,7 @@ import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
||||
const DeliveryOrderProductForm = ({
|
||||
formState,
|
||||
salesOrders,
|
||||
deliveryOrders,
|
||||
initialValues,
|
||||
exisitingValues,
|
||||
onUpdateForm,
|
||||
@@ -39,6 +43,7 @@ const DeliveryOrderProductForm = ({
|
||||
}: {
|
||||
formState: 'add' | 'edit';
|
||||
salesOrders: BaseSalesOrder[];
|
||||
deliveryOrders?: BaseDeliveryOrder[];
|
||||
initialValues?: DeliveryOrderProductFormValues;
|
||||
exisitingValues?: DeliveryOrderProductFormValues[];
|
||||
onSubmitForm?: (value: DeliveryOrderProductFormValues) => Promise<void>;
|
||||
@@ -115,6 +120,36 @@ const DeliveryOrderProductForm = ({
|
||||
})
|
||||
?.filter((item) => item != null) as OptionType[];
|
||||
|
||||
const hasDeliveryOrder = useMemo(() => {
|
||||
return (
|
||||
deliveryOrders &&
|
||||
deliveryOrders.length > 0 &&
|
||||
deliveryOrders.some(
|
||||
(doItem) => doItem.deliveries && doItem.deliveries.length > 0
|
||||
)
|
||||
);
|
||||
}, [deliveryOrders]);
|
||||
|
||||
const deliveryOrder = useMemo(() => {
|
||||
if (!hasDeliveryOrder || !deliveryOrders) return null;
|
||||
|
||||
for (const doItem of deliveryOrders) {
|
||||
const found = doItem.deliveries.find(
|
||||
(d) =>
|
||||
d.product_warehouse.id ===
|
||||
initialValues?.marketing_product?.product_warehouse_id
|
||||
);
|
||||
if (found) {
|
||||
return {
|
||||
...found,
|
||||
delivery_date: doItem.delivery_date,
|
||||
do_number: doItem.do_number,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [deliveryOrders, hasDeliveryOrder, initialValues]);
|
||||
|
||||
const salesOrder = salesOrders.find(
|
||||
(item) => item.id === initialValues?.marketing_product_id
|
||||
);
|
||||
@@ -122,15 +157,25 @@ const DeliveryOrderProductForm = ({
|
||||
const formik = useFormik<DeliveryOrderProductFormValues>({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
delivery_date: initialValues?.delivery_date || undefined,
|
||||
vehicle_number: initialValues?.vehicle_number || undefined,
|
||||
delivery_date:
|
||||
deliveryOrder?.delivery_date ||
|
||||
initialValues?.delivery_date ||
|
||||
undefined,
|
||||
vehicle_number:
|
||||
deliveryOrder?.vehicle_number ||
|
||||
initialValues?.vehicle_number ||
|
||||
undefined,
|
||||
marketing_product_id:
|
||||
salesOrder?.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,
|
||||
unit_price:
|
||||
deliveryOrder?.unit_price ?? initialValues?.unit_price ?? undefined,
|
||||
total_weight:
|
||||
deliveryOrder?.total_weight ?? initialValues?.total_weight ?? undefined,
|
||||
qty: deliveryOrder?.qty ?? initialValues?.qty ?? undefined,
|
||||
avg_weight:
|
||||
deliveryOrder?.avg_weight ?? initialValues?.avg_weight ?? undefined,
|
||||
total_price:
|
||||
deliveryOrder?.total_price ?? initialValues?.total_price ?? undefined,
|
||||
marketing_product: initialValues?.marketing_product || undefined,
|
||||
uom: initialValues?.uom || '',
|
||||
weight_per_convertion:
|
||||
|
||||
@@ -2,10 +2,11 @@ import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/for
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useMemo } from 'react';
|
||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
||||
import { Marketing } from '@/types/api/marketing/marketing';
|
||||
import { Marketing, BaseDelivery } from '@/types/api/marketing/marketing';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
|
||||
type DeliveryOrderProductTableProps = {
|
||||
data: DeliveryOrderProductFormValues[];
|
||||
@@ -42,7 +43,31 @@ const DeliveryOrderProductTable = ({
|
||||
|
||||
const approvalStepNumber = marketing?.latest_approval?.step_number;
|
||||
|
||||
const renderTableContent = (item: DeliveryOrderProductFormValues) => {
|
||||
const hasDeliveryOrder = useMemo(() => {
|
||||
return (
|
||||
marketing?.delivery_order &&
|
||||
marketing.delivery_order.length > 0 &&
|
||||
marketing.delivery_order.some(
|
||||
(doItem) => doItem.deliveries && doItem.deliveries.length > 0
|
||||
)
|
||||
);
|
||||
}, [marketing?.delivery_order]);
|
||||
|
||||
const deliveryItems = useMemo(() => {
|
||||
if (!hasDeliveryOrder) return [];
|
||||
return (
|
||||
marketing?.delivery_order?.flatMap((doItem) =>
|
||||
doItem.deliveries.map((delivery) => ({
|
||||
...delivery,
|
||||
do_number: doItem.do_number,
|
||||
delivery_date: doItem.delivery_date,
|
||||
warehouse: doItem.warehouse,
|
||||
}))
|
||||
) ?? []
|
||||
);
|
||||
}, [marketing?.delivery_order, hasDeliveryOrder]);
|
||||
|
||||
const renderSalesOrderContent = (item: DeliveryOrderProductFormValues) => {
|
||||
const doItem = marketing?.delivery_order?.find(
|
||||
(doItem) => doItem.do_number === item.do_number
|
||||
);
|
||||
@@ -185,50 +210,217 @@ const DeliveryOrderProductTable = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderDeliveryOrderContent = (
|
||||
item: BaseDelivery & {
|
||||
do_number: string;
|
||||
delivery_date: string;
|
||||
warehouse: Warehouse;
|
||||
}
|
||||
) => {
|
||||
const parentDoItem = marketing?.delivery_order?.find(
|
||||
(doItem) => doItem.do_number === item.do_number
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||
<div>Value</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>{item.warehouse?.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Produk</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.product_warehouse?.product?.name}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Qty</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.qty
|
||||
? `${formatNumber(item.qty)} ${item.product_warehouse?.product?.uom?.name ?? ''}`
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
{Number(item.avg_weight ?? 0) > 0 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Avg Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(Number(item.avg_weight))} Kg
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{Number(item.total_weight ?? 0) > 0 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(Number(item.total_weight))}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(item.unit_price)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(item.total_price)}
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
<tr className='border-b border-t border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||
<div>Value</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
{approvalStepNumber !== 1 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tanggal Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.delivery_date
|
||||
? formatDate(item.delivery_date, 'DD MMM YYYY')
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{item.do_number && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>{item.do_number}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Polisi</td>
|
||||
<td className='text-sm px-4 py-3'>{item.vehicle_number}</td>
|
||||
</tr>
|
||||
{parentDoItem && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Dokumen Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
<DeliveryOrderExport
|
||||
data={marketing}
|
||||
deliveryOrder={parentDoItem}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||
{data.map((item) => (
|
||||
<div key={`table-${item.id}`}>
|
||||
{formType === 'success' ? (
|
||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>{renderTableContent(item)}</tbody>
|
||||
</table>
|
||||
{hasDeliveryOrder
|
||||
? deliveryItems.map((item, index) => (
|
||||
<div key={`do-table-${item.product_warehouse?.id}-${index}`}>
|
||||
{formType === 'success' ? (
|
||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
{renderDeliveryOrderContent(item)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
key={`do-table-${item.product_warehouse?.id}-${index}`}
|
||||
title={item.product_warehouse?.product?.name || 'Produk'}
|
||||
collapsible={true}
|
||||
defaultCollapsed={false}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-0',
|
||||
title: 'px-2 py-1.5 font-normal text-sm',
|
||||
collapsible: 'rounded-lg',
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
{renderDeliveryOrderContent(item)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
key={`table-${item.id}`}
|
||||
title={
|
||||
item.marketing_product?.product_warehouse?.label || 'Produk'
|
||||
}
|
||||
collapsible={true}
|
||||
defaultCollapsed={false}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-0',
|
||||
title: 'px-2 py-1.5 font-normal text-sm',
|
||||
collapsible: 'rounded-lg',
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>{renderTableContent(item)}</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
: data.map((item) => (
|
||||
<div key={`table-${item.id}`}>
|
||||
{formType === 'success' ? (
|
||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
{renderSalesOrderContent(item)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
key={`table-${item.id}`}
|
||||
title={
|
||||
item.marketing_product?.product_warehouse?.label ||
|
||||
'Produk'
|
||||
}
|
||||
collapsible={true}
|
||||
defaultCollapsed={false}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-0',
|
||||
title: 'px-2 py-1.5 font-normal text-sm',
|
||||
collapsible: 'rounded-lg',
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
{renderSalesOrderContent(item)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Button from '@/components/Button';
|
||||
import { BaseDeliveryOrder, Marketing } from '@/types/api/marketing/marketing';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { Document, Image, Page, pdf, Text, View } from '@react-pdf/renderer';
|
||||
import { Document, Page, pdf, Text, View } from '@react-pdf/renderer';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { formatDate, formatNumber, formatVechicleNumber } from '@/lib/helper';
|
||||
import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles';
|
||||
|
||||
@@ -143,7 +143,7 @@ const AreasTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('areas-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
@@ -143,7 +143,7 @@ const BanksTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('banks-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
@@ -145,7 +145,7 @@ const CustomersTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('customers-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
@@ -145,7 +145,7 @@ const FlockTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('flocks-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
@@ -128,7 +128,7 @@ const NonstocksTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('nonstocks-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import useSWR from 'swr';
|
||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
||||
@@ -222,7 +216,7 @@ const ProductCategoryTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('product-category-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -222,7 +222,7 @@ const ProductsTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('product-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
+1
-8
@@ -178,14 +178,7 @@ const ProductionStandardForm = ({
|
||||
const router = useRouter();
|
||||
|
||||
// ===== Store =====
|
||||
const {
|
||||
formData,
|
||||
setFormData,
|
||||
addDetail,
|
||||
updateDetail,
|
||||
deleteDetail,
|
||||
clearCache,
|
||||
} = useFormStore();
|
||||
const { formData, setFormData, clearCache } = useFormStore();
|
||||
|
||||
// ===== Formik =====
|
||||
// Initial values - only recalculate when initialValue changes (for edit/detail mode)
|
||||
|
||||
@@ -23,7 +23,6 @@ import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import PopoverButton from '@/components/popover/PopoverButton';
|
||||
import PopoverContent from '@/components/popover/PopoverContent';
|
||||
import SupplierTableSkeleton from '@/components/pages/master-data/supplier/skeleton/SupplierTableSkeleton';
|
||||
import SelectInput from '@/components/input/SelectInput';
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
|
||||
@@ -167,6 +166,8 @@ const SuppliersTable = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { setFieldValue } = formik;
|
||||
|
||||
// ===== CATEGORY OPTIONS (SAPRONAK or BOP) =====
|
||||
const categoryOptions = useMemo(
|
||||
() => [
|
||||
@@ -191,9 +192,9 @@ const SuppliersTable = () => {
|
||||
const option = val as OptionType | null;
|
||||
const categoryId = option?.value ? String(option.value) : null;
|
||||
|
||||
formik.setFieldValue('category_id', categoryId);
|
||||
setFieldValue('category_id', categoryId);
|
||||
},
|
||||
[formik]
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
const handleFilterFlagChange = useCallback(
|
||||
@@ -206,9 +207,9 @@ const SuppliersTable = () => {
|
||||
? false
|
||||
: null;
|
||||
|
||||
formik.setFieldValue('flag', boolValue);
|
||||
setFieldValue('flag', boolValue);
|
||||
},
|
||||
[formik]
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
// ===== FILTER HELPERS =====
|
||||
@@ -238,9 +239,9 @@ const SuppliersTable = () => {
|
||||
if (filterModal.open) {
|
||||
const flagBoolValue =
|
||||
tableFilterState.flagFilter === 'EKSPEDISI' ? true : false;
|
||||
formik.setFieldValue('flag', flagBoolValue);
|
||||
setFieldValue('flag', flagBoolValue);
|
||||
}
|
||||
}, [filterModal.open, tableFilterState.flagFilter]);
|
||||
}, [filterModal.open, tableFilterState.flagFilter, setFieldValue]);
|
||||
|
||||
useEffect(() => {
|
||||
updateFilter('search', searchValue);
|
||||
@@ -508,6 +509,7 @@ const SuppliersTable = () => {
|
||||
options={flagOptions}
|
||||
value={flagValue}
|
||||
onChange={handleFilterFlagChange}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -167,29 +167,26 @@ const SupplierForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { setValues: formikSetValues } = formik;
|
||||
const { setFieldValue } = formik;
|
||||
|
||||
// Initialize Formik
|
||||
useEffect(() => {
|
||||
formikSetValues(formikInitialValues);
|
||||
if (formType != 'add') {
|
||||
const hatcheryArrays = formikInitialValues.hatchery?.split(',');
|
||||
const hatcheryCreatedOptions = hatcheryArrays?.map((item) => ({
|
||||
if (formType !== 'add' && initialValues?.hatchery) {
|
||||
const hatcheryArrays = initialValues.hatchery.split(',');
|
||||
const hatcheryCreatedOptions = hatcheryArrays.map((item) => ({
|
||||
value: item,
|
||||
label: item,
|
||||
}));
|
||||
setHatcheryOptionValues(hatcheryCreatedOptions ?? []);
|
||||
setHatcheryOptionValues(hatcheryCreatedOptions);
|
||||
}
|
||||
}, [formikSetValues, formikInitialValues, setHatcheryOptionValues]);
|
||||
}, [formType, initialValues?.hatchery]);
|
||||
|
||||
useEffect(() => {
|
||||
const commaSeparatedValues = hatcheryOptionsValues
|
||||
.map((item) => item.value)
|
||||
.join(',');
|
||||
formikSetValues({
|
||||
...formik.values,
|
||||
hatchery: commaSeparatedValues,
|
||||
});
|
||||
}, [hatcheryOptionsValues, formikSetValues]);
|
||||
setFieldValue('hatchery', commaSeparatedValues);
|
||||
}, [hatcheryOptionsValues, setFieldValue]);
|
||||
|
||||
// Option Handler
|
||||
const typeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
|
||||
@@ -128,7 +128,7 @@ const UomsTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('uoms-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
|
||||
@@ -169,6 +169,8 @@ const WarehousesTable = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { setFieldValue } = formik;
|
||||
|
||||
// ===== AREA OPTIONS =====
|
||||
const {
|
||||
setInputValue: setAreaInputValue,
|
||||
@@ -245,9 +247,13 @@ const WarehousesTable = () => {
|
||||
if (filterModal.open) {
|
||||
const activeProjectFlockValue =
|
||||
tableFilterState.activeProjectFlockFilter === 'true' ? true : false; // Default ke false (Semua Kandang)
|
||||
formik.setFieldValue('active_project_flock', activeProjectFlockValue);
|
||||
setFieldValue('active_project_flock', activeProjectFlockValue);
|
||||
}
|
||||
}, [filterModal.open]);
|
||||
}, [
|
||||
filterModal.open,
|
||||
tableFilterState.activeProjectFlockFilter,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
updateFilter('search', searchValue);
|
||||
|
||||
@@ -40,7 +40,6 @@ import { RecordingApi } from '@/services/api/production';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import toast from 'react-hot-toast';
|
||||
import Badge from '@/components/Badge';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import { useUiStore } from '@/stores/ui/ui.store';
|
||||
@@ -532,7 +531,7 @@ const RecordingTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
setTableState('recording-table', pathname);
|
||||
}, [pathname]);
|
||||
}, [pathname, setTableState]);
|
||||
|
||||
const searchChangeHandler = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -1119,14 +1118,11 @@ const RecordingTable = () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
isRecordingApproved,
|
||||
tableFilterState.pageSize,
|
||||
tableFilterState.page,
|
||||
selectedRecording,
|
||||
singleDeleteModal,
|
||||
approveModal,
|
||||
rejectModal,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
setApprovalNotes,
|
||||
setSelectedRecording,
|
||||
|
||||
@@ -1007,6 +1007,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
});
|
||||
|
||||
// ===== HELPER FUNCTIONS =====
|
||||
const { setFieldValue } = formik;
|
||||
|
||||
const getAvailableStock = useCallback(
|
||||
(productWarehouseId: number) => {
|
||||
if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0;
|
||||
@@ -1098,16 +1100,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const isAlreadyRecorded = recordedProjectFlockKandangIds.has(
|
||||
projectFlockKandangLookup.project_flock_kandang_id
|
||||
);
|
||||
let color: 'neutral' | 'success' | 'warning' | 'error';
|
||||
|
||||
if (isAlreadyRecorded) {
|
||||
color = 'warning';
|
||||
} else {
|
||||
color = 'success';
|
||||
}
|
||||
const color = isAlreadyRecorded ? 'warning' : 'success';
|
||||
|
||||
return (
|
||||
<span className={'whitespace-nowrap text-xs'}>
|
||||
<span className={`whitespace-nowrap text-xs text-${color}`}>
|
||||
Periode {projectFlockKandangLookup.project_flock?.period}
|
||||
</span>
|
||||
);
|
||||
@@ -1411,9 +1407,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
}
|
||||
|
||||
if (formik.values.project_flock_kandang_id !== projectFlockKandangId) {
|
||||
formik.setFieldValue('project_flock_kandang_id', projectFlockKandangId);
|
||||
setFieldValue('project_flock_kandang_id', projectFlockKandangId);
|
||||
|
||||
formik.setFieldValue('project_flock_kandang', {
|
||||
setFieldValue('project_flock_kandang', {
|
||||
value: projectFlockKandangId,
|
||||
label: projectFlockKandangLookup
|
||||
? `${projectFlockKandangLookup.project_flock.flock_name} - ${projectFlockKandangLookup.kandang.name}`
|
||||
@@ -1431,6 +1427,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
nextDayRecording,
|
||||
existingRecordings,
|
||||
today,
|
||||
currentRecordDate,
|
||||
duplicateErrorShown,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1472,11 +1471,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
formik.values.project_flock_kandang_id !==
|
||||
projectFlockKandangDetail.id
|
||||
) {
|
||||
formik.setFieldValue(
|
||||
setFieldValue(
|
||||
'project_flock_kandang_id',
|
||||
projectFlockKandangDetail.id
|
||||
);
|
||||
formik.setFieldValue('project_flock_kandang', {
|
||||
setFieldValue('project_flock_kandang', {
|
||||
value: projectFlockKandangDetail.id,
|
||||
label: `${projectFlock.flock_name} - ${kandang.name}`,
|
||||
});
|
||||
@@ -1490,6 +1489,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
type,
|
||||
enhancedProjectFlockOptions,
|
||||
formik.values.project_flock_kandang_id,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
const approveHandler = async (notes: string) => {
|
||||
@@ -1653,10 +1653,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
if (isLayingCategory && (type as 'add' | 'edit' | 'detail') !== 'detail') {
|
||||
const layingValues = formik.values as RecordingLayingFormValues;
|
||||
if (!layingValues.eggs || layingValues.eggs.length === 0) {
|
||||
formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: '' }]);
|
||||
setFieldValue('eggs', [{ product_warehouse_id: 0, qty: '' }]);
|
||||
}
|
||||
}
|
||||
}, [isLayingCategory, type]);
|
||||
}, [isLayingCategory, type, formik.values, setFieldValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (type !== 'add') {
|
||||
@@ -3199,6 +3199,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
pageSize={100}
|
||||
className={{
|
||||
tableWrapperClassName: 'overflow-x-auto',
|
||||
containerClassName: 'mb-6!',
|
||||
tableClassName: 'w-full table-auto text-sm',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { type BaseApiResponse } from '@/types/api/api-general';
|
||||
import Table from '@/components/Table';
|
||||
import Badge from '@/components/Badge';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
@@ -186,7 +185,7 @@ const UniformityTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const pathname = usePathname();
|
||||
const { searchValue, setSearchValue, setTableState } = useUiStore();
|
||||
const { searchValue, setTableState } = useUiStore();
|
||||
const isSuccess = useUniformityStore((s) => s.isSuccess);
|
||||
const setIsSuccess = useUniformityStore((s) => s.setIsSuccess);
|
||||
const createdUniformity = useUniformityStore((s) => s.createdUniformity);
|
||||
@@ -198,7 +197,6 @@ const UniformityTable = () => {
|
||||
state: tableFilterState,
|
||||
updateFilter,
|
||||
setPage,
|
||||
setPageSize,
|
||||
toQueryString: getTableFilterQueryString,
|
||||
} = useTableFilter({
|
||||
initial: {
|
||||
@@ -251,6 +249,10 @@ const UniformityTable = () => {
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
// ===== FILTER STATE =====
|
||||
const [filterDateValues, setFilterDateValues] = useState({
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
});
|
||||
const [filterLocation, setFilterLocation] = useState<OptionType | null>(null);
|
||||
const [filterProjectFlock, setFilterProjectFlock] =
|
||||
useState<OptionType | null>(null);
|
||||
@@ -345,8 +347,8 @@ const UniformityTable = () => {
|
||||
// ===== FORMIK FILTER =====
|
||||
const filterFormik = useFormik<UniformityTableFilterValues>({
|
||||
initialValues: {
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
start_date: filterDateValues.start_date,
|
||||
end_date: filterDateValues.end_date,
|
||||
location: filterLocation,
|
||||
project_flock: filterProjectFlock,
|
||||
project_flock_kandang_id: filterProjectFlockKandangId,
|
||||
@@ -383,6 +385,13 @@ const UniformityTable = () => {
|
||||
const { formErrorList, close, handleFormSubmit } =
|
||||
useFormikErrorList(filterFormik);
|
||||
|
||||
useEffect(() => {
|
||||
setFilterDateValues({
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
});
|
||||
}, [tableFilterState.start_date, tableFilterState.end_date]);
|
||||
|
||||
// ===== BUILD SWR KEY WITH FILTERS =====
|
||||
const uniformitySwrKey = useMemo(() => {
|
||||
const basePath = UniformityApi.basePath;
|
||||
@@ -496,6 +505,7 @@ const UniformityTable = () => {
|
||||
setFilterKandang(null);
|
||||
setFilterProjectFlockKandangId(undefined);
|
||||
setFilterErrors({});
|
||||
setFilterDateValues({ start_date: '', end_date: '' });
|
||||
|
||||
updateFilter('start_date', '');
|
||||
updateFilter('end_date', '');
|
||||
@@ -505,7 +515,7 @@ const UniformityTable = () => {
|
||||
|
||||
filterFormik.resetForm();
|
||||
filterModal.closeModal();
|
||||
}, [filterFormik, updateFilter]);
|
||||
}, [filterFormik, updateFilter, filterModal]);
|
||||
|
||||
const selectedRowIds = useMemo(() => {
|
||||
return Object.keys(rowSelection)
|
||||
@@ -553,7 +563,13 @@ const UniformityTable = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [searchParams, uniformities]);
|
||||
}, [
|
||||
searchParams,
|
||||
uniformities,
|
||||
router,
|
||||
singleApproveModal,
|
||||
singleRejectModal,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
@@ -1289,7 +1305,7 @@ const UniformityTable = () => {
|
||||
<div className='flex flex-col p-4 gap-1.5'>
|
||||
{/* Rentang Waktu */}
|
||||
<div>
|
||||
<label className='flex text-xs items-center gap-2 py-2 font-semibold'>
|
||||
<label className='flex text-xs items-center gap-1 py-2 font-semibold after:content-["*"] after:text-red-500'>
|
||||
Tanggal
|
||||
</label>
|
||||
<div className='flex items-center gap-2'>
|
||||
@@ -1301,6 +1317,10 @@ const UniformityTable = () => {
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
filterFormik.setFieldValue('start_date', value);
|
||||
setFilterDateValues((prev) => ({
|
||||
...prev,
|
||||
start_date: value,
|
||||
}));
|
||||
|
||||
if (value && filterFormik.values.end_date) {
|
||||
const startDate = new Date(value);
|
||||
@@ -1344,6 +1364,10 @@ const UniformityTable = () => {
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
filterFormik.setFieldValue('end_date', value);
|
||||
setFilterDateValues((prev) => ({
|
||||
...prev,
|
||||
end_date: value,
|
||||
}));
|
||||
|
||||
if (value && filterFormik.values.start_date) {
|
||||
const startDateObj = new Date(
|
||||
|
||||
@@ -99,14 +99,22 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
||||
setExpandedDrawerOpen(true);
|
||||
}, 0);
|
||||
}
|
||||
}, [shouldFetchDetails, uniformity_details, hasFetchedDetails]);
|
||||
}, [
|
||||
shouldFetchDetails,
|
||||
uniformity_details,
|
||||
hasFetchedDetails,
|
||||
setExpandedDrawerContent,
|
||||
setExpandedDrawerOpen,
|
||||
initialValues.info_umum,
|
||||
initialValues.id,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setExpandedDrawerOpen(false);
|
||||
setExpandedDrawerContent(null);
|
||||
};
|
||||
}, []);
|
||||
}, [setExpandedDrawerOpen, setExpandedDrawerContent]);
|
||||
|
||||
const infoUmumTableData: DetailOptionType[] = useMemo(() => {
|
||||
if (!initialValues) return [];
|
||||
@@ -223,7 +231,7 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
||||
},
|
||||
},
|
||||
],
|
||||
[initialValues]
|
||||
[initialValues, handleViewUniformityDetails, isLoading]
|
||||
);
|
||||
|
||||
const samplingTableData: DetailOptionType[] = useMemo(() => {
|
||||
|
||||
@@ -279,6 +279,8 @@ const UniformityForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { setFieldValue, setFieldTouched } = formik;
|
||||
|
||||
const handleValidateForm = async () => {
|
||||
const errors = await formik.validateForm();
|
||||
|
||||
@@ -301,10 +303,10 @@ const UniformityForm = ({
|
||||
const location = val as OptionType | null;
|
||||
const locationId = Number(location?.value);
|
||||
|
||||
formik.setFieldTouched('location', true);
|
||||
formik.setFieldValue('location', location);
|
||||
formik.setFieldTouched('location_id', true);
|
||||
formik.setFieldValue('location_id', locationId);
|
||||
setFieldTouched('location', true);
|
||||
setFieldValue('location', location);
|
||||
setFieldTouched('location_id', true);
|
||||
setFieldValue('location_id', locationId);
|
||||
|
||||
setSelectedLocation(location);
|
||||
setSelectedProjectFlock(null);
|
||||
@@ -312,7 +314,7 @@ const UniformityForm = ({
|
||||
location ? location.value.toString() : ''
|
||||
);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleProjectFlockChange = useCallback(
|
||||
@@ -320,14 +322,14 @@ const UniformityForm = ({
|
||||
const projectFlock = val as OptionType | null;
|
||||
const projectFlockId = Number(projectFlock?.value);
|
||||
|
||||
formik.setFieldTouched('project_flock', true);
|
||||
formik.setFieldValue('project_flock', projectFlock);
|
||||
formik.setFieldTouched('project_flock_id', true);
|
||||
formik.setFieldValue('project_flock_id', projectFlockId);
|
||||
setFieldTouched('project_flock', true);
|
||||
setFieldValue('project_flock', projectFlock);
|
||||
setFieldTouched('project_flock_id', true);
|
||||
setFieldValue('project_flock_id', projectFlockId);
|
||||
|
||||
setSelectedProjectFlock(projectFlock);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleKandangChange = useCallback(
|
||||
@@ -335,24 +337,24 @@ const UniformityForm = ({
|
||||
const kandang = val as OptionType | null;
|
||||
const kandangId = Number(kandang?.value);
|
||||
|
||||
formik.setFieldTouched('kandang', true);
|
||||
formik.setFieldValue('kandang', kandang);
|
||||
formik.setFieldTouched('kandang_id', true);
|
||||
formik.setFieldValue('kandang_id', kandangId);
|
||||
setFieldTouched('kandang', true);
|
||||
setFieldValue('kandang', kandang);
|
||||
setFieldTouched('kandang_id', true);
|
||||
setFieldValue('kandang_id', kandangId);
|
||||
|
||||
setSelectedKandang(kandang);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const document = e.target.files?.[0];
|
||||
|
||||
formik.setFieldTouched('document', true);
|
||||
setFieldTouched('document', true);
|
||||
|
||||
if (!document) {
|
||||
formik.setFieldValue('document', undefined);
|
||||
setFieldValue('document', undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,24 +374,95 @@ const UniformityForm = ({
|
||||
return;
|
||||
}
|
||||
|
||||
formik.setFieldValue('document', document);
|
||||
setFieldValue('document', document);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
formik.setFieldValue('date', e.target.value);
|
||||
setFieldValue('date', e.target.value);
|
||||
},
|
||||
[]
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
const handleRemoveFile = useCallback(() => {
|
||||
formik.setFieldValue('document', undefined);
|
||||
setFieldValue('document', undefined);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}, [formik]);
|
||||
}, [setFieldValue]);
|
||||
|
||||
// ===== RESET PROJECT FLOCK & KANDANG WHEN LOCATION CHANGES =====
|
||||
const prevLocationIdRef = useRef<number | string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const currentLocationId = formik.values.location_id || '';
|
||||
|
||||
if (currentLocationId === prevLocationIdRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
prevLocationIdRef.current = currentLocationId;
|
||||
|
||||
if (formik.values.project_flock !== null) {
|
||||
setFieldTouched('project_flock', false);
|
||||
setFieldValue('project_flock', null);
|
||||
}
|
||||
if (formik.values.project_flock_id !== 0) {
|
||||
setFieldTouched('project_flock_id', false);
|
||||
setFieldValue('project_flock_id', 0);
|
||||
}
|
||||
|
||||
if (formik.values.kandang !== null) {
|
||||
setFieldTouched('kandang', false);
|
||||
setFieldValue('kandang', null);
|
||||
}
|
||||
if (formik.values.kandang_id !== 0) {
|
||||
setFieldTouched('kandang_id', false);
|
||||
setFieldValue('kandang_id', 0);
|
||||
}
|
||||
|
||||
setSelectedProjectFlock(null);
|
||||
setSelectedKandang(null);
|
||||
}, [
|
||||
formik.values.location_id,
|
||||
formik.values.project_flock,
|
||||
formik.values.project_flock_id,
|
||||
formik.values.kandang,
|
||||
formik.values.kandang_id,
|
||||
setFieldTouched,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
const prevProjectFlockIdRef = useRef<number | string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const currentProjectFlockId = formik.values.project_flock_id || '';
|
||||
|
||||
if (currentProjectFlockId === prevProjectFlockIdRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
prevProjectFlockIdRef.current = currentProjectFlockId;
|
||||
|
||||
if (formik.values.kandang !== null) {
|
||||
setFieldTouched('kandang', false);
|
||||
setFieldValue('kandang', null);
|
||||
}
|
||||
if (formik.values.kandang_id !== 0) {
|
||||
setFieldTouched('kandang_id', false);
|
||||
setFieldValue('kandang_id', 0);
|
||||
}
|
||||
|
||||
setSelectedKandang(null);
|
||||
}, [
|
||||
formik.values.project_flock_id,
|
||||
formik.values.kandang,
|
||||
formik.values.kandang_id,
|
||||
setFieldTouched,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
const handleDownloadTemplate = useCallback(() => {
|
||||
const population = projectFlockKandangLookup?.population;
|
||||
@@ -442,9 +515,9 @@ const UniformityForm = ({
|
||||
|
||||
const weeksDiff = Math.floor(daysDiff / 7);
|
||||
|
||||
formik.setFieldValue('week', initialWeek + weeksDiff);
|
||||
setFieldValue('week', initialWeek + weeksDiff);
|
||||
} else {
|
||||
formik.setFieldValue('week', initialWeek);
|
||||
setFieldValue('week', initialWeek);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -452,6 +525,7 @@ const UniformityForm = ({
|
||||
projectFlockKandangLookup?.project_flock_kandang_id,
|
||||
recordingsData,
|
||||
formik.values.date,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -686,7 +760,7 @@ const UniformityForm = ({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{projectFlockKandangLookup?.population && (
|
||||
{!!projectFlockKandangLookup?.population && (
|
||||
<>
|
||||
<div className='flex items-center justify-center gap-2 py-4'>
|
||||
<div className='h-px bg-[#18181B33] w-8'></div>
|
||||
|
||||
@@ -22,7 +22,6 @@ import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import PopoverButton from '@/components/popover/PopoverButton';
|
||||
import PopoverContent from '@/components/popover/PopoverContent';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/PurchaseTableSkeleton';
|
||||
@@ -32,7 +31,6 @@ import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { Purchase } from '@/types/api/purchase/purchase';
|
||||
import { PurchaseApi } from '@/services/api/purchase';
|
||||
import { ExpenseApi } from '@/services/api/expense';
|
||||
@@ -412,13 +410,13 @@ const PurchaseTable = () => {
|
||||
[updateFilter, setSearchValue]
|
||||
);
|
||||
|
||||
const pageSizeChangeHandler = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const newVal = val as OptionType;
|
||||
setPageSize(newVal.value as number);
|
||||
},
|
||||
[setPageSize]
|
||||
);
|
||||
// const pageSizeChangeHandler = useCallback(
|
||||
// (val: OptionType | OptionType[] | null) => {
|
||||
// const newVal = val as OptionType;
|
||||
// setPageSize(newVal.value as number);
|
||||
// },
|
||||
// [setPageSize]
|
||||
// );
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -95,62 +95,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
};
|
||||
};
|
||||
|
||||
// ===== SUBMISSION HANDLERS =====
|
||||
const createAcceptApprovalHandler = useCallback(
|
||||
async (payload: CreateAcceptApprovalRequestPayload) => {
|
||||
const purchaseRequestId = searchParams.get('purchaseId')
|
||||
? parseInt(searchParams.get('purchaseId')!)
|
||||
: initialValues?.id || 1;
|
||||
|
||||
if (!purchaseRequestId) {
|
||||
setPurchaseOrderFormErrorMessage('Purchase Request ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await PurchaseApi.acceptApproval.create(
|
||||
purchaseRequestId,
|
||||
payload
|
||||
);
|
||||
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
formik.resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[
|
||||
initialValues?.id,
|
||||
searchParams,
|
||||
refreshApprovals,
|
||||
onModalClose,
|
||||
onRefetchData,
|
||||
]
|
||||
);
|
||||
|
||||
const updateAcceptApprovalHandler = useCallback(
|
||||
async (purchaseId: number, payload: CreateAcceptApprovalRequestPayload) => {
|
||||
const res = await PurchaseApi.acceptApproval.create(purchaseId, payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
formik.resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[refreshApprovals, onModalClose, onRefetchData]
|
||||
);
|
||||
|
||||
// ===== SELECT INPUT DATA =====
|
||||
const {
|
||||
options: expeditionVendors,
|
||||
@@ -244,6 +188,67 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { resetForm, setFieldValue } = formik;
|
||||
|
||||
// ===== SUBMISSION HANDLERS =====
|
||||
const createAcceptApprovalHandler = useCallback(
|
||||
async (payload: CreateAcceptApprovalRequestPayload) => {
|
||||
const purchaseRequestId = searchParams.get('purchaseId')
|
||||
? parseInt(searchParams.get('purchaseId')!)
|
||||
: initialValues?.id || 1;
|
||||
|
||||
if (!purchaseRequestId) {
|
||||
setPurchaseOrderFormErrorMessage('Purchase Request ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await PurchaseApi.acceptApproval.create(
|
||||
purchaseRequestId,
|
||||
payload
|
||||
);
|
||||
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[
|
||||
initialValues?.id,
|
||||
searchParams,
|
||||
refreshApprovals,
|
||||
onModalClose,
|
||||
onRefetchData,
|
||||
onCancel,
|
||||
router,
|
||||
resetForm,
|
||||
]
|
||||
);
|
||||
|
||||
const updateAcceptApprovalHandler = useCallback(
|
||||
async (purchaseId: number, payload: CreateAcceptApprovalRequestPayload) => {
|
||||
const res = await PurchaseApi.acceptApproval.create(purchaseId, payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[refreshApprovals, onModalClose, onRefetchData, onCancel, router, resetForm]
|
||||
);
|
||||
|
||||
const handleValidateForm = async () => {
|
||||
const errors = await formik.validateForm();
|
||||
|
||||
@@ -307,9 +312,9 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
transport_per_item: item.transport_per_item || '',
|
||||
};
|
||||
});
|
||||
formik.setFieldValue('items', updatedItems);
|
||||
setFieldValue('items', updatedItems);
|
||||
}
|
||||
}, [purchaseItems, initialValues, key]);
|
||||
}, [purchaseItems, initialValues, key, setFieldValue]);
|
||||
|
||||
// ===== HELPER FUNCTIONS =====
|
||||
const getQuantityExceededError = useCallback(
|
||||
|
||||
@@ -146,120 +146,6 @@ const PurchaseOrderStaffApprovalForm = ({
|
||||
return !item.purchase_item_id || item.purchase_item_id === 0;
|
||||
};
|
||||
|
||||
// ===== SUBMISSION HANDLERS =====
|
||||
const createStaffApprovalHandler = useCallback(
|
||||
async (payload: CreateStaffApprovalRequestPayload) => {
|
||||
const purchaseRequestId = searchParams.get('purchaseId')
|
||||
? parseInt(searchParams.get('purchaseId')!)
|
||||
: initialValues?.id || 1;
|
||||
|
||||
if (!purchaseRequestId) {
|
||||
setPurchaseOrderFormErrorMessage('Purchase Request ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await PurchaseApi.staffApproval.create(
|
||||
purchaseRequestId,
|
||||
payload
|
||||
);
|
||||
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
formik.resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[
|
||||
initialValues?.id,
|
||||
searchParams,
|
||||
refreshApprovals,
|
||||
onModalClose,
|
||||
onRefetchData,
|
||||
]
|
||||
);
|
||||
|
||||
const updateStaffApprovalHandler = useCallback(
|
||||
async (purchaseId: number, payload: UpdateStaffApprovalRequestPayload) => {
|
||||
const res = await PurchaseApi.staffApproval.update(purchaseId, payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
formik.resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[refreshApprovals, onModalClose, onRefetchData]
|
||||
);
|
||||
|
||||
// ===== DELETE HANDLER =====
|
||||
const deleteItemsHandler = useCallback(async () => {
|
||||
const purchaseRequestId = searchParams.get('purchaseId')
|
||||
? parseInt(searchParams.get('purchaseId')!)
|
||||
: initialValues?.id || 1;
|
||||
|
||||
if (!purchaseRequestId) {
|
||||
toast.error('Purchase Request ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemIdsToDelete = selectedItemForDelete
|
||||
? [selectedItemForDelete]
|
||||
: [];
|
||||
|
||||
if (itemIdsToDelete.length === 0) {
|
||||
toast.error('Tidak ada item yang dipilih untuk dihapus');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await PurchaseApi.items.delete(purchaseRequestId, {
|
||||
item_ids: itemIdsToDelete,
|
||||
});
|
||||
|
||||
if (isResponseError(res)) {
|
||||
toast.error(res.message || 'Gagal menghapus item pembelian');
|
||||
return;
|
||||
}
|
||||
|
||||
const successMessage = 'Item pembelian berhasil dihapus';
|
||||
toast.success(successMessage);
|
||||
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
deleteModal.closeModal();
|
||||
setSelectedItemForDelete(null);
|
||||
setSelectedItemIndex(null);
|
||||
|
||||
if (selectedItemIndex !== null) {
|
||||
const updatedPurchaseItems = formik.values.items?.filter(
|
||||
(_, i) => i !== selectedItemIndex
|
||||
);
|
||||
formik.setFieldValue('items', updatedPurchaseItems);
|
||||
}
|
||||
} catch {
|
||||
toast.error('Terjadi kesalahan saat menghapus item pembelian');
|
||||
}
|
||||
}, [
|
||||
initialValues?.id,
|
||||
searchParams,
|
||||
selectedItemForDelete,
|
||||
selectedItemIndex,
|
||||
refreshApprovals,
|
||||
onRefetchData,
|
||||
deleteModal,
|
||||
]);
|
||||
|
||||
// ===== API DATA FETCHING FOR SUPPLIER PRODUCTS =====
|
||||
const { data: supplierData, isLoading: isLoadingSupplierProducts } = useSWR(
|
||||
initialValues?.supplier?.id
|
||||
@@ -418,6 +304,127 @@ const PurchaseOrderStaffApprovalForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { resetForm, setFieldValue } = formik;
|
||||
|
||||
// ===== SUBMISSION HANDLERS =====
|
||||
const createStaffApprovalHandler = useCallback(
|
||||
async (payload: CreateStaffApprovalRequestPayload) => {
|
||||
const purchaseRequestId = searchParams.get('purchaseId')
|
||||
? parseInt(searchParams.get('purchaseId')!)
|
||||
: initialValues?.id || 1;
|
||||
|
||||
if (!purchaseRequestId) {
|
||||
setPurchaseOrderFormErrorMessage('Purchase Request ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await PurchaseApi.staffApproval.create(
|
||||
purchaseRequestId,
|
||||
payload
|
||||
);
|
||||
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[
|
||||
initialValues?.id,
|
||||
searchParams,
|
||||
refreshApprovals,
|
||||
onModalClose,
|
||||
onRefetchData,
|
||||
resetForm,
|
||||
onCancel,
|
||||
router,
|
||||
]
|
||||
);
|
||||
|
||||
const updateStaffApprovalHandler = useCallback(
|
||||
async (purchaseId: number, payload: UpdateStaffApprovalRequestPayload) => {
|
||||
const res = await PurchaseApi.staffApproval.update(purchaseId, payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseOrderFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
resetForm();
|
||||
onCancel?.();
|
||||
onModalClose?.();
|
||||
router.refresh();
|
||||
},
|
||||
[refreshApprovals, onModalClose, onRefetchData, resetForm, onCancel, router]
|
||||
);
|
||||
|
||||
// ===== DELETE HANDLER =====
|
||||
const deleteItemsHandler = useCallback(async () => {
|
||||
const purchaseRequestId = searchParams.get('purchaseId')
|
||||
? parseInt(searchParams.get('purchaseId')!)
|
||||
: initialValues?.id || 1;
|
||||
|
||||
if (!purchaseRequestId) {
|
||||
toast.error('Purchase Request ID is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemIdsToDelete = selectedItemForDelete
|
||||
? [selectedItemForDelete]
|
||||
: [];
|
||||
|
||||
if (itemIdsToDelete.length === 0) {
|
||||
toast.error('Tidak ada item yang dipilih untuk dihapus');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await PurchaseApi.items.delete(purchaseRequestId, {
|
||||
item_ids: itemIdsToDelete,
|
||||
});
|
||||
|
||||
if (isResponseError(res)) {
|
||||
toast.error(res.message || 'Gagal menghapus item pembelian');
|
||||
return;
|
||||
}
|
||||
|
||||
const successMessage = 'Item pembelian berhasil dihapus';
|
||||
toast.success(successMessage);
|
||||
|
||||
refreshApprovals?.();
|
||||
onRefetchData?.();
|
||||
deleteModal.closeModal();
|
||||
setSelectedItemForDelete(null);
|
||||
setSelectedItemIndex(null);
|
||||
|
||||
if (selectedItemIndex !== null) {
|
||||
const updatedPurchaseItems = formik.values.items?.filter(
|
||||
(_, i) => i !== selectedItemIndex
|
||||
);
|
||||
setFieldValue('items', updatedPurchaseItems);
|
||||
}
|
||||
} catch {
|
||||
toast.error('Terjadi kesalahan saat menghapus item pembelian');
|
||||
}
|
||||
}, [
|
||||
initialValues?.id,
|
||||
searchParams,
|
||||
selectedItemForDelete,
|
||||
selectedItemIndex,
|
||||
refreshApprovals,
|
||||
onRefetchData,
|
||||
deleteModal,
|
||||
setFieldValue,
|
||||
formik.values.items,
|
||||
]);
|
||||
|
||||
const handleValidateForm = async () => {
|
||||
const errors = await formik.validateForm();
|
||||
|
||||
@@ -519,9 +526,9 @@ const PurchaseOrderStaffApprovalForm = ({
|
||||
};
|
||||
return itemData;
|
||||
});
|
||||
formik.setFieldValue('items', updatedItems);
|
||||
setFieldValue('items', updatedItems);
|
||||
}
|
||||
}, [purchaseItems, type, initialValues]);
|
||||
}, [purchaseItems, type, initialValues, setFieldValue]);
|
||||
|
||||
// ===== PURCHASE ITEM OPERATIONS =====
|
||||
const addPurchaseItem = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import useSWR from 'swr';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -234,6 +234,8 @@ const PurchaseRequestForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { setFieldValue, setFieldTouched, handleBlur } = formik;
|
||||
|
||||
const handleValidateForm = async () => {
|
||||
const errors = await formik.validateForm();
|
||||
|
||||
@@ -317,51 +319,73 @@ const PurchaseRequestForm = ({
|
||||
};
|
||||
|
||||
// ===== UTILITY FUNCTIONS =====
|
||||
const updateCreditTermBasedOnSupplier = useCallback(
|
||||
(supplierId: number) => {
|
||||
if (supplierId > 0 && isResponseSuccess(supplierRawData)) {
|
||||
const supplierData = supplierRawData.data.find(
|
||||
(s: Supplier) => s.id === supplierId
|
||||
);
|
||||
if (supplierData?.due_date) {
|
||||
formik.setFieldTouched('credit_term', false);
|
||||
formik.setFieldValue('credit_term', supplierData.due_date.toString());
|
||||
} else {
|
||||
formik.setFieldTouched('credit_term', false);
|
||||
formik.setFieldValue('credit_term', '');
|
||||
}
|
||||
} else {
|
||||
formik.setFieldTouched('credit_term', false);
|
||||
formik.setFieldValue('credit_term', '');
|
||||
}
|
||||
},
|
||||
[supplierRawData]
|
||||
);
|
||||
const prevSupplierIdRef = useRef<number | string>('');
|
||||
|
||||
const resetPurchaseItems = useCallback(() => {
|
||||
if (formik.values.items) {
|
||||
formik.values.items.forEach((_, idx) => {
|
||||
formik.setFieldTouched(`items.${idx}.product`, false);
|
||||
formik.setFieldValue(`items.${idx}.product`, null);
|
||||
formik.setFieldTouched(`items.${idx}.product_id`, false);
|
||||
formik.setFieldValue(`items.${idx}.product_id`, 0);
|
||||
formik.setFieldTouched(`items.${idx}.qty`, false);
|
||||
formik.setFieldValue(`items.${idx}.qty`, 0);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ===== SIDE EFFECTS =====
|
||||
useEffect(() => {
|
||||
if (formik.values.supplier_id && Number(formik.values.supplier_id) > 0) {
|
||||
updateCreditTermBasedOnSupplier(Number(formik.values.supplier_id));
|
||||
resetPurchaseItems();
|
||||
} else {
|
||||
formik.setFieldTouched('credit_term', false);
|
||||
formik.setFieldValue('credit_term', '');
|
||||
resetPurchaseItems();
|
||||
const currentSupplierId = formik.values.supplier_id || '';
|
||||
|
||||
if (currentSupplierId === prevSupplierIdRef.current) {
|
||||
return;
|
||||
}
|
||||
}, [formik.values.supplier_id]);
|
||||
|
||||
prevSupplierIdRef.current = currentSupplierId;
|
||||
|
||||
if (currentSupplierId && Number(currentSupplierId) > 0) {
|
||||
if (isResponseSuccess(supplierRawData)) {
|
||||
const supplierData = supplierRawData.data.find(
|
||||
(s: Supplier) => s.id === Number(currentSupplierId)
|
||||
);
|
||||
const newCreditTerm = supplierData?.due_date || 0;
|
||||
if (formik.values.credit_term !== newCreditTerm) {
|
||||
setFieldTouched('credit_term', false);
|
||||
setFieldValue('credit_term', newCreditTerm);
|
||||
}
|
||||
}
|
||||
|
||||
const itemsNeedReset = formik.values.items?.filter(
|
||||
(item) => item.product_id !== 0 || item.product !== null
|
||||
);
|
||||
if (itemsNeedReset && itemsNeedReset.length > 0) {
|
||||
formik.values.items.forEach((item, idx) => {
|
||||
if (item.product_id !== 0 || item.product !== null) {
|
||||
setFieldTouched(`items.${idx}.product`, false);
|
||||
setFieldValue(`items.${idx}.product`, null);
|
||||
setFieldTouched(`items.${idx}.product_id`, false);
|
||||
setFieldValue(`items.${idx}.product_id`, 0);
|
||||
setFieldTouched(`items.${idx}.qty`, false);
|
||||
setFieldValue(`items.${idx}.qty`, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (formik.values.credit_term !== 0) {
|
||||
setFieldTouched('credit_term', false);
|
||||
setFieldValue('credit_term', 0);
|
||||
}
|
||||
const itemsNeedReset = formik.values.items?.filter(
|
||||
(item) => item.product_id !== 0 || item.product !== null
|
||||
);
|
||||
if (itemsNeedReset && itemsNeedReset.length > 0) {
|
||||
formik.values.items.forEach((item, idx) => {
|
||||
if (item.product_id !== 0 || item.product !== null) {
|
||||
setFieldTouched(`items.${idx}.product`, false);
|
||||
setFieldValue(`items.${idx}.product`, null);
|
||||
setFieldTouched(`items.${idx}.product_id`, false);
|
||||
setFieldValue(`items.${idx}.product_id`, 0);
|
||||
setFieldTouched(`items.${idx}.qty`, false);
|
||||
setFieldValue(`items.${idx}.qty`, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
formik.values.supplier_id,
|
||||
formik.values.items,
|
||||
formik.values.credit_term,
|
||||
supplierRawData,
|
||||
setFieldTouched,
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (type !== 'add' && initialValues) {
|
||||
@@ -381,63 +405,63 @@ const PurchaseRequestForm = ({
|
||||
const supplier = val as OptionType | null;
|
||||
const supplierId = Number(supplier?.value);
|
||||
|
||||
formik.setFieldTouched('supplier', true);
|
||||
formik.setFieldValue('supplier', supplier);
|
||||
formik.setFieldTouched('supplier_id', true);
|
||||
formik.setFieldValue('supplier_id', supplierId);
|
||||
setFieldTouched('supplier', true);
|
||||
setFieldValue('supplier', supplier);
|
||||
setFieldTouched('supplier_id', true);
|
||||
setFieldValue('supplier_id', supplierId);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleCreditTermChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
|
||||
formik.setFieldTouched('credit_term', true);
|
||||
formik.setFieldValue('credit_term', value);
|
||||
setFieldTouched('credit_term', true);
|
||||
setFieldValue('credit_term', value);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleCreditTermBlur = useCallback(
|
||||
(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
formik.handleBlur(e);
|
||||
handleBlur(e);
|
||||
},
|
||||
[formik]
|
||||
[handleBlur]
|
||||
);
|
||||
|
||||
const handleAreaChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const area = val as OptionType | null;
|
||||
formik.setFieldTouched('area_id', true);
|
||||
formik.setFieldValue('area_id', (area as OptionType)?.value || 0);
|
||||
formik.setFieldTouched('area', true);
|
||||
formik.setFieldValue('area', area);
|
||||
setFieldTouched('area_id', true);
|
||||
setFieldValue('area_id', (area as OptionType)?.value || 0);
|
||||
setFieldTouched('area', true);
|
||||
setFieldValue('area', area);
|
||||
|
||||
setSelectedArea((area as OptionType)?.value as string);
|
||||
setSelectedLocation('');
|
||||
const disabled = (area as OptionType)?.value == null;
|
||||
setDisabledLocation(disabled);
|
||||
|
||||
formik.setFieldTouched('location_id', false);
|
||||
formik.setFieldValue('location_id', 0);
|
||||
formik.setFieldTouched('location', false);
|
||||
formik.setFieldValue('location', null);
|
||||
setFieldTouched('location_id', false);
|
||||
setFieldValue('location_id', 0);
|
||||
setFieldTouched('location', false);
|
||||
setFieldValue('location', null);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleLocationChange = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const location = val as OptionType | null;
|
||||
formik.setFieldTouched('location_id', true);
|
||||
formik.setFieldValue('location_id', (location as OptionType)?.value || 0);
|
||||
formik.setFieldTouched('location', true);
|
||||
formik.setFieldValue('location', location);
|
||||
setFieldTouched('location_id', true);
|
||||
setFieldValue('location_id', (location as OptionType)?.value || 0);
|
||||
setFieldTouched('location', true);
|
||||
setFieldValue('location', location);
|
||||
|
||||
setSelectedLocation((location as OptionType)?.value as string);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
const handleWarehouseChange = useCallback(
|
||||
@@ -445,12 +469,12 @@ const PurchaseRequestForm = ({
|
||||
const warehouse = val as OptionType | null;
|
||||
const warehouseId = (warehouse as OptionType)?.value || 0;
|
||||
|
||||
formik.setFieldTouched(`items.${idx}.warehouse`, true);
|
||||
formik.setFieldValue(`items.${idx}.warehouse`, warehouse);
|
||||
formik.setFieldTouched(`items.${idx}.warehouse_id`, true);
|
||||
formik.setFieldValue(`items.${idx}.warehouse_id`, warehouseId);
|
||||
setFieldTouched(`items.${idx}.warehouse`, true);
|
||||
setFieldValue(`items.${idx}.warehouse`, warehouse);
|
||||
setFieldTouched(`items.${idx}.warehouse_id`, true);
|
||||
setFieldValue(`items.${idx}.warehouse_id`, warehouseId);
|
||||
},
|
||||
[]
|
||||
[setFieldTouched, setFieldValue]
|
||||
);
|
||||
|
||||
// ===== PURCHASE ITEM OPERATIONS =====
|
||||
|
||||
@@ -351,13 +351,13 @@ const PurchaseOrderDetail = ({
|
||||
refreshApprovals();
|
||||
refetchData?.();
|
||||
staffApprovalModal.closeModal();
|
||||
}, [refreshApprovals, refetchData]);
|
||||
}, [refreshApprovals, refetchData, staffApprovalModal]);
|
||||
|
||||
const handleEditModalClose = useCallback(() => {
|
||||
refreshApprovals();
|
||||
refetchData?.();
|
||||
editModal.closeModal();
|
||||
}, [refreshApprovals, refetchData]);
|
||||
}, [refreshApprovals, refetchData, editModal]);
|
||||
|
||||
// ===== DELETE HANDLER =====
|
||||
const deleteItemsHandler = useCallback(async () => {
|
||||
@@ -399,7 +399,7 @@ const PurchaseOrderDetail = ({
|
||||
deleteModal.closeModal();
|
||||
setSelectedItem(null);
|
||||
setRowSelection({});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
toast.error('Terjadi kesalahan saat menghapus item pembelian');
|
||||
} finally {
|
||||
setIsDeleteLoading(false);
|
||||
@@ -410,6 +410,8 @@ const PurchaseOrderDetail = ({
|
||||
selectedItem,
|
||||
selectedRowIds,
|
||||
refetchData,
|
||||
refreshApprovals,
|
||||
deleteModal,
|
||||
]);
|
||||
|
||||
// ===== APPROVAL/REJECTION HANDLERS =====
|
||||
|
||||
@@ -263,6 +263,7 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
|
||||
<Page size='A4' style={pdfStyles.page}>
|
||||
{/* Header Section */}
|
||||
<View style={pdfStyles.header}>
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<Image
|
||||
src={'https://placehold.co/120x30/png'}
|
||||
style={pdfStyles.logo}
|
||||
|
||||
@@ -30,8 +30,7 @@ const getStatusColor = (action?: string): [number, number, number] => {
|
||||
};
|
||||
|
||||
export const generateReportExpensePDF = async (
|
||||
data: ReportExpense[],
|
||||
params: PDFParams
|
||||
data: ReportExpense[]
|
||||
): Promise<void> => {
|
||||
// Inisialisasi dokumen dengan tipe yang sudah diekstensi
|
||||
const doc = new jsPDF('l', 'mm', 'a4') as jsPDFWithAutoTable;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
@@ -68,37 +74,10 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
// ===== OPTIONS =====
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocations,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Kandang>(LocationApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
setInputValue: setSupplierInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSuppliers,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
setInputValue: setKandangInputValue,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangs,
|
||||
loadMore: loadMoreKandangs,
|
||||
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
setInputValue: setNonstockInputValue,
|
||||
options: nonstockOptions,
|
||||
isLoadingOptions: isLoadingNonstocks,
|
||||
loadMore: loadMoreNonstocks,
|
||||
} = useSelect<Nonstock>(NonstockApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const categoryOptions = useMemo(
|
||||
() => [
|
||||
{ value: 'BOP', label: 'BOP' },
|
||||
@@ -149,6 +128,48 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== OPTIONS =====
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocations,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Kandang>(LocationApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
setInputValue: setSupplierInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSuppliers,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
setInputValue: setKandangInputValue,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangs,
|
||||
loadMore: loadMoreKandangs,
|
||||
} = useSelect<Kandang>(
|
||||
KandangApi.basePath,
|
||||
'id',
|
||||
'name',
|
||||
'search',
|
||||
formik.values.location_id?.value
|
||||
? { location_id: String(formik.values.location_id.value) }
|
||||
: undefined
|
||||
);
|
||||
|
||||
const {
|
||||
setInputValue: setNonstockInputValue,
|
||||
options: nonstockOptions,
|
||||
isLoadingOptions: isLoadingNonstocks,
|
||||
loadMore: loadMoreNonstocks,
|
||||
} = useSelect<Nonstock>(NonstockApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
// ===== FILTER VALUES =====
|
||||
const locationValue = useMemo(
|
||||
() => formik.values.location_id,
|
||||
@@ -268,13 +289,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const pdfParams = {
|
||||
location_name: locationValue?.label,
|
||||
supplier_name: supplierValue?.label,
|
||||
realization_date: formik.values.realization_date || undefined,
|
||||
};
|
||||
|
||||
await generateReportExpensePDF(allData, pdfParams);
|
||||
await generateReportExpensePDF(allData);
|
||||
|
||||
toast.success('PDF berhasil dibuat dan diunduh.');
|
||||
} catch {
|
||||
@@ -282,98 +297,105 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
||||
} finally {
|
||||
setIsPdfExportLoading(false);
|
||||
}
|
||||
}, [
|
||||
reportExpenseExport,
|
||||
locationValue,
|
||||
supplierValue,
|
||||
kandangValue,
|
||||
nonstockValue,
|
||||
categoryValue,
|
||||
formik.values.realization_date,
|
||||
]);
|
||||
}, [reportExpenseExport]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
onClick={() => filterModal.openModal()}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
<span>Export</span>
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
formik.values,
|
||||
filterParams,
|
||||
isAnyExportLoading,
|
||||
handleExportExcel,
|
||||
handleExportPDF,
|
||||
setTabActions,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
// ===== TABLE COLUMNS DEFINITION =====
|
||||
const columns = useMemo((): ColumnDef<ReportExpense>[] => {
|
||||
@@ -505,6 +527,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<ReportExpenseSkeleton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Card from '@/components/Card';
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
formatDate,
|
||||
formatNumber,
|
||||
formatTitleCase,
|
||||
cn,
|
||||
} from '@/lib/helper';
|
||||
import {
|
||||
CustomerPaymentReport,
|
||||
@@ -67,6 +66,8 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||
const [hasDateError, setHasDateError] = useState(false);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
const dataTypeOptions = useMemo(
|
||||
@@ -84,11 +85,6 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
loadMore: loadMoreCustomers,
|
||||
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<CustomerPaymentFilterType>({
|
||||
initialValues: {
|
||||
@@ -123,6 +119,11 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
const getPaymentStatusBadgeColor = (notes: string): Color => {
|
||||
const normalizedValue = notes.toLowerCase();
|
||||
|
||||
@@ -213,7 +214,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
dataTypeOptions.find((opt) => opt.value === formik.values.filter_by) ||
|
||||
null
|
||||
);
|
||||
}, [formik.values.filter_by]);
|
||||
}, [formik.values.filter_by, dataTypeOptions]);
|
||||
|
||||
// ===== DATA FETCHING =====
|
||||
const { data: customerPayment, isLoading } = useSWR(
|
||||
@@ -350,90 +351,104 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
}
|
||||
}, [customerPaymentExport, filterParams, customerOptions]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={handleFilterModalOpen}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
<span>Export</span>
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
formik.values,
|
||||
isAnyExportLoading,
|
||||
handleExportExcel,
|
||||
handleExportPdf,
|
||||
filterModal.open,
|
||||
setTabActions,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
filterParams,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
const getTableColumns = (
|
||||
summary: CustomerPaymentSummary
|
||||
@@ -683,6 +698,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<CustomerSupplierSkeleton
|
||||
|
||||
@@ -17,7 +17,7 @@ import { generateDebtSupplierExcel } from '@/components/pages/report/finance/exp
|
||||
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR from 'swr';
|
||||
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
|
||||
@@ -91,6 +91,8 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||
const [hasDateError, setHasDateError] = useState(false);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
const {
|
||||
@@ -108,11 +110,6 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<DebtSupplierFilterType>({
|
||||
initialValues: {
|
||||
@@ -146,6 +143,11 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== DATA FETCHING =====
|
||||
const { data: debtSupplier, isLoading } = useSWR(
|
||||
isSubmitted
|
||||
@@ -268,92 +270,112 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
||||
} finally {
|
||||
setIsPdfExportLoading(false);
|
||||
}
|
||||
}, [debtSupplierExport]);
|
||||
}, [
|
||||
debtSupplierExport,
|
||||
formik.values.supplierIds,
|
||||
formik.values.filterBy,
|
||||
formik.values.startDate,
|
||||
formik.values.endDate,
|
||||
]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
fieldGroups={[['startDate', 'endDate']]}
|
||||
onClick={handleFilterModalOpen}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
<span>Export</span>
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
formik.values,
|
||||
filterParams,
|
||||
isAnyExportLoading,
|
||||
handleExportExcel,
|
||||
handleExportPdf,
|
||||
setTabActions,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -587,6 +609,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<DebtSupplierSkeleton
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSelect } from '@/components/input/SelectInput';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import Table from '@/components/Table';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import { AreaApi } from '@/services/api/master-data';
|
||||
import { SupplierApi } from '@/services/api/master-data';
|
||||
import { ProductApi } from '@/services/api/master-data';
|
||||
@@ -20,7 +20,7 @@ import { generatePurchasesPerSupplierExcel } from '@/components/pages/report/log
|
||||
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR from 'swr';
|
||||
import { useFormik } from 'formik';
|
||||
@@ -65,6 +65,8 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||
const [hasDateError, setHasDateError] = useState(false);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
// ===== OPTIONS =====
|
||||
@@ -104,11 +106,6 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<PurchasesPerSupplierFilterType>({
|
||||
initialValues: {
|
||||
@@ -151,11 +148,18 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
const { setFieldValue } = formik;
|
||||
|
||||
// ===== DATE CHANGE HANDLERS =====
|
||||
const handleStartDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('start_date', value || null);
|
||||
setFieldValue('start_date', value || null);
|
||||
|
||||
if (value && formik.values.end_date) {
|
||||
const startDate = new Date(value);
|
||||
@@ -180,13 +184,13 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
setHasDateError(false);
|
||||
}
|
||||
},
|
||||
[formik, dateErrorShown]
|
||||
[setFieldValue, dateErrorShown, formik.values.end_date]
|
||||
);
|
||||
|
||||
const handleEndDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('end_date', value || null);
|
||||
setFieldValue('end_date', value || null);
|
||||
|
||||
if (value && formik.values.start_date) {
|
||||
const startDateObj = new Date(formik.values.start_date);
|
||||
@@ -210,7 +214,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
},
|
||||
[formik, dateErrorShown]
|
||||
[setFieldValue, dateErrorShown, formik.values.start_date]
|
||||
);
|
||||
|
||||
// ===== DERIVED VALUES =====
|
||||
@@ -443,88 +447,104 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
productCategoryOptions,
|
||||
]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={handleFilterModalOpen}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
<span>Export</span>
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
formik.values,
|
||||
filterParams,
|
||||
isAnyExportLoading,
|
||||
filterModal.open,
|
||||
setTabActions,
|
||||
handleExportExcel,
|
||||
handleExportPdf,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
const getTableColumns = (
|
||||
summary: LogisticPurchasePerSupplierSummary
|
||||
@@ -704,6 +724,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<PurchasePerSupplierSkeleton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { useSelect } from '@/components/input/SelectInput';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
@@ -83,6 +83,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||
const [hasDateError, setHasDateError] = useState(false);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
// ===== OPTIONS =====
|
||||
@@ -102,11 +104,6 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
const { options: customerOptions, isLoadingOptions: isLoadingCustomers } =
|
||||
useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<DailyMarketingReportFilterType>({
|
||||
initialValues: {
|
||||
@@ -145,6 +142,11 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== SEARCH CHANGE HANDLER =====
|
||||
const searchChangeHandler = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -353,121 +355,126 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
}
|
||||
}, [dailyMarketingsExport, summaryTotal]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffectHook(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3 items-center'>
|
||||
<DebouncedTextInput
|
||||
name='search'
|
||||
placeholder='Search'
|
||||
value={searchValue}
|
||||
onChange={searchChangeHandler}
|
||||
startAdornment={
|
||||
<Icon icon='heroicons:magnifying-glass' width={20} height={20} />
|
||||
}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-48 max-w-3xs',
|
||||
inputWrapper: 'rounded-xl! shadow-button-soft',
|
||||
input: 'placeholder:font-semibold placeholder:text-base-content/50',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={handleFilterModalOpen}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
useEffectHook(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3 items-center'>
|
||||
<DebouncedTextInput
|
||||
name='search'
|
||||
placeholder='Search'
|
||||
value={searchValue}
|
||||
onChange={searchChangeHandler}
|
||||
startAdornment={
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
icon='heroicons:magnifying-glass'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-48 max-w-3xs',
|
||||
inputWrapper: 'rounded-xl! shadow-button-soft',
|
||||
input:
|
||||
'placeholder:font-semibold placeholder:text-base-content/50',
|
||||
}}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
useEffectHook(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
filterParams,
|
||||
searchValue,
|
||||
formik.values,
|
||||
isAnyExportLoading,
|
||||
filterModal.open,
|
||||
setTabActions,
|
||||
handleExportExcel,
|
||||
handleExportPDF,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
searchChangeHandler,
|
||||
]);
|
||||
|
||||
useEffectHook(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
|
||||
useEffectHook(() => {
|
||||
return () => {
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
}
|
||||
};
|
||||
}, [dateErrorShown]);
|
||||
|
||||
useEffectHook(() => {
|
||||
return () => {
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
};
|
||||
}, [filterModal.open, dateErrorShown]);
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
const getTableColumns = (): ColumnDef<DailyMarketingRow>[] => {
|
||||
const tableColumns: ColumnDef<DailyMarketingRow>[] = [
|
||||
@@ -639,6 +646,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<DailyMarketingReportSkeleton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { useSelect } from '@/components/input/SelectInput';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import { cn } from '@/lib/helper';
|
||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||
import HppPerKandangSkeleton from '@/components/pages/report/marketing/skeleton/HppPerKandangSkeleton';
|
||||
import { useEffect as useEffectHook } from 'react';
|
||||
@@ -67,6 +66,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
||||
// ===== FILTER STATE =====
|
||||
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
// ===== OPTIONS =====
|
||||
@@ -96,11 +97,6 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<HppPerKandangFilterType>({
|
||||
initialValues: {
|
||||
@@ -141,6 +137,11 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== WEIGHT CHANGE HANDLERS =====
|
||||
const handleWeightMinChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -443,87 +444,103 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
||||
allDocSuppliers,
|
||||
]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffectHook(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
onClick={handleFilterModalOpen}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
useEffectHook(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
<span>Export</span>
|
||||
useEffectHook(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
formik.values,
|
||||
filterParams,
|
||||
isAnyExportLoading,
|
||||
filterModal.open,
|
||||
setTabActions,
|
||||
handleExportExcel,
|
||||
handleExportPDF,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
]);
|
||||
|
||||
useEffectHook(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
const getTableColumns = (): ColumnDef<HppPerKandangReport['rows'][0]>[] => {
|
||||
const tableColumns: ColumnDef<HppPerKandangReport['rows'][0]>[] = [
|
||||
@@ -768,6 +785,7 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<HppPerKandangSkeleton
|
||||
|
||||
+101
-71
@@ -1,6 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { generateProductionResultExcel } from '../export/ProductionResultExportXLSX';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -37,7 +43,7 @@ import ProductionResultReportPDF from '../export/ProductionResultExportPDF';
|
||||
import { pdf } from '@react-pdf/renderer';
|
||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import { cn, formatNumber } from '@/lib/helper';
|
||||
import { formatNumber } from '@/lib/helper';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import ProductionResultSkeleton from '@/components/pages/report/production-result/skeleton/ProductionResultSkeleton';
|
||||
|
||||
@@ -66,6 +72,8 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
// ===== TABLE COLUMNS =====
|
||||
@@ -242,6 +250,11 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
filterModal.openModal();
|
||||
formik.validateForm();
|
||||
};
|
||||
|
||||
// ===== OPTIONS =====
|
||||
const {
|
||||
setInputValue: setAreaInputValue,
|
||||
@@ -519,91 +532,108 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
||||
setIsPdfExportLoading(false);
|
||||
}, [filterParams]);
|
||||
|
||||
// ===== REGISTER TAB ACTIONS TO STORE =====
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
onClick={() => filterModal.openModal()}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={exportToExcelHandler}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={exportToPdfHandler}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
<span>Export</span>
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={exportToExcelHandler}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={exportToPdfHandler}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
}, [
|
||||
tabId,
|
||||
filterParams,
|
||||
isAnyExportLoading,
|
||||
exportToExcelHandler,
|
||||
exportToPdfHandler,
|
||||
setTabActions,
|
||||
isExcelExportLoading,
|
||||
isPdfExportLoading,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [tabId, clearTabActions]);
|
||||
// Render the TabActions component
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{!isSubmitted ? (
|
||||
<ProductionResultSkeleton
|
||||
|
||||
+1
-1
@@ -263,7 +263,7 @@ const ProductionResultProjectFlockKandangTable = ({
|
||||
updateFilter('filter_by', '');
|
||||
updateFilter('sort_by', '');
|
||||
}
|
||||
}, [sorting]);
|
||||
}, [sorting, updateFilter]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
||||
+1
-1
@@ -272,7 +272,7 @@ export function transformAdjustmentSubtypes(
|
||||
export function transformLegacyFlagAliases(
|
||||
aliases: ConstantsApiResponse['legacy_flag_aliases']
|
||||
): OptionType[] {
|
||||
return Object.entries(aliases).map(([key, value]) => ({
|
||||
return Object.entries(aliases).map(([key]) => ({
|
||||
value: key,
|
||||
label: formatConstantLabel(key),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user