mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 584b495e4b | |||
| c8e76a8558 | |||
| a468a5948b | |||
| e05e1a4121 | |||
| e3b86e3033 | |||
| d467c56ea6 | |||
| 784d9f26ab | |||
| 978ef764ea | |||
| 928136ff18 | |||
| 79567e4f1b | |||
| 633deece21 | |||
| 46483af4c2 | |||
| c2653e5068 | |||
| 8569bda7d6 | |||
| 7da63dc542 | |||
| aeceef4361 | |||
| ff6955be54 | |||
| eccab314b3 |
@@ -66,7 +66,7 @@ const ExpenseRealizationForm = ({
|
|||||||
toast.success(createExpenseRes?.message as string);
|
toast.success(createExpenseRes?.message as string);
|
||||||
router.push('/expense');
|
router.push('/expense');
|
||||||
},
|
},
|
||||||
[router]
|
[router, initialValues?.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateExpenseHandler = useCallback(
|
const updateExpenseHandler = useCallback(
|
||||||
|
|||||||
@@ -178,12 +178,14 @@ const ExpenseRequestForm = ({
|
|||||||
setInputValue: setLocationInputValue,
|
setInputValue: setLocationInputValue,
|
||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocationOptions,
|
isLoadingOptions: isLoadingLocationOptions,
|
||||||
|
loadMore: loadMoreLocationOptions,
|
||||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setVendorInputValue,
|
setInputValue: setVendorInputValue,
|
||||||
options: supplierOptions,
|
options: supplierOptions,
|
||||||
isLoadingOptions: isLoadingVendorOptions,
|
isLoadingOptions: isLoadingVendorOptions,
|
||||||
|
loadMore: loadMoreVendorOptions,
|
||||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
@@ -414,6 +416,7 @@ const ExpenseRequestForm = ({
|
|||||||
errorMessage={formik.errors.location_id as string}
|
errorMessage={formik.errors.location_id as string}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'col-span-12 sm:col-span-4' }}
|
className={{ wrapper: 'col-span-12 sm:col-span-4' }}
|
||||||
|
onMenuScrollToBottom={loadMoreLocationOptions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DateInput
|
<DateInput
|
||||||
@@ -457,6 +460,7 @@ const ExpenseRequestForm = ({
|
|||||||
}
|
}
|
||||||
errorMessage={formik.errors.supplier_id as string}
|
errorMessage={formik.errors.supplier_id as string}
|
||||||
className={{ wrapper: 'col-span-12' }}
|
className={{ wrapper: 'col-span-12' }}
|
||||||
|
onMenuScrollToBottom={loadMoreVendorOptions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RequirePermission permissions='lti.expense.document'>
|
<RequirePermission permissions='lti.expense.document'>
|
||||||
|
|||||||
@@ -1,212 +1,154 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import React from 'react';
|
||||||
Document,
|
import { Document, Page, StyleSheet, View, Text } from '@react-pdf/renderer';
|
||||||
Image,
|
|
||||||
Link,
|
|
||||||
Page,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from '@react-pdf/renderer';
|
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
|
import { PdfTypography } from '@/components/helper/pdf/typography/PdfTypography';
|
||||||
|
import { PdfParamBadge } from '@/components/helper/pdf/badge/PdfParamBadge';
|
||||||
|
import { PdfPageNumber } from '@/components/helper/pdf/layout/PdfPageNumber';
|
||||||
|
import { PdfTable, PdfColumn } from '@/components/helper/pdf/table';
|
||||||
|
|
||||||
interface ExpensePDFProps {
|
interface ExpensePDFProps {
|
||||||
expense?: Expense;
|
expense?: Expense;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpensePDFStyle = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
page: {
|
page: {
|
||||||
paddingTop: 24,
|
fontSize: 10,
|
||||||
paddingBottom: 64,
|
fontFamily: 'Helvetica',
|
||||||
paddingHorizontal: 32,
|
padding: 20,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
},
|
},
|
||||||
|
titleSection: {
|
||||||
companyInfoHeader: {
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
companyLogo: {
|
|
||||||
width: 64,
|
|
||||||
height: 'auto',
|
|
||||||
},
|
|
||||||
companyInfoHeaderDate: {
|
|
||||||
paddingTop: 8,
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
companyName: {
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
companyAddress: {
|
|
||||||
fontSize: 8,
|
|
||||||
maxWidth: 400,
|
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
|
parameterContainer: {
|
||||||
title: {
|
|
||||||
marginTop: 16,
|
|
||||||
fontSize: 16,
|
|
||||||
lineHeight: '150%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontFamily: 'Times-Roman',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
|
|
||||||
footer: {
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
flexWrap: 'wrap',
|
||||||
alignItems: 'center',
|
marginBottom: 8,
|
||||||
paddingHorizontal: 32,
|
},
|
||||||
|
infoTableSection: {
|
||||||
position: 'absolute',
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
infoTableTitle: {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
bottom: 30,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: 'grey',
|
|
||||||
},
|
|
||||||
|
|
||||||
// wrapper
|
|
||||||
generalInfoTable: {
|
|
||||||
width: '100%',
|
|
||||||
marginTop: 8,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#000000',
|
|
||||||
borderBottomWidth: 0,
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
|
|
||||||
generalInfoTableRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#000000',
|
|
||||||
},
|
|
||||||
|
|
||||||
// columns
|
|
||||||
generalInfoTableColLabel: {
|
|
||||||
width: '30%',
|
|
||||||
paddingVertical: 6,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
},
|
|
||||||
generalInfoTableColSeparator: {
|
|
||||||
width: '3%',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 6,
|
|
||||||
},
|
|
||||||
generalInfoTableColValue: {
|
|
||||||
width: '67%',
|
|
||||||
paddingVertical: 6,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
generalInfoTableLabelText: {
|
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 6,
|
||||||
|
color: '#333',
|
||||||
},
|
},
|
||||||
generalInfoTableValueText: {},
|
tableSection: {
|
||||||
|
marginBottom: 12,
|
||||||
// expense detail table
|
|
||||||
expenseDetailContainer: {
|
|
||||||
width: '100%',
|
|
||||||
marginTop: 12,
|
|
||||||
},
|
},
|
||||||
expenseDetailTitle: {
|
tableTitle: {
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: '150%',
|
|
||||||
fontFamily: 'Times-Roman',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
kandangExpenseContainer: {
|
|
||||||
width: '100%',
|
|
||||||
marginTop: 8,
|
|
||||||
},
|
|
||||||
kandangExpenseTitle: {
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: '150%',
|
|
||||||
fontFamily: 'Times-Roman',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
kandangExpenseTable: {
|
|
||||||
width: '100%',
|
|
||||||
marginTop: 8,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#000000',
|
|
||||||
borderBottomWidth: 0,
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
kandangExpenseTableRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#000000',
|
|
||||||
},
|
|
||||||
kandangExpenseTableColLabel: {
|
|
||||||
width: '20%',
|
|
||||||
paddingVertical: 6,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
},
|
|
||||||
kandangExpenseTableColLabelBorderRight: {
|
|
||||||
borderRight: '1px solid #000000',
|
|
||||||
},
|
|
||||||
kandangExpenseTableColNonstock: {
|
|
||||||
width: '20%',
|
|
||||||
},
|
|
||||||
kandangExpenseTableColNote: {
|
|
||||||
width: '40%',
|
|
||||||
},
|
|
||||||
kandangExpenseHeaderLabelText: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
kandangExpenseLabelText: {
|
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 6,
|
||||||
|
color: '#333',
|
||||||
},
|
},
|
||||||
kandangExpenseTableFooterColTotalExpenseCaption: {
|
emptyText: {
|
||||||
width: '40%',
|
fontSize: 8,
|
||||||
paddingVertical: 6,
|
color: '#666',
|
||||||
paddingHorizontal: 8,
|
fontStyle: 'italic',
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
kandangExpenseTableFooterColTotalExpenseValue: {
|
|
||||||
width: '60%',
|
|
||||||
paddingVertical: 6,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
// utils
|
|
||||||
doubleDivider: {
|
|
||||||
width: '100%',
|
|
||||||
height: 6,
|
|
||||||
borderTop: '2px solid black',
|
|
||||||
borderBottom: '2px solid black',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
type ExpenseKandang = Expense['kandangs'][number];
|
||||||
|
type PengajuanItem = NonNullable<ExpenseKandang['pengajuans']>[number];
|
||||||
|
type RealisasiItem = NonNullable<ExpenseKandang['realisasi']>[number];
|
||||||
|
|
||||||
|
const valueText = (v: unknown) => {
|
||||||
|
if (v === null || v === undefined) return '-';
|
||||||
|
if (typeof v === 'number') return formatNumber(v);
|
||||||
|
return String(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPengajuanColumns = (): PdfColumn<PengajuanItem>[] => [
|
||||||
|
{
|
||||||
|
key: 'no',
|
||||||
|
header: 'No',
|
||||||
|
flex: 0.5,
|
||||||
|
align: 'center',
|
||||||
|
cell: ({ index }) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nonstock',
|
||||||
|
header: 'Nonstock',
|
||||||
|
flex: 1.5,
|
||||||
|
cell: ({ row }) => row.nonstock.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'qty',
|
||||||
|
header: 'Kuantitas',
|
||||||
|
flex: 1,
|
||||||
|
align: 'right',
|
||||||
|
cell: ({ row }) => valueText(row.qty),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'price',
|
||||||
|
header: 'Harga Satuan',
|
||||||
|
flex: 1.2,
|
||||||
|
align: 'right',
|
||||||
|
cell: ({ row }) => formatCurrency(row.price),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'notes',
|
||||||
|
header: 'Catatan',
|
||||||
|
flex: 1.5,
|
||||||
|
cell: ({ row }) => row.notes || '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getRealisasiColumns = (): PdfColumn<RealisasiItem>[] => [
|
||||||
|
{
|
||||||
|
key: 'no',
|
||||||
|
header: 'No',
|
||||||
|
flex: 0.5,
|
||||||
|
align: 'center',
|
||||||
|
cell: ({ index }) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nonstock',
|
||||||
|
header: 'Nonstock',
|
||||||
|
flex: 1.5,
|
||||||
|
cell: ({ row }) => row.nonstock.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'qty',
|
||||||
|
header: 'Kuantitas',
|
||||||
|
flex: 1,
|
||||||
|
align: 'right',
|
||||||
|
cell: ({ row }) => valueText(row.qty),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'price',
|
||||||
|
header: 'Harga Satuan',
|
||||||
|
flex: 1.2,
|
||||||
|
align: 'right',
|
||||||
|
cell: ({ row }) => formatCurrency(row.price),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'notes',
|
||||||
|
header: 'Catatan',
|
||||||
|
flex: 1.5,
|
||||||
|
cell: ({ row }) => row.notes || '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getInfoTableRows = (expense?: Expense) => {
|
||||||
const isLatestApprovalRejected =
|
const isLatestApprovalRejected =
|
||||||
expense?.latest_approval?.action === 'REJECTED';
|
expense?.latest_approval?.action === 'REJECTED';
|
||||||
const isExpenseRealized =
|
const isExpenseRealized =
|
||||||
expense?.latest_approval?.step_number &&
|
expense?.latest_approval?.step_number &&
|
||||||
expense?.latest_approval.step_number >= 5;
|
expense?.latest_approval.step_number >= 5;
|
||||||
|
|
||||||
const realizationStatus = isExpenseRealized
|
const realizationStatus = isExpenseRealized
|
||||||
? 'Sudah Realisasi'
|
? 'Sudah Realisasi'
|
||||||
: 'Belum Realisasi';
|
: 'Belum Realisasi';
|
||||||
|
|
||||||
const rows = [
|
return [
|
||||||
{ label: 'Nomor PO', value: expense?.po_number },
|
{ label: 'Nomor PO', value: expense?.po_number || '-' },
|
||||||
{ label: 'Nomor Referensi', value: expense?.reference_number },
|
{ label: 'Nomor Referensi', value: expense?.reference_number || '-' },
|
||||||
{
|
{
|
||||||
label: 'Kategori',
|
label: 'Kategori',
|
||||||
value:
|
value:
|
||||||
@@ -214,9 +156,9 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
? 'Biaya Operasional'
|
? 'Biaya Operasional'
|
||||||
: expense?.category === 'NON-BOP'
|
: expense?.category === 'NON-BOP'
|
||||||
? 'Non Biaya Operasional'
|
? 'Non Biaya Operasional'
|
||||||
: '',
|
: '-',
|
||||||
},
|
},
|
||||||
{ label: 'Lokasi', value: expense?.location.name },
|
{ label: 'Lokasi', value: expense?.location?.name || '-' },
|
||||||
{
|
{
|
||||||
label: 'Kandang',
|
label: 'Kandang',
|
||||||
value:
|
value:
|
||||||
@@ -227,7 +169,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
.join(', ')
|
.join(', ')
|
||||||
: '-',
|
: '-',
|
||||||
},
|
},
|
||||||
{ label: 'Vendor', value: expense?.supplier.name },
|
{ label: 'Vendor', value: expense?.supplier?.name || '-' },
|
||||||
{
|
{
|
||||||
label: 'Tanggal Transaksi',
|
label: 'Tanggal Transaksi',
|
||||||
value: formatDate(expense?.transaction_date, 'DD MMMM YYYY'),
|
value: formatDate(expense?.transaction_date, 'DD MMMM YYYY'),
|
||||||
@@ -238,12 +180,12 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
? formatDate(expense?.realization_date, 'DD MMMM YYYY')
|
? formatDate(expense?.realization_date, 'DD MMMM YYYY')
|
||||||
: '-',
|
: '-',
|
||||||
},
|
},
|
||||||
{ label: 'Nama Pengaju', value: expense?.created_user.name },
|
{ label: 'Nama Pengaju', value: expense?.created_user?.name || '-' },
|
||||||
{
|
{
|
||||||
label: 'Nominal Biaya',
|
label: 'Nominal Biaya',
|
||||||
value: formatCurrency(
|
value: formatCurrency(
|
||||||
expense?.latest_approval.step_number === 5 ||
|
expense?.latest_approval?.step_number === 5 ||
|
||||||
expense?.latest_approval.step_number === 6
|
expense?.latest_approval?.step_number === 6
|
||||||
? (expense?.total_realisasi ?? 0)
|
? (expense?.total_realisasi ?? 0)
|
||||||
: (expense?.total_pengajuan ?? 0)
|
: (expense?.total_pengajuan ?? 0)
|
||||||
),
|
),
|
||||||
@@ -263,401 +205,136 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
label: 'Status Biaya',
|
label: 'Status Biaya',
|
||||||
value: isLatestApprovalRejected
|
value: isLatestApprovalRejected
|
||||||
? 'Ditolak'
|
? 'Ditolak'
|
||||||
: expense?.latest_approval?.step_name,
|
: expense?.latest_approval?.step_name || '-',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface InfoRow {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInfoTableColumns = (): PdfColumn<InfoRow>[] => [
|
||||||
|
{
|
||||||
|
key: 'label',
|
||||||
|
header: 'Field',
|
||||||
|
flex: 1,
|
||||||
|
cell: ({ row }) => row.label,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'value',
|
||||||
|
header: 'Value',
|
||||||
|
flex: 2,
|
||||||
|
cell: ({ row }) => row.value,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
||||||
|
const kandangs = expense?.kandangs || [];
|
||||||
|
const infoRows = getInfoTableRows(expense);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Document>
|
<Document>
|
||||||
<Page style={ExpensePDFStyle.page}>
|
<Page style={styles.page} size='A4'>
|
||||||
<View>
|
{/* Title Section */}
|
||||||
<View style={ExpensePDFStyle.companyInfoHeader}>
|
<View style={styles.titleSection}>
|
||||||
<Image
|
<PdfTypography size='h1' variant='primary'>
|
||||||
style={ExpensePDFStyle.companyLogo}
|
Laporan{' '}
|
||||||
src='/assets/img/lti-logo.png'
|
{expense?.category === 'BOP'
|
||||||
/>
|
? 'Biaya Operasional'
|
||||||
|
: 'Non-Biaya Operasional'}
|
||||||
<Text style={ExpensePDFStyle.companyInfoHeaderDate}>
|
</PdfTypography>
|
||||||
{formatDate(Date.now(), 'DD MMMM YYYY')}
|
<PdfTypography size='h2'>{expense?.po_number || '-'}</PdfTypography>
|
||||||
</Text>
|
<View style={styles.parameterContainer}>
|
||||||
</View>
|
<PdfParamBadge>
|
||||||
|
Tanggal: {formatDate(Date.now(), 'DD MMMM YYYY')}
|
||||||
<View>
|
</PdfParamBadge>
|
||||||
<Text style={ExpensePDFStyle.companyName}>
|
<PdfParamBadge>
|
||||||
PT LUMBUNG TELUR INDONESIA
|
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||||
</Text>
|
</PdfParamBadge>
|
||||||
<Text style={ExpensePDFStyle.companyAddress}>
|
|
||||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
|
||||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={ExpensePDFStyle.doubleDivider} />
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={ExpensePDFStyle.title}>
|
{/* Info Table Section */}
|
||||||
Laporan{' '}
|
<View style={styles.infoTableSection}>
|
||||||
{expense?.category === 'BOP'
|
<Text style={styles.infoTableTitle}>Informasi Biaya</Text>
|
||||||
? 'Biaya Operasional'
|
<PdfTable columns={getInfoTableColumns()} data={infoRows} />
|
||||||
: 'Non-Biaya Operasional'}{' '}
|
|
||||||
{expense?.po_number}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* General info table */}
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTable}>
|
|
||||||
{rows.map((row) => (
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTableRow} key={row.label}>
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTableColLabel}>
|
|
||||||
<Text style={ExpensePDFStyle.generalInfoTableLabelText}>
|
|
||||||
{row.label}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTableColSeparator}>
|
|
||||||
<Text>:</Text>
|
|
||||||
</View>
|
|
||||||
<View style={ExpensePDFStyle.generalInfoTableColValue}>
|
|
||||||
<Text style={ExpensePDFStyle.generalInfoTableValueText}>
|
|
||||||
{row.value}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Detail expense request */}
|
{/* Rincian Pengajuan Section */}
|
||||||
<View
|
<View style={styles.tableSection}>
|
||||||
minPresenceAhead={80}
|
<Text style={styles.tableTitle}>1. Rincian Pengajuan Biaya</Text>
|
||||||
style={ExpensePDFStyle.expenseDetailContainer}
|
{kandangs.length === 0 ? (
|
||||||
>
|
<Text style={styles.emptyText}>Tidak ada data pengajuan.</Text>
|
||||||
<Text style={ExpensePDFStyle.expenseDetailTitle}>
|
) : (
|
||||||
Rincian Pengajuan Biaya Operasional
|
kandangs.map((kandang, idx) => {
|
||||||
</Text>
|
const pengajuans = kandang.pengajuans || [];
|
||||||
|
const kandangName =
|
||||||
|
kandang.kandang_id && kandang.name
|
||||||
|
? kandang.name
|
||||||
|
: expense?.location?.name || 'Umum';
|
||||||
|
|
||||||
{expense?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
|
return (
|
||||||
let expenseRequestTotal = 0;
|
<View key={idx} style={{ marginBottom: 12 }}>
|
||||||
|
<PdfTypography size='h3' style={{ paddingLeft: 12 }}>
|
||||||
kandangExpense.pengajuans?.forEach(
|
{idx + 1}) {kandangName}
|
||||||
(item) => (expenseRequestTotal += item.qty * item.price)
|
</PdfTypography>
|
||||||
);
|
{pengajuans.length > 0 ? (
|
||||||
|
<PdfTable
|
||||||
return (
|
columns={getPengajuanColumns()}
|
||||||
<View
|
data={pengajuans}
|
||||||
key={kandangExpenseIdx}
|
showFooter={true}
|
||||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
footerLabel='Total'
|
||||||
>
|
/>
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
) : (
|
||||||
{kandangExpense.kandang_id && kandangExpense.name
|
<Text style={styles.emptyText}>
|
||||||
? `Biaya ${kandangExpense.name}`
|
Tidak ada item pengajuan untuk kandang ini.
|
||||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
</Text>
|
||||||
</Text>
|
)}
|
||||||
|
|
||||||
<View style={ExpensePDFStyle.kandangExpenseTable}>
|
|
||||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Nonstock
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Kuantitas
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Harga Satuan
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Catatan
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{kandangExpense.pengajuans?.map((pengajuan, pengajuanIdx) => (
|
|
||||||
<View
|
|
||||||
key={pengajuanIdx}
|
|
||||||
style={ExpensePDFStyle.kandangExpenseTableRow}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{pengajuan.nonstock.name}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{formatNumber(pengajuan.qty)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{formatCurrency(pengajuan.price)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{pengajuan.notes}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseCaption,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Total Biaya Keseluruhan
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseValue,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
{formatCurrency(expenseRequestTotal)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
);
|
||||||
);
|
})
|
||||||
})}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Detail expense realization */}
|
{/* Rincian Realisasi Section */}
|
||||||
<View
|
<View style={styles.tableSection}>
|
||||||
minPresenceAhead={80}
|
<Text style={styles.tableTitle}>2. Rincian Realisasi Biaya</Text>
|
||||||
style={ExpensePDFStyle.expenseDetailContainer}
|
{kandangs.length === 0 ? (
|
||||||
>
|
<Text style={styles.emptyText}>Tidak ada data realisasi.</Text>
|
||||||
<Text style={ExpensePDFStyle.expenseDetailTitle}>
|
) : (
|
||||||
Rincian Realisasi Biaya Operasional
|
kandangs.map((kandang, idx) => {
|
||||||
</Text>
|
const realisasi = kandang.realisasi || [];
|
||||||
|
const kandangName =
|
||||||
|
kandang.kandang_id && kandang.name
|
||||||
|
? kandang.name
|
||||||
|
: expense?.location?.name || 'Umum';
|
||||||
|
|
||||||
{expense?.kandangs.map((kandangExpense, kandangExpenseIdx) => {
|
return (
|
||||||
let expenseRealizationTotal = 0;
|
<View key={idx} style={{ marginBottom: 12 }}>
|
||||||
|
<PdfTypography size='h3' style={{ paddingLeft: 12 }}>
|
||||||
kandangExpense.realisasi?.forEach(
|
{idx + 1}) {kandangName}
|
||||||
(item) => (expenseRealizationTotal += item.qty * item.price)
|
</PdfTypography>
|
||||||
);
|
{realisasi.length > 0 ? (
|
||||||
|
<PdfTable
|
||||||
return (
|
columns={getRealisasiColumns()}
|
||||||
<View
|
data={realisasi}
|
||||||
key={kandangExpenseIdx}
|
showFooter={true}
|
||||||
style={ExpensePDFStyle.kandangExpenseContainer}
|
footerLabel='Total'
|
||||||
>
|
/>
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseTitle}>
|
) : (
|
||||||
{kandangExpense.kandang_id && kandangExpense.name
|
<Text style={styles.emptyText}>
|
||||||
? `Biaya ${kandangExpense.name}`
|
Tidak ada item realisasi untuk kandang ini.
|
||||||
: `Biaya ${expense?.location.name || 'Umum'}`}
|
</Text>
|
||||||
</Text>
|
)}
|
||||||
|
|
||||||
<View style={ExpensePDFStyle.kandangExpenseTable}>
|
|
||||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Nonstock
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Kuantitas
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Harga Satuan
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Catatan
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{kandangExpense.realisasi?.map((realisasi, realisasiIdx) => (
|
|
||||||
<View
|
|
||||||
key={realisasiIdx}
|
|
||||||
style={ExpensePDFStyle.kandangExpenseTableRow}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNonstock,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{realisasi.nonstock.name}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{formatNumber(realisasi.qty)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{formatCurrency(realisasi.price)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabel,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColNote,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={ExpensePDFStyle.kandangExpenseLabelText}>
|
|
||||||
{realisasi.notes}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<View style={[ExpensePDFStyle.kandangExpenseTableRow]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseCaption,
|
|
||||||
ExpensePDFStyle.kandangExpenseTableColLabelBorderRight,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
Total Biaya Keseluruhan
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
ExpensePDFStyle.kandangExpenseTableFooterColTotalExpenseValue,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={ExpensePDFStyle.kandangExpenseHeaderLabelText}
|
|
||||||
>
|
|
||||||
{formatCurrency(expenseRealizationTotal)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
);
|
||||||
);
|
})
|
||||||
})}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={ExpensePDFStyle.footer} fixed>
|
<PdfPageNumber />
|
||||||
<Link
|
|
||||||
src={`${process.env.NEXT_PUBLIC_LTI_URL}expense/detail?expenseId=${expense?.id}`}
|
|
||||||
>
|
|
||||||
{expense?.po_number}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
render={({ pageNumber, totalPages }) =>
|
|
||||||
`${pageNumber} / ${totalPages}`
|
|
||||||
}
|
|
||||||
fixed
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Page>
|
</Page>
|
||||||
</Document>
|
</Document>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ const InventoryAdjustmentTable = () => {
|
|||||||
accessorFn: (row) => row.created_user?.name ?? '-',
|
accessorFn: (row) => row.created_user?.name ?? '-',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[tableFilterState.pageSize, tableFilterState.page]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateSortingFilter = useCallback(
|
const updateSortingFilter = useCallback(
|
||||||
|
|||||||
@@ -185,9 +185,7 @@ const InventoryAdjustmentForm = ({
|
|||||||
isLoadingOptions: isLoadingProductOptions,
|
isLoadingOptions: isLoadingProductOptions,
|
||||||
loadMore: loadMoreProducts,
|
loadMore: loadMoreProducts,
|
||||||
rawData: products,
|
rawData: products,
|
||||||
} = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search', {
|
} = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search');
|
||||||
include_all: 'true',
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setDepletionProductInputValue,
|
setInputValue: setDepletionProductInputValue,
|
||||||
|
|||||||
@@ -323,6 +323,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { setFieldValue, setFieldTouched, setFieldError } = formik;
|
||||||
|
|
||||||
const prevSourceWarehouseIdRef = useRef<number | null>(
|
const prevSourceWarehouseIdRef = useRef<number | null>(
|
||||||
formik.values.source_warehouse_id
|
formik.values.source_warehouse_id
|
||||||
);
|
);
|
||||||
@@ -336,14 +338,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
prevSourceWarehouseId !== currentSourceWarehouseId &&
|
prevSourceWarehouseId !== currentSourceWarehouseId &&
|
||||||
prevSourceWarehouseId !== null
|
prevSourceWarehouseId !== null
|
||||||
) {
|
) {
|
||||||
formik.setFieldValue('products', [
|
setFieldValue('products', [
|
||||||
{
|
{
|
||||||
product: null,
|
product: null,
|
||||||
product_id: 0,
|
product_id: 0,
|
||||||
product_qty: '',
|
product_qty: '',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
formik.setFieldTouched('products', false);
|
setFieldTouched('products', false);
|
||||||
|
|
||||||
const updatedDeliveries = formik.values.deliveries.map(
|
const updatedDeliveries = formik.values.deliveries.map(
|
||||||
(delivery: DeliverySchema) => ({
|
(delivery: DeliverySchema) => ({
|
||||||
@@ -357,12 +359,17 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
formik.setFieldValue('deliveries', updatedDeliveries);
|
setFieldValue('deliveries', updatedDeliveries);
|
||||||
formik.setFieldTouched('deliveries', false);
|
setFieldTouched('deliveries', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
prevSourceWarehouseIdRef.current = currentSourceWarehouseId;
|
prevSourceWarehouseIdRef.current = currentSourceWarehouseId;
|
||||||
}, [formik.values.source_warehouse_id, formik.values.deliveries]);
|
}, [
|
||||||
|
formik.values.source_warehouse_id,
|
||||||
|
formik.values.deliveries,
|
||||||
|
setFieldValue,
|
||||||
|
setFieldTouched,
|
||||||
|
]);
|
||||||
|
|
||||||
// ===== PRODUCT WAREHOUSE FETCHING (after form initialization) =====
|
// ===== PRODUCT WAREHOUSE FETCHING (after form initialization) =====
|
||||||
const {
|
const {
|
||||||
@@ -455,9 +462,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
// ===== EVENT HANDLERS =====
|
// ===== EVENT HANDLERS =====
|
||||||
const handleTransferDateChange = useCallback(
|
const handleTransferDateChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
formik.setFieldValue('transfer_date', e.target.value);
|
setFieldValue('transfer_date', e.target.value);
|
||||||
},
|
},
|
||||||
[]
|
[setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSourceWarehouseChange = useCallback(
|
const handleSourceWarehouseChange = useCallback(
|
||||||
@@ -477,14 +484,16 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
formik.setFieldTouched('source_warehouse', true);
|
setFieldTouched('source_warehouse', true);
|
||||||
formik.setFieldValue('source_warehouse', val);
|
setFieldValue('source_warehouse', val);
|
||||||
formik.setFieldTouched('source_warehouse_id', true);
|
setFieldTouched('source_warehouse_id', true);
|
||||||
formik.setFieldValue('source_warehouse_id', newSourceWarehouseId);
|
setFieldValue('source_warehouse_id', newSourceWarehouseId);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
formik.values.destination_warehouse_id,
|
formik.values.destination_warehouse_id,
|
||||||
formik.values.destination_warehouse,
|
formik.values.destination_warehouse,
|
||||||
|
setFieldTouched,
|
||||||
|
setFieldValue,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -505,15 +514,17 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
formik.setFieldTouched('destination_warehouse', true);
|
setFieldTouched('destination_warehouse', true);
|
||||||
formik.setFieldValue('destination_warehouse', val);
|
setFieldValue('destination_warehouse', val);
|
||||||
formik.setFieldTouched('destination_warehouse_id', true);
|
setFieldTouched('destination_warehouse_id', true);
|
||||||
formik.setFieldValue(
|
setFieldValue('destination_warehouse_id', newDestinationWarehouseId);
|
||||||
'destination_warehouse_id',
|
|
||||||
newDestinationWarehouseId
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[formik.values.source_warehouse_id, formik.values.source_warehouse]
|
[
|
||||||
|
formik.values.source_warehouse_id,
|
||||||
|
formik.values.source_warehouse,
|
||||||
|
setFieldTouched,
|
||||||
|
setFieldValue,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const addProduct = useCallback(() => {
|
const addProduct = useCallback(() => {
|
||||||
@@ -525,15 +536,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
product_qty: '',
|
product_qty: '',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
formik.setFieldValue('products', newProducts);
|
setFieldValue('products', newProducts);
|
||||||
}, [formik.values.products]);
|
}, [formik.values.products, setFieldValue]);
|
||||||
|
|
||||||
const removeProduct = useCallback(
|
const removeProduct = useCallback(
|
||||||
(i: number) => {
|
(i: number) => {
|
||||||
const updatedProducts = formik.values.products?.filter(
|
const updatedProducts = formik.values.products?.filter(
|
||||||
(_, idx) => idx !== i
|
(_, idx) => idx !== i
|
||||||
);
|
);
|
||||||
formik.setFieldValue('products', updatedProducts);
|
setFieldValue('products', updatedProducts);
|
||||||
|
|
||||||
setSelectedProducts([]);
|
setSelectedProducts([]);
|
||||||
|
|
||||||
@@ -542,7 +553,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
setProductQtyErrorShown(false);
|
setProductQtyErrorShown(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[formik.values.products, productQtyErrorShown, setSelectedProducts]
|
[
|
||||||
|
formik.values.products,
|
||||||
|
productQtyErrorShown,
|
||||||
|
setSelectedProducts,
|
||||||
|
setFieldValue,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const bulkRemoveProduct = useCallback(() => {
|
const bulkRemoveProduct = useCallback(() => {
|
||||||
@@ -550,26 +566,32 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
formik.values.products?.filter(
|
formik.values.products?.filter(
|
||||||
(_, idx) => !selectedProducts.includes(idx)
|
(_, idx) => !selectedProducts.includes(idx)
|
||||||
) ?? [];
|
) ?? [];
|
||||||
formik.setFieldValue('products', updatedProducts);
|
setFieldValue('products', updatedProducts);
|
||||||
setSelectedProducts([]);
|
setSelectedProducts([]);
|
||||||
|
|
||||||
if (productQtyErrorShown) {
|
if (productQtyErrorShown) {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
setProductQtyErrorShown(false);
|
setProductQtyErrorShown(false);
|
||||||
}
|
}
|
||||||
}, [formik, selectedProducts, setSelectedProducts, productQtyErrorShown]);
|
}, [
|
||||||
|
selectedProducts,
|
||||||
|
setSelectedProducts,
|
||||||
|
productQtyErrorShown,
|
||||||
|
setFieldValue,
|
||||||
|
formik.values.products,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleProductChange = useCallback(
|
const handleProductChange = useCallback(
|
||||||
(idx: number, val: OptionType | OptionType[] | null) => {
|
(idx: number, val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched(`products.${idx}.product`, true);
|
setFieldTouched(`products.${idx}.product`, true);
|
||||||
formik.setFieldValue(`products.${idx}.product`, val);
|
setFieldValue(`products.${idx}.product`, val);
|
||||||
formik.setFieldTouched(`products.${idx}.product_id`, true);
|
setFieldTouched(`products.${idx}.product_id`, true);
|
||||||
formik.setFieldValue(
|
setFieldValue(
|
||||||
`products.${idx}.product_id`,
|
`products.${idx}.product_id`,
|
||||||
(val as ProductWarehouseOptionType)?.value
|
(val as ProductWarehouseOptionType)?.value
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[]
|
[setFieldTouched, setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleProductSelectAllChange = useCallback(
|
const handleProductSelectAllChange = useCallback(
|
||||||
@@ -596,7 +618,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const addDelivery = useCallback(() => {
|
const addDelivery = useCallback(() => {
|
||||||
formik.setFieldValue('deliveries', [
|
setFieldValue('deliveries', [
|
||||||
...(formik.values.deliveries || []),
|
...(formik.values.deliveries || []),
|
||||||
{
|
{
|
||||||
delivery_cost: '',
|
delivery_cost: '',
|
||||||
@@ -615,14 +637,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}, [formik.values.deliveries]);
|
}, [formik.values.deliveries, setFieldValue]);
|
||||||
|
|
||||||
const removeDelivery = useCallback(
|
const removeDelivery = useCallback(
|
||||||
(i: number) => {
|
(i: number) => {
|
||||||
const updatedDeliveries = formik.values.deliveries?.filter(
|
const updatedDeliveries = formik.values.deliveries?.filter(
|
||||||
(_, idx) => idx !== i
|
(_, idx) => idx !== i
|
||||||
);
|
);
|
||||||
formik.setFieldValue('deliveries', updatedDeliveries);
|
setFieldValue('deliveries', updatedDeliveries);
|
||||||
|
|
||||||
setSelectedDeliveries([]);
|
setSelectedDeliveries([]);
|
||||||
|
|
||||||
@@ -631,7 +653,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
setDeliveryQtyErrorShown(false);
|
setDeliveryQtyErrorShown(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[formik.values.deliveries, deliveryQtyErrorShown, setSelectedDeliveries]
|
[
|
||||||
|
formik.values.deliveries,
|
||||||
|
deliveryQtyErrorShown,
|
||||||
|
setSelectedDeliveries,
|
||||||
|
setFieldValue,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const bulkRemoveDelivery = useCallback(() => {
|
const bulkRemoveDelivery = useCallback(() => {
|
||||||
@@ -639,7 +666,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
formik.values.deliveries?.filter(
|
formik.values.deliveries?.filter(
|
||||||
(_, idx) => !selectedDeliveries.includes(idx)
|
(_, idx) => !selectedDeliveries.includes(idx)
|
||||||
) ?? [];
|
) ?? [];
|
||||||
formik.setFieldValue('deliveries', updatedDeliveries);
|
setFieldValue('deliveries', updatedDeliveries);
|
||||||
setSelectedDeliveries([]);
|
setSelectedDeliveries([]);
|
||||||
|
|
||||||
if (deliveryQtyErrorShown) {
|
if (deliveryQtyErrorShown) {
|
||||||
@@ -647,10 +674,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
setDeliveryQtyErrorShown(false);
|
setDeliveryQtyErrorShown(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
formik,
|
|
||||||
selectedDeliveries,
|
selectedDeliveries,
|
||||||
setSelectedDeliveries,
|
setSelectedDeliveries,
|
||||||
deliveryQtyErrorShown,
|
deliveryQtyErrorShown,
|
||||||
|
setFieldValue,
|
||||||
|
formik.values.deliveries,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleDeliverySelectAllChange = useCallback(
|
const handleDeliverySelectAllChange = useCallback(
|
||||||
@@ -680,34 +708,28 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
|
|
||||||
const handleDeliveryProductChange = useCallback(
|
const handleDeliveryProductChange = useCallback(
|
||||||
(deliveryIdx: number, val: OptionType | OptionType[] | null) => {
|
(deliveryIdx: number, val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched(
|
setFieldTouched(`deliveries.${deliveryIdx}.products.0.product`, true);
|
||||||
`deliveries.${deliveryIdx}.products.0.product`,
|
setFieldValue(`deliveries.${deliveryIdx}.products.0.product`, val);
|
||||||
true
|
setFieldTouched(`deliveries.${deliveryIdx}.products.0.product_id`, true);
|
||||||
);
|
setFieldValue(
|
||||||
formik.setFieldValue(`deliveries.${deliveryIdx}.products.0.product`, val);
|
|
||||||
formik.setFieldTouched(
|
|
||||||
`deliveries.${deliveryIdx}.products.0.product_id`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
formik.setFieldValue(
|
|
||||||
`deliveries.${deliveryIdx}.products.0.product_id`,
|
`deliveries.${deliveryIdx}.products.0.product_id`,
|
||||||
(val as OptionType)?.value
|
(val as OptionType)?.value
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[]
|
[setFieldTouched, setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliverySupplierChange = useCallback(
|
const handleDeliverySupplierChange = useCallback(
|
||||||
(deliveryIdx: number, val: OptionType | OptionType[] | null) => {
|
(deliveryIdx: number, val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched(`deliveries.${deliveryIdx}.supplier`, true);
|
setFieldTouched(`deliveries.${deliveryIdx}.supplier`, true);
|
||||||
formik.setFieldValue(`deliveries.${deliveryIdx}.supplier`, val);
|
setFieldValue(`deliveries.${deliveryIdx}.supplier`, val);
|
||||||
formik.setFieldTouched(`deliveries.${deliveryIdx}.supplier_id`, true);
|
setFieldTouched(`deliveries.${deliveryIdx}.supplier_id`, true);
|
||||||
formik.setFieldValue(
|
setFieldValue(
|
||||||
`deliveries.${deliveryIdx}.supplier_id`,
|
`deliveries.${deliveryIdx}.supplier_id`,
|
||||||
(val as OptionType)?.value
|
(val as OptionType)?.value
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[]
|
[setFieldTouched, setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliveryDocumentChange = useCallback(
|
const handleDeliveryDocumentChange = useCallback(
|
||||||
@@ -719,15 +741,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formik.setFieldValue(`deliveries.${deliveryIdx}.document`, file);
|
setFieldValue(`deliveries.${deliveryIdx}.document`, file);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliveryCostChange = useCallback(
|
const handleDeliveryCostChange = useCallback(
|
||||||
(idx: number, value: number) => {
|
(idx: number, value: number) => {
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value);
|
setFieldValue(`deliveries.${idx}.delivery_cost`, value);
|
||||||
|
|
||||||
const delivery = formik.values.deliveries?.[idx];
|
const delivery = formik.values.deliveries?.[idx];
|
||||||
if (delivery) {
|
if (delivery) {
|
||||||
@@ -737,21 +759,18 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
if (productQty > 0 && value > 0) {
|
if (productQty > 0 && value > 0) {
|
||||||
const perItem = value / productQty;
|
const perItem = value / productQty;
|
||||||
formik.setFieldValue(
|
setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, perItem);
|
||||||
`deliveries.${idx}.delivery_cost_per_item`,
|
|
||||||
perItem
|
|
||||||
);
|
|
||||||
} else if (value === 0) {
|
} else if (value === 0) {
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0);
|
setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[formik.values.deliveries]
|
[formik.values.deliveries, setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliveryCostPerItemChange = useCallback(
|
const handleDeliveryCostPerItemChange = useCallback(
|
||||||
(idx: number, value: number) => {
|
(idx: number, value: number) => {
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, value);
|
setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, value);
|
||||||
|
|
||||||
const delivery = formik.values.deliveries?.[idx];
|
const delivery = formik.values.deliveries?.[idx];
|
||||||
if (delivery) {
|
if (delivery) {
|
||||||
@@ -761,13 +780,13 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
if (productQty > 0 && value > 0) {
|
if (productQty > 0 && value > 0) {
|
||||||
const totalCost = value * productQty;
|
const totalCost = value * productQty;
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost);
|
setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost);
|
||||||
} else if (value === 0) {
|
} else if (value === 0) {
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, 0);
|
setFieldValue(`deliveries.${idx}.delivery_cost`, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[formik.values.deliveries]
|
[formik.values.deliveries, setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliveryCostChangeWrapper = useCallback(
|
const handleDeliveryCostChangeWrapper = useCallback(
|
||||||
@@ -1044,12 +1063,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
return !validateDeliveryQty(deliveryIdx, productIdx, qty);
|
return !validateDeliveryQty(deliveryIdx, productIdx, qty);
|
||||||
})
|
})
|
||||||
) ?? []),
|
) ?? []),
|
||||||
[
|
[formik.values.deliveries, validateDeliveryQty, type]
|
||||||
formik.values.deliveries,
|
|
||||||
formik.values.products,
|
|
||||||
validateDeliveryQty,
|
|
||||||
type,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasInvalidQty = useMemo(
|
const hasInvalidQty = useMemo(
|
||||||
@@ -1066,6 +1080,27 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
}, [formik.values.products, getProductQtyError, type]);
|
}, [formik.values.products, getProductQtyError, type]);
|
||||||
|
|
||||||
|
const deliveryCostDepString = useMemo(
|
||||||
|
() =>
|
||||||
|
formik.values.deliveries
|
||||||
|
?.map((d, idx) => ({
|
||||||
|
idx,
|
||||||
|
productQty: d.products.reduce(
|
||||||
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
deliveryCost: parseInt((d.delivery_cost || '').toString()) || 0,
|
||||||
|
deliveryCostPerItem:
|
||||||
|
parseInt((d.delivery_cost_per_item || '').toString()) || 0,
|
||||||
|
}))
|
||||||
|
.map(
|
||||||
|
(item) =>
|
||||||
|
`${item.idx}:${item.productQty}:${item.deliveryCost}:${item.deliveryCostPerItem}`
|
||||||
|
)
|
||||||
|
.join('|'),
|
||||||
|
[formik.values.deliveries]
|
||||||
|
);
|
||||||
|
|
||||||
// ===== EFFECTS =====
|
// ===== EFFECTS =====
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formik.values.deliveries?.forEach((delivery, idx) => {
|
formik.values.deliveries?.forEach((delivery, idx) => {
|
||||||
@@ -1082,36 +1117,16 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
if (deliveryCost > 0 && productQty > 0) {
|
if (deliveryCost > 0 && productQty > 0) {
|
||||||
const perItem = deliveryCost / productQty;
|
const perItem = deliveryCost / productQty;
|
||||||
if (Math.abs(deliveryCostPerItem - perItem) > 0.01) {
|
if (Math.abs(deliveryCostPerItem - perItem) > 0.01) {
|
||||||
formik.setFieldValue(
|
setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, perItem);
|
||||||
`deliveries.${idx}.delivery_cost_per_item`,
|
|
||||||
perItem
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (deliveryCostPerItem > 0 && productQty > 0) {
|
} else if (deliveryCostPerItem > 0 && productQty > 0) {
|
||||||
const totalCost = deliveryCostPerItem * productQty;
|
const totalCost = deliveryCostPerItem * productQty;
|
||||||
if (Math.abs(deliveryCost - totalCost) > 0.01) {
|
if (Math.abs(deliveryCost - totalCost) > 0.01) {
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost);
|
setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [
|
}, [deliveryCostDepString, setFieldValue, formik.values.deliveries]);
|
||||||
formik.values.deliveries
|
|
||||||
?.map((d, idx) => ({
|
|
||||||
idx,
|
|
||||||
productQty: d.products.reduce(
|
|
||||||
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
deliveryCost: parseInt((d.delivery_cost || '').toString()) || 0,
|
|
||||||
deliveryCostPerItem:
|
|
||||||
parseInt((d.delivery_cost_per_item || '').toString()) || 0,
|
|
||||||
}))
|
|
||||||
.map(
|
|
||||||
(item) =>
|
|
||||||
`${item.idx}:${item.productQty}:${item.deliveryCost}:${item.deliveryCostPerItem}`
|
|
||||||
)
|
|
||||||
.join('|'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -1121,7 +1136,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
!isInitialized
|
!isInitialized
|
||||||
) {
|
) {
|
||||||
if (formik.values.products.length === 0) {
|
if (formik.values.products.length === 0) {
|
||||||
formik.setFieldValue('products', [
|
setFieldValue('products', [
|
||||||
{
|
{
|
||||||
product: null,
|
product: null,
|
||||||
product_id: 0,
|
product_id: 0,
|
||||||
@@ -1130,7 +1145,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (formik.values.deliveries.length === 0) {
|
if (formik.values.deliveries.length === 0) {
|
||||||
formik.setFieldValue('deliveries', [
|
setFieldValue('deliveries', [
|
||||||
{
|
{
|
||||||
delivery_cost: undefined,
|
delivery_cost: undefined,
|
||||||
delivery_cost_per_item: undefined,
|
delivery_cost_per_item: undefined,
|
||||||
@@ -1152,7 +1167,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
}
|
}
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
}
|
}
|
||||||
}, [formik.values.source_warehouse_id, isInitialized, type]);
|
}, [
|
||||||
|
formik.values.source_warehouse_id,
|
||||||
|
isInitialized,
|
||||||
|
type,
|
||||||
|
setFieldValue,
|
||||||
|
formik.values.products.length,
|
||||||
|
formik.values.deliveries.length,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -1161,7 +1183,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
formik.values.source_warehouse_id ===
|
formik.values.source_warehouse_id ===
|
||||||
formik.values.destination_warehouse_id
|
formik.values.destination_warehouse_id
|
||||||
) {
|
) {
|
||||||
formik.setFieldError(
|
setFieldError(
|
||||||
'destination_warehouse_id',
|
'destination_warehouse_id',
|
||||||
'Gudang tujuan tidak boleh sama dengan gudang asal!'
|
'Gudang tujuan tidak boleh sama dengan gudang asal!'
|
||||||
);
|
);
|
||||||
@@ -1170,13 +1192,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
formik.errors.destination_warehouse_id ===
|
formik.errors.destination_warehouse_id ===
|
||||||
'Gudang tujuan tidak boleh sama dengan gudang asal!'
|
'Gudang tujuan tidak boleh sama dengan gudang asal!'
|
||||||
) {
|
) {
|
||||||
formik.setFieldError('destination_warehouse_id', undefined);
|
setFieldError('destination_warehouse_id', undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
formik.values.source_warehouse_id,
|
formik.values.source_warehouse_id,
|
||||||
formik.values.destination_warehouse_id,
|
formik.values.destination_warehouse_id,
|
||||||
formik.errors.destination_warehouse_id,
|
formik.errors.destination_warehouse_id,
|
||||||
|
setFieldError,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1212,29 +1235,37 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
formik.setFieldValue('deliveries', updatedDeliveries);
|
setFieldValue('deliveries', updatedDeliveries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [formik.values.products]);
|
}, [formik.values.products, formik.values.deliveries, setFieldValue]);
|
||||||
|
|
||||||
|
const productQtyDepString = useMemo(
|
||||||
|
() => formik.values.products?.map((p) => p.product_qty).join(','),
|
||||||
|
[formik.values.products]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (productQtyErrorShown) {
|
if (productQtyErrorShown) {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
setProductQtyErrorShown(false);
|
setProductQtyErrorShown(false);
|
||||||
}
|
}
|
||||||
}, [formik.values.products?.map((p) => p.product_qty).join(',')]);
|
}, [productQtyErrorShown]);
|
||||||
|
|
||||||
|
const deliveryProductQtyDepString = useMemo(
|
||||||
|
() =>
|
||||||
|
formik.values.deliveries
|
||||||
|
?.map((d) => d.products.map((p) => p.product_qty).join(','))
|
||||||
|
.join('|'),
|
||||||
|
[formik.values.deliveries]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (deliveryQtyErrorShown) {
|
if (deliveryQtyErrorShown) {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
setDeliveryQtyErrorShown(false);
|
setDeliveryQtyErrorShown(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [deliveryProductQtyDepString, productQtyDepString, deliveryQtyErrorShown]);
|
||||||
formik.values.deliveries
|
|
||||||
?.map((d) => d.products.map((p) => p.product_qty).join(','))
|
|
||||||
.join('|'),
|
|
||||||
formik.values.products?.map((p) => p.product_qty).join(','),
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasExceededStock && !productQtyErrorShown && type !== 'detail') {
|
if (hasExceededStock && !productQtyErrorShown && type !== 'detail') {
|
||||||
|
|||||||
@@ -536,9 +536,13 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
|||||||
formModal.closeModal();
|
formModal.closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasLoadedInitialValues = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getFilledInitialValues = async () => {
|
const getFilledInitialValues = async () => {
|
||||||
if (marketingId && isResponseSuccess(marketing)) {
|
if (marketingId && isResponseSuccess(marketing)) {
|
||||||
|
if (hasLoadedInitialValues.current) return;
|
||||||
|
hasLoadedInitialValues.current = true;
|
||||||
|
|
||||||
const filledInitialValues = await getFilledMarketingFormInitialValues(
|
const filledInitialValues = await getFilledMarketingFormInitialValues(
|
||||||
marketing.data
|
marketing.data
|
||||||
);
|
);
|
||||||
@@ -582,9 +586,15 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
|||||||
setFormErrorMessage('');
|
setFormErrorMessage('');
|
||||||
}, [step]);
|
}, [step]);
|
||||||
|
|
||||||
// sync delivery order values to formik
|
const prevDeliveryOrderValuesRef = useRef(deliveryOrderValues);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formik.setFieldValue('delivery_order', deliveryOrderValues);
|
if (
|
||||||
|
JSON.stringify(prevDeliveryOrderValuesRef.current) !==
|
||||||
|
JSON.stringify(deliveryOrderValues)
|
||||||
|
) {
|
||||||
|
prevDeliveryOrderValuesRef.current = deliveryOrderValues;
|
||||||
|
formik.setFieldValue('delivery_order', deliveryOrderValues);
|
||||||
|
}
|
||||||
}, [deliveryOrderValues]);
|
}, [deliveryOrderValues]);
|
||||||
|
|
||||||
const grandTotal = useMemo(() => {
|
const grandTotal = useMemo(() => {
|
||||||
|
|||||||
@@ -226,11 +226,6 @@ const MarketingTable = () => {
|
|||||||
confirmationModal.openModal();
|
confirmationModal.openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const productsClickHandler = (item: Marketing) => {
|
|
||||||
setSelectedItem(item);
|
|
||||||
productsModal.openModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteMarketingHandler = async () => {
|
const deleteMarketingHandler = async () => {
|
||||||
const deleteMarketingRes = await MarketingApi.delete(
|
const deleteMarketingRes = await MarketingApi.delete(
|
||||||
selectedItem?.id as number
|
selectedItem?.id as number
|
||||||
@@ -450,6 +445,11 @@ const MarketingTable = () => {
|
|||||||
accessorKey: 'marketing_products.length',
|
accessorKey: 'marketing_products.length',
|
||||||
header: 'Product Details',
|
header: 'Product Details',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
const productsClickHandler = (item: Marketing) => {
|
||||||
|
setSelectedItem(item);
|
||||||
|
productsModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
if (props?.row?.original?.sales_order?.length) {
|
if (props?.row?.original?.sales_order?.length) {
|
||||||
if (props?.row?.original?.sales_order?.length > 1) {
|
if (props?.row?.original?.sales_order?.length > 1) {
|
||||||
return (
|
return (
|
||||||
@@ -504,7 +504,7 @@ const MarketingTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, [deleteModal, deliveryModal, setSelectedItem, productsModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -458,9 +458,13 @@ const SalesOrderFormModal = ({
|
|||||||
);
|
);
|
||||||
}, [memoSalesOrder]);
|
}, [memoSalesOrder]);
|
||||||
|
|
||||||
|
const hasLoadedInitialValues = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getFilledInitialValues = async () => {
|
const getFilledInitialValues = async () => {
|
||||||
if (marketingId && isResponseSuccess(marketing)) {
|
if (marketingId && isResponseSuccess(marketing)) {
|
||||||
|
if (hasLoadedInitialValues.current) return;
|
||||||
|
hasLoadedInitialValues.current = true;
|
||||||
|
|
||||||
const filledInitialValues = await getFilledMarketingFormInitialValues(
|
const filledInitialValues = await getFilledMarketingFormInitialValues(
|
||||||
marketing.data
|
marketing.data
|
||||||
);
|
);
|
||||||
|
|||||||
+26
-15
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
DeliveryOrderProductFormValues,
|
DeliveryOrderProductFormValues,
|
||||||
DeliveryOrderProductSchema,
|
DeliveryOrderProductSchema,
|
||||||
@@ -224,6 +224,8 @@ const DeliveryOrderProductForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { resetForm } = formik;
|
||||||
|
|
||||||
const hasWeekField = useMemo(() => {
|
const hasWeekField = useMemo(() => {
|
||||||
const marketingType = formik.values.marketing_type?.value?.toLowerCase();
|
const marketingType = formik.values.marketing_type?.value?.toLowerCase();
|
||||||
if (marketingType === 'ayam_pullet') {
|
if (marketingType === 'ayam_pullet') {
|
||||||
@@ -243,9 +245,9 @@ const DeliveryOrderProductForm = ({
|
|||||||
return false;
|
return false;
|
||||||
}, [formik.values.marketing_product, formik.values.marketing_type]);
|
}, [formik.values.marketing_product, formik.values.marketing_type]);
|
||||||
|
|
||||||
const handleResetForm = () => {
|
const handleResetForm = useCallback(() => {
|
||||||
setFormErrorMessage('');
|
setFormErrorMessage('');
|
||||||
formik.resetForm({
|
resetForm({
|
||||||
values: {
|
values: {
|
||||||
delivery_date: '',
|
delivery_date: '',
|
||||||
vehicle_number: '',
|
vehicle_number: '',
|
||||||
@@ -269,17 +271,20 @@ const DeliveryOrderProductForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// setSelectedProduct(null);
|
// setSelectedProduct(null);
|
||||||
};
|
}, [resetForm]);
|
||||||
|
|
||||||
const handleBlurField = (field: string) => {
|
const handleBlurField = useCallback(
|
||||||
setCurrentInput(field);
|
(field: string) => {
|
||||||
|
setCurrentInput(field);
|
||||||
|
|
||||||
handleMarketingCalculation(field, {
|
handleMarketingCalculation(field, {
|
||||||
values: formik.values,
|
values: formik.values,
|
||||||
setFieldValue: formik.setFieldValue,
|
setFieldValue: formik.setFieldValue,
|
||||||
hasSisaBerat,
|
hasSisaBerat,
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
[formik.values, formik.setFieldValue, hasSisaBerat]
|
||||||
|
);
|
||||||
|
|
||||||
// Handler untuk onChange - auto calculation real-time untuk field yang mempengaruhi total_price (total_peti, weight_per_convertion, price_per_convertion, sisa_berat, price_sisa_berat, price_per_qty, qty)
|
// Handler untuk onChange - auto calculation real-time untuk field yang mempengaruhi total_price (total_peti, weight_per_convertion, price_per_convertion, sisa_berat, price_sisa_berat, price_per_qty, qty)
|
||||||
const handleFieldChange = (
|
const handleFieldChange = (
|
||||||
@@ -324,8 +329,12 @@ const DeliveryOrderProductForm = ({
|
|||||||
|
|
||||||
const { setValues: setFormikValues } = formik;
|
const { setValues: setFormikValues } = formik;
|
||||||
|
|
||||||
|
const processedInitialValuesRef = useRef<number | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialValues) {
|
if (initialValues) {
|
||||||
|
if (processedInitialValuesRef.current === initialValues.id) return;
|
||||||
|
processedInitialValuesRef.current = initialValues.id as number;
|
||||||
|
|
||||||
if (!Boolean(initialValues.qty)) {
|
if (!Boolean(initialValues.qty)) {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
} else {
|
} else {
|
||||||
@@ -338,7 +347,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialValues]);
|
}, [handleResetForm, initialValues, setFormikValues]);
|
||||||
|
|
||||||
// ===== Formik Error List =====
|
// ===== Formik Error List =====
|
||||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(
|
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(
|
||||||
@@ -356,8 +365,10 @@ const DeliveryOrderProductForm = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleBlurField('week');
|
if (formik.values.week) {
|
||||||
}, [formik.values.week]);
|
handleBlurField('week');
|
||||||
|
}
|
||||||
|
}, [formik.values.week, handleBlurField]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
+16
-11
@@ -5,7 +5,7 @@ import {
|
|||||||
SalesOrderProductFormValues,
|
SalesOrderProductFormValues,
|
||||||
SalesOrderProductSchema,
|
SalesOrderProductSchema,
|
||||||
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||||
import { RefObject, useEffect, useMemo, useState } from 'react';
|
import { RefObject, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { WarehouseApi } from '@/services/api/master-data';
|
import { WarehouseApi } from '@/services/api/master-data';
|
||||||
@@ -240,15 +240,18 @@ const SalesOrderProductForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlurField = (field: string) => {
|
const handleBlurField = useCallback(
|
||||||
setCurrentInput(field);
|
(field: string) => {
|
||||||
|
setCurrentInput(field);
|
||||||
|
|
||||||
handleMarketingCalculation(field, {
|
handleMarketingCalculation(field, {
|
||||||
values: formik.values,
|
values: formik.values,
|
||||||
setFieldValue: formik.setFieldValue,
|
setFieldValue: formik.setFieldValue,
|
||||||
hasSisaBerat,
|
hasSisaBerat,
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
[formik.values, formik.setFieldValue, hasSisaBerat]
|
||||||
|
);
|
||||||
|
|
||||||
// Handler untuk onChange - auto calculation real-time untuk field yang mempengaruhi total_price (total_peti, weight_per_convertion, price_per_convertion, sisa_berat, price_sisa_berat, price_per_qty, qty)
|
// Handler untuk onChange - auto calculation real-time untuk field yang mempengaruhi total_price (total_peti, weight_per_convertion, price_per_convertion, sisa_berat, price_sisa_berat, price_per_qty, qty)
|
||||||
const handleFieldChange = (
|
const handleFieldChange = (
|
||||||
@@ -307,8 +310,10 @@ const SalesOrderProductForm = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleBlurField('week');
|
if (formik.values.week) {
|
||||||
}, [formik.values.week]);
|
handleBlurField('week');
|
||||||
|
}
|
||||||
|
}, [formik.values.week, handleBlurField]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useMemo, useState } from 'react';
|
|||||||
import { formatDate, formatNumber, formatVechicleNumber } from '@/lib/helper';
|
import { formatDate, formatNumber, formatVechicleNumber } from '@/lib/helper';
|
||||||
import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles';
|
import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
interface DeliveryOrderExportProps {
|
interface DeliveryOrderExportProps {
|
||||||
data?: Marketing;
|
data?: Marketing;
|
||||||
@@ -20,9 +19,6 @@ const DeliveryOrderExport = ({
|
|||||||
}: DeliveryOrderExportProps) => {
|
}: DeliveryOrderExportProps) => {
|
||||||
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
||||||
const salesData = data;
|
const salesData = data;
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const action = searchParams.get('action');
|
|
||||||
const id = searchParams.get('id');
|
|
||||||
|
|
||||||
const handleDownloadPDF = async () => {
|
const handleDownloadPDF = async () => {
|
||||||
if (!salesData) {
|
if (!salesData) {
|
||||||
@@ -53,7 +49,6 @@ const DeliveryOrderExport = ({
|
|||||||
toast.error('Failed to generate PDF. Please try again.');
|
toast.error('Failed to generate PDF. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsGeneratingPDF(false);
|
setIsGeneratingPDF(false);
|
||||||
window.location.href = `/marketing?action=${action}&id=${id}`;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,7 +87,7 @@ const PDFDocument = ({
|
|||||||
return (
|
return (
|
||||||
deliveryOrder.deliveries?.reduce((a, b) => a + b.total_price, 0) ?? 0
|
deliveryOrder.deliveries?.reduce((a, b) => a + b.total_price, 0) ?? 0
|
||||||
);
|
);
|
||||||
}, []);
|
}, [deliveryOrder.deliveries]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Document>
|
<Document>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useMemo, useState } from 'react';
|
|||||||
import { formatDate, formatNumber } from '@/lib/helper';
|
import { formatDate, formatNumber } from '@/lib/helper';
|
||||||
import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles';
|
import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
interface SalesOrderExportProps {
|
interface SalesOrderExportProps {
|
||||||
data?: Marketing;
|
data?: Marketing;
|
||||||
@@ -16,9 +15,6 @@ interface SalesOrderExportProps {
|
|||||||
const SalesOrderExport = ({ data }: SalesOrderExportProps) => {
|
const SalesOrderExport = ({ data }: SalesOrderExportProps) => {
|
||||||
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
||||||
const salesData = data;
|
const salesData = data;
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const action = searchParams.get('action');
|
|
||||||
const id = searchParams.get('id');
|
|
||||||
|
|
||||||
const handleDownloadPDF = async () => {
|
const handleDownloadPDF = async () => {
|
||||||
if (!salesData) {
|
if (!salesData) {
|
||||||
@@ -47,7 +43,6 @@ const SalesOrderExport = ({ data }: SalesOrderExportProps) => {
|
|||||||
toast.error('Failed to generate PDF. Please try again.');
|
toast.error('Failed to generate PDF. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsGeneratingPDF(false);
|
setIsGeneratingPDF(false);
|
||||||
window.location.href = `/marketing?action=${action}&id=${id}`;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ const ChickinFormKandang = ({
|
|||||||
|
|
||||||
const afterSubmitFormChickin = () => {
|
const afterSubmitFormChickin = () => {
|
||||||
setOpenChickin(true);
|
setOpenChickin(true);
|
||||||
afterSubmit && afterSubmit();
|
if (afterSubmit) {
|
||||||
|
afterSubmit();
|
||||||
|
}
|
||||||
refreshApprovals();
|
refreshApprovals();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const ChickinLogsView = ({
|
|||||||
rawDataApprovals: BaseApproval[];
|
rawDataApprovals: BaseApproval[];
|
||||||
}) => {
|
}) => {
|
||||||
const [chickinErrorMessage, setChickinErrorMessage] = useState('');
|
const [chickinErrorMessage, setChickinErrorMessage] = useState('');
|
||||||
const { openChickinApproveModal, openChickinDeleteModal } = useChickinStore();
|
const { openChickinApproveModal } = useChickinStore();
|
||||||
|
|
||||||
const handleClickApprove = () => {
|
const handleClickApprove = () => {
|
||||||
openChickinApproveModal(initialValues, async (notes?: string) => {
|
openChickinApproveModal(initialValues, async (notes?: string) => {
|
||||||
@@ -40,21 +40,8 @@ const ChickinLogsView = ({
|
|||||||
toast.error(approveChickinRes?.message as string);
|
toast.error(approveChickinRes?.message as string);
|
||||||
setChickinErrorMessage(approveChickinRes?.message as string);
|
setChickinErrorMessage(approveChickinRes?.message as string);
|
||||||
}
|
}
|
||||||
afterSubmit && afterSubmit();
|
if (afterSubmit) {
|
||||||
});
|
afterSubmit();
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteChickin = (chickinId: number) => {
|
|
||||||
openChickinDeleteModal(chickinId, async () => {
|
|
||||||
const deleteRes = await ChickinApi.delete(chickinId);
|
|
||||||
|
|
||||||
if (isResponseSuccess(deleteRes)) {
|
|
||||||
toast.success(deleteRes?.message || 'Chickin berhasil dihapus');
|
|
||||||
afterSubmit && afterSubmit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isResponseError(deleteRes)) {
|
|
||||||
toast.error(deleteRes?.message || 'Gagal menghapus chickin');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -101,30 +88,14 @@ const ChickinLogsView = ({
|
|||||||
<div className='text-lg font-semibold'>
|
<div className='text-lg font-semibold'>
|
||||||
Chick In #{index + 1} - {latestApproval?.step_number}
|
Chick In #{index + 1} - {latestApproval?.step_number}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-row gap-2 items-center'>
|
<PillBadge
|
||||||
<PillBadge
|
content={
|
||||||
content={
|
isApproved ? 'Disetujui' : isPending ? 'Pending' : '-'
|
||||||
isApproved ? 'Disetujui' : isPending ? 'Pending' : '-'
|
}
|
||||||
}
|
color={
|
||||||
color={
|
isApproved ? 'green' : isPending ? 'yellow' : 'gray'
|
||||||
isApproved ? 'green' : isPending ? 'yellow' : 'gray'
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
{isApproved && (
|
|
||||||
<Button
|
|
||||||
color='error'
|
|
||||||
className='w-fit text-sm text-base-100 rounded-lg shadow-sm btn-xs!'
|
|
||||||
onClick={() => handleDeleteChickin(chickin.id)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='heroicons:trash-solid'
|
|
||||||
width={10}
|
|
||||||
height={10}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tanggal Chick In */}
|
{/* Tanggal Chick In */}
|
||||||
|
|||||||
@@ -200,7 +200,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
const confirmModal = useModal();
|
const confirmModal = useModal();
|
||||||
const successModal = useModal();
|
const successModal = useModal();
|
||||||
const chickinApproveModal = useModal();
|
const chickinApproveModal = useModal();
|
||||||
const chickinDeleteModal = useModal();
|
|
||||||
const closingModal = useModal();
|
const closingModal = useModal();
|
||||||
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
|
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
|
||||||
'APPROVED'
|
'APPROVED'
|
||||||
@@ -215,11 +214,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
chickinApproveCallback,
|
chickinApproveCallback,
|
||||||
closeChickinApproveModal,
|
closeChickinApproveModal,
|
||||||
setChickinApproveLoading,
|
setChickinApproveLoading,
|
||||||
isChickinDeleteModalOpen,
|
|
||||||
isChickinDeleteLoading,
|
|
||||||
chickinDeleteCallback,
|
|
||||||
closeChickinDeleteModal,
|
|
||||||
setChickinDeleteLoading,
|
|
||||||
} = useChickinStore();
|
} = useChickinStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -484,14 +478,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
}
|
}
|
||||||
}, [isChickinApproveModalOpen, chickinApproveModal]);
|
}, [isChickinApproveModalOpen, chickinApproveModal]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isChickinDeleteModalOpen) {
|
|
||||||
chickinDeleteModal.openModal();
|
|
||||||
} else {
|
|
||||||
chickinDeleteModal.closeModal();
|
|
||||||
}
|
|
||||||
}, [isChickinDeleteModalOpen, chickinDeleteModal]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isClosingModalOpen) {
|
if (isClosingModalOpen) {
|
||||||
closingModal.openModal();
|
closingModal.openModal();
|
||||||
@@ -1222,38 +1208,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Chickin Delete Modal */}
|
|
||||||
<ConfirmationModal
|
|
||||||
ref={chickinDeleteModal.ref}
|
|
||||||
type='error'
|
|
||||||
text='Apakah anda yakin ingin menghapus data chick in ini?'
|
|
||||||
secondaryButton={{
|
|
||||||
text: 'Tidak',
|
|
||||||
onClick: () => {
|
|
||||||
closeChickinDeleteModal();
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
className={{
|
|
||||||
modal: 'z-9999',
|
|
||||||
}}
|
|
||||||
primaryButton={{
|
|
||||||
text: 'Ya',
|
|
||||||
color: 'error',
|
|
||||||
isLoading: isChickinDeleteLoading,
|
|
||||||
onClick: async () => {
|
|
||||||
if (chickinDeleteCallback) {
|
|
||||||
setChickinDeleteLoading(true);
|
|
||||||
try {
|
|
||||||
await chickinDeleteCallback();
|
|
||||||
} finally {
|
|
||||||
setChickinDeleteLoading(false);
|
|
||||||
closeChickinDeleteModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Filter Modal */}
|
{/* Filter Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
ref={filterModal.ref}
|
ref={filterModal.ref}
|
||||||
|
|||||||
@@ -104,19 +104,8 @@ const RowOptionsMenu = ({
|
|||||||
return recording.approval?.action === 'REJECTED';
|
return recording.approval?.action === 'REJECTED';
|
||||||
};
|
};
|
||||||
|
|
||||||
const isRecordingEditable = (recording: Recording) => {
|
|
||||||
if (recording.project_flock?.project_flock_category === 'GROWING') {
|
|
||||||
if (recording.transfer_executed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return recording.population_can_change === true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isApproved = isRecordingApproved(props.row.original);
|
const isApproved = isRecordingApproved(props.row.original);
|
||||||
const isRejected = isRecordingRejected(props.row.original);
|
const isRejected = isRecordingRejected(props.row.original);
|
||||||
const isEditable = isRecordingEditable(props.row.original);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
@@ -149,20 +138,18 @@ const RowOptionsMenu = ({
|
|||||||
View Details
|
View Details
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
{isEditable && (
|
<RequirePermission permissions='lti.production.recording.update'>
|
||||||
<RequirePermission permissions='lti.production.recording.update'>
|
<Button
|
||||||
<Button
|
href={`/production/recording/detail/edit/?recordingId=${props.row.original.id}`}
|
||||||
href={`/production/recording/detail/edit/?recordingId=${props.row.original.id}`}
|
variant='ghost'
|
||||||
variant='ghost'
|
color='none'
|
||||||
color='none'
|
className='p-3 justify-start text-sm font-semibold w-full'
|
||||||
className='p-3 justify-start text-sm font-semibold w-full'
|
onClick={closePopover}
|
||||||
onClick={closePopover}
|
>
|
||||||
>
|
<Icon icon='mdi:pencil-outline' width={20} height={20} />
|
||||||
<Icon icon='mdi:pencil-outline' width={20} height={20} />
|
Edit
|
||||||
Edit
|
</Button>
|
||||||
</Button>
|
</RequirePermission>
|
||||||
</RequirePermission>
|
|
||||||
)}
|
|
||||||
{!isApproved && !isRejected && (
|
{!isApproved && !isRejected && (
|
||||||
<RequirePermission permissions='lti.production.recording.approve'>
|
<RequirePermission permissions='lti.production.recording.approve'>
|
||||||
<Button
|
<Button
|
||||||
@@ -195,22 +182,20 @@ const RowOptionsMenu = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
{isEditable && (
|
<RequirePermission permissions='lti.production.recording.delete'>
|
||||||
<RequirePermission permissions='lti.production.recording.delete'>
|
<Button
|
||||||
<Button
|
onClick={() => {
|
||||||
onClick={() => {
|
deleteClickHandler();
|
||||||
deleteClickHandler();
|
closePopover();
|
||||||
closePopover();
|
}}
|
||||||
}}
|
variant='ghost'
|
||||||
variant='ghost'
|
color='error'
|
||||||
color='error'
|
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
||||||
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
>
|
||||||
>
|
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
||||||
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
Delete
|
||||||
Delete
|
</Button>
|
||||||
</Button>
|
</RequirePermission>
|
||||||
</RequirePermission>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ type RecordingGrowingFormSchemaType = {
|
|||||||
} | null;
|
} | null;
|
||||||
project_flock_kandang_id: number;
|
project_flock_kandang_id: number;
|
||||||
stocks: {
|
stocks: {
|
||||||
product_warehouse_id?: number;
|
product_warehouse_id: number;
|
||||||
qty?: number | string;
|
qty: number | string;
|
||||||
}[];
|
}[];
|
||||||
depletions: {
|
depletions: {
|
||||||
product_warehouse_id?: number;
|
product_warehouse_id?: number;
|
||||||
@@ -73,18 +73,6 @@ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
|
|||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
.typeError('Jumlah penggunaan harus berupa angka!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const OptionalStockObjectSchema: Yup.ObjectSchema<{
|
|
||||||
product_warehouse_id?: number;
|
|
||||||
qty?: number | string;
|
|
||||||
}> = Yup.object({
|
|
||||||
product_warehouse_id: Yup.number()
|
|
||||||
.optional()
|
|
||||||
.typeError('Produk harus berupa angka!'),
|
|
||||||
qty: Yup.number()
|
|
||||||
.optional()
|
|
||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
|
const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
|
||||||
product_warehouse_id: Yup.number()
|
product_warehouse_id: Yup.number()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -102,9 +90,7 @@ const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
|
|||||||
weight: Yup.number().optional().typeError('Berat telur harus berupa angka!'),
|
weight: Yup.number().optional().typeError('Berat telur harus berupa angka!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RecordingGrowingFormSchema = (
|
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
|
||||||
isTransitioningToLaying = false
|
|
||||||
): Yup.ObjectSchema<RecordingGrowingFormSchemaType> =>
|
|
||||||
Yup.object({
|
Yup.object({
|
||||||
record_date: Yup.string()
|
record_date: Yup.string()
|
||||||
.required('Tanggal recording wajib diisi!')
|
.required('Tanggal recording wajib diisi!')
|
||||||
@@ -164,24 +150,20 @@ export const RecordingGrowingFormSchema = (
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
stocks: isTransitioningToLaying
|
stocks: Yup.array()
|
||||||
? Yup.array().of(OptionalStockObjectSchema).default([])
|
.of(StockObjectSchema)
|
||||||
: Yup.array()
|
.min(1, 'Minimal harus ada 1 data stok!')
|
||||||
.of(StockObjectSchema)
|
.required('Data stok wajib diisi!'),
|
||||||
.min(1, 'Minimal harus ada 1 data stok!')
|
|
||||||
.required('Data stok wajib diisi!'),
|
|
||||||
depletions: Yup.array().of(DepletionObjectSchema).default([]),
|
depletions: Yup.array().of(DepletionObjectSchema).default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSchemaType> =
|
export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSchemaType> =
|
||||||
RecordingGrowingFormSchema().shape({
|
RecordingGrowingFormSchema.shape({
|
||||||
eggs: Yup.array().of(EggObjectSchema).default([]),
|
eggs: Yup.array().of(EggObjectSchema).default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateRecordingGrowingFormSchema = (
|
export const UpdateRecordingGrowingFormSchema =
|
||||||
isTransitioningToLaying = false
|
RecordingGrowingFormSchema.shape({
|
||||||
) =>
|
|
||||||
RecordingGrowingFormSchema(isTransitioningToLaying).shape({
|
|
||||||
location_id: Yup.number().nullable().optional(),
|
location_id: Yup.number().nullable().optional(),
|
||||||
project_flock_id: Yup.number().nullable().optional(),
|
project_flock_id: Yup.number().nullable().optional(),
|
||||||
kandang_id: Yup.number().nullable().optional(),
|
kandang_id: Yup.number().nullable().optional(),
|
||||||
@@ -211,13 +193,10 @@ export const UpdateRecordingLayingFormSchema = RecordingLayingFormSchema.shape({
|
|||||||
.required('Project Flock Kandang wajib diisi!'),
|
.required('Project Flock Kandang wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
type RecordingGrowingFormSchemaFn = ReturnType<
|
export type RecordingGrowingFormValues = Yup.InferType<
|
||||||
typeof RecordingGrowingFormSchema
|
typeof RecordingGrowingFormSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type RecordingGrowingFormValues =
|
|
||||||
Yup.InferType<RecordingGrowingFormSchemaFn>;
|
|
||||||
|
|
||||||
export type RecordingLayingFormValues = Yup.InferType<
|
export type RecordingLayingFormValues = Yup.InferType<
|
||||||
typeof RecordingLayingFormSchema
|
typeof RecordingLayingFormSchema
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -272,16 +272,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
return recording?.approval?.action === 'REJECTED';
|
return recording?.approval?.action === 'REJECTED';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isRecordingEditable = useCallback((recording?: Recording) => {
|
|
||||||
if (recording?.project_flock?.project_flock_category === 'GROWING') {
|
|
||||||
if (recording?.transfer_executed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return recording?.population_can_change === true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// ===== PAYLOAD CREATION HELPERS =====
|
// ===== PAYLOAD CREATION HELPERS =====
|
||||||
const createGrowingPayload = useCallback(
|
const createGrowingPayload = useCallback(
|
||||||
(values: RecordingGrowingFormValues) => {
|
(values: RecordingGrowingFormValues) => {
|
||||||
@@ -591,14 +581,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
projectFlockKandangLookup?.project_flock?.category === 'GROWING' ||
|
projectFlockKandangLookup?.project_flock?.category === 'GROWING' ||
|
||||||
projectFlockKandangDetail?.project_flock?.category === 'GROWING';
|
projectFlockKandangDetail?.project_flock?.category === 'GROWING';
|
||||||
|
|
||||||
const isTransitioningToLaying = useMemo(() => {
|
|
||||||
if (!isGrowingCategory) return false;
|
|
||||||
return (
|
|
||||||
initialValues?.population_can_change === true ||
|
|
||||||
initialValues?.transfer_executed === true
|
|
||||||
);
|
|
||||||
}, [initialValues, isGrowingCategory]);
|
|
||||||
|
|
||||||
const recordingApprovalLines = useMemo(() => {
|
const recordingApprovalLines = useMemo(() => {
|
||||||
if (isLayingCategory) {
|
if (isLayingCategory) {
|
||||||
return LAYING_RECORDING_APPROVAL_LINE;
|
return LAYING_RECORDING_APPROVAL_LINE;
|
||||||
@@ -959,8 +941,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
} else {
|
} else {
|
||||||
schema =
|
schema =
|
||||||
type === 'edit'
|
type === 'edit'
|
||||||
? UpdateRecordingGrowingFormSchema(isTransitioningToLaying)
|
? UpdateRecordingGrowingFormSchema
|
||||||
: RecordingGrowingFormSchema(isTransitioningToLaying);
|
: RecordingGrowingFormSchema;
|
||||||
}
|
}
|
||||||
return schema.clone().concat(
|
return schema.clone().concat(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
@@ -2341,25 +2323,21 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
)}
|
)}
|
||||||
<th>
|
<th>
|
||||||
Persediaan
|
Persediaan
|
||||||
{!isTransitioningToLaying && (
|
<span
|
||||||
<span
|
className='tooltip tooltip-error tooltip-bottom '
|
||||||
className='tooltip tooltip-error tooltip-bottom '
|
data-tip='required'
|
||||||
data-tip='required'
|
>
|
||||||
>
|
<span className='text-error'>*</span>
|
||||||
<span className='text-error'>*</span>
|
</span>
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Jumlah Pakai
|
Jumlah Pakai
|
||||||
{!isTransitioningToLaying && (
|
<span
|
||||||
<span
|
className='tooltip tooltip-error tooltip-bottom '
|
||||||
className='tooltip tooltip-error tooltip-bottom '
|
data-tip='required'
|
||||||
data-tip='required'
|
>
|
||||||
>
|
<span className='text-error'>*</span>
|
||||||
<span className='text-error'>*</span>
|
</span>
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</th>
|
</th>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
@@ -2394,7 +2372,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
)}
|
)}
|
||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required={!isTransitioningToLaying}
|
required
|
||||||
key={`stock-product-${idx}-${stock.product_warehouse_id}`}
|
key={`stock-product-${idx}-${stock.product_warehouse_id}`}
|
||||||
value={
|
value={
|
||||||
unifiedStockProducts.find(
|
unifiedStockProducts.find(
|
||||||
@@ -2452,7 +2430,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<td>
|
<td>
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required={!isTransitioningToLaying}
|
required
|
||||||
name={`stocks.${idx}.qty`}
|
name={`stocks.${idx}.qty`}
|
||||||
value={stock.qty ?? ''}
|
value={stock.qty ?? ''}
|
||||||
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
||||||
@@ -3012,46 +2990,42 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<div className='flex flex-col sm:flex-row sm:justify-between gap-2'>
|
<div className='flex flex-col sm:flex-row sm:justify-between gap-2'>
|
||||||
{/* Left side - Detail & Edit actions */}
|
{/* Left side - Detail & Edit actions */}
|
||||||
<div className='flex flex-col sm:flex-row justify-start gap-2 w-full sm:w-auto'>
|
<div className='flex flex-col sm:flex-row justify-start gap-2 w-full sm:w-auto'>
|
||||||
{type === 'detail' &&
|
{type === 'detail' && deleteRecordingClickHandler && (
|
||||||
deleteRecordingClickHandler &&
|
<RequirePermission permissions='lti.production.recording.delete'>
|
||||||
isRecordingEditable(initialValues) && (
|
<Button
|
||||||
<RequirePermission permissions='lti.production.recording.delete'>
|
type='button'
|
||||||
<Button
|
color='error'
|
||||||
type='button'
|
onClick={deleteRecordingClickHandler}
|
||||||
color='error'
|
className='px-4'
|
||||||
onClick={deleteRecordingClickHandler}
|
>
|
||||||
className='px-4'
|
<Icon
|
||||||
>
|
icon='material-symbols:delete-outline-rounded'
|
||||||
<Icon
|
width={24}
|
||||||
icon='material-symbols:delete-outline-rounded'
|
height={24}
|
||||||
width={24}
|
className='justify-start text-sm'
|
||||||
height={24}
|
/>
|
||||||
className='justify-start text-sm'
|
Delete
|
||||||
/>
|
</Button>
|
||||||
Delete
|
</RequirePermission>
|
||||||
</Button>
|
)}
|
||||||
</RequirePermission>
|
{type === 'detail' && initialValues && (
|
||||||
)}
|
<RequirePermission permissions='lti.production.recording.update'>
|
||||||
{type === 'detail' &&
|
<Button
|
||||||
initialValues &&
|
type='button'
|
||||||
isRecordingEditable(initialValues) && (
|
color='warning'
|
||||||
<RequirePermission permissions='lti.production.recording.update'>
|
href={`/production/recording/detail/edit/?recordingId=${initialValues.id}`}
|
||||||
<Button
|
className='px-4'
|
||||||
type='button'
|
>
|
||||||
color='warning'
|
<Icon
|
||||||
href={`/production/recording/detail/edit/?recordingId=${initialValues.id}`}
|
icon='material-symbols:edit-outline'
|
||||||
className='px-4'
|
width={24}
|
||||||
>
|
height={24}
|
||||||
<Icon
|
className='justify-start text-sm'
|
||||||
icon='material-symbols:edit-outline'
|
/>
|
||||||
width={24}
|
Edit
|
||||||
height={24}
|
</Button>
|
||||||
className='justify-start text-sm'
|
</RequirePermission>
|
||||||
/>
|
)}
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
</RequirePermission>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{/* Right side actions */}
|
{/* Right side actions */}
|
||||||
<div className='flex flex-col sm:flex-row sm:justify-end gap-2 w-full sm:w-auto'>
|
<div className='flex flex-col sm:flex-row sm:justify-end gap-2 w-full sm:w-auto'>
|
||||||
|
|||||||
+8
-18
@@ -50,18 +50,12 @@ const TransferToLayingConfirmationModalTable = ({
|
|||||||
transferToLayingForm?: TransferToLayingFormValues;
|
transferToLayingForm?: TransferToLayingFormValues;
|
||||||
transferToLayingId?: number;
|
transferToLayingId?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const isValidId =
|
|
||||||
transferToLayingId !== undefined &&
|
|
||||||
transferToLayingId !== null &&
|
|
||||||
!isNaN(transferToLayingId) &&
|
|
||||||
transferToLayingId > 0;
|
|
||||||
|
|
||||||
const { data: transferToLaying, isLoading: isLoadingTransferToLaying } =
|
const { data: transferToLaying, isLoading: isLoadingTransferToLaying } =
|
||||||
useSWR(
|
useSWR(
|
||||||
isValidId
|
transferToLayingId
|
||||||
? ['detail-transfer-to-laying', String(transferToLayingId)]
|
? ['detail-transfer-to-laying', String(transferToLayingId)]
|
||||||
: undefined,
|
: undefined,
|
||||||
([, id]) => TransferToLayingApi.getSingle(Number(id))
|
([id]) => TransferToLayingApi.getSingle(Number(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
const confirmationTableColumns: ColumnDef<TransferToLayingConfirmationTableDataType>[] =
|
const confirmationTableColumns: ColumnDef<TransferToLayingConfirmationTableDataType>[] =
|
||||||
@@ -279,16 +273,12 @@ const TransferToLayingConfirmationModal = ({
|
|||||||
|
|
||||||
{transferToLayingIds &&
|
{transferToLayingIds &&
|
||||||
!transferToLayingForm &&
|
!transferToLayingForm &&
|
||||||
transferToLayingIds
|
transferToLayingIds.map((transferToLayingId, idx) => (
|
||||||
.filter(
|
<TransferToLayingConfirmationModalTable
|
||||||
(id) => id !== undefined && id !== null && !isNaN(id) && id > 0
|
key={idx}
|
||||||
)
|
transferToLayingId={transferToLayingId}
|
||||||
.map((transferToLayingId, idx) => (
|
/>
|
||||||
<TransferToLayingConfirmationModalTable
|
))}
|
||||||
key={idx}
|
|
||||||
transferToLayingId={transferToLayingId}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{withNote && (
|
{withNote && (
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const TransferToLayingDetailModal = () => {
|
|||||||
if (modalAction === 'detail') {
|
if (modalAction === 'detail') {
|
||||||
detailModal.openModal();
|
detailModal.openModal();
|
||||||
}
|
}
|
||||||
}, [modalAction]);
|
}, [modalAction, detailModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -229,8 +229,6 @@ const TransferToLayingFormModal = () => {
|
|||||||
ProjectFlock | undefined
|
ProjectFlock | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
const [maxSourceQuantity, setMaxSourceQuantity] = useState<number>(0);
|
|
||||||
|
|
||||||
const selectedFlockDestinationRawData = isResponseSuccess(
|
const selectedFlockDestinationRawData = isResponseSuccess(
|
||||||
flockDestinationRawData
|
flockDestinationRawData
|
||||||
)
|
)
|
||||||
@@ -355,14 +353,19 @@ const TransferToLayingFormModal = () => {
|
|||||||
return { available: countAvailable, unavailable: countUnavailable };
|
return { available: countAvailable, unavailable: countUnavailable };
|
||||||
}, [mappedFlockDestinationKandangsMaxTargetQty]);
|
}, [mappedFlockDestinationKandangsMaxTargetQty]);
|
||||||
|
|
||||||
|
const totalEnteredChickenForTransfer =
|
||||||
|
formik.values.flockSourceKandangs.reduce(
|
||||||
|
(acc, item) => acc + Number(item.quantity),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
const totalTransferedChicken = formik.values.flockDestinationKandangs.reduce(
|
const totalTransferedChicken = formik.values.flockDestinationKandangs.reduce(
|
||||||
(acc, item) => acc + Number(item.quantity),
|
(acc, item) => acc + Number(item.quantity),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sisa transfer = Max available dari kandang asal - Total yang sudah diisi di kandang tujuan
|
|
||||||
const totalAvailableChickenForTransfer =
|
const totalAvailableChickenForTransfer =
|
||||||
maxSourceQuantity - totalTransferedChicken;
|
totalEnteredChickenForTransfer - totalTransferedChicken;
|
||||||
|
|
||||||
const isNextButtonDisabled = useMemo(() => {
|
const isNextButtonDisabled = useMemo(() => {
|
||||||
if (step === 1) {
|
if (step === 1) {
|
||||||
@@ -394,7 +397,6 @@ const TransferToLayingFormModal = () => {
|
|||||||
formik.setFieldValue('maxTotalQuantity', '');
|
formik.setFieldValue('maxTotalQuantity', '');
|
||||||
formik.setFieldValue('reason', '');
|
formik.setFieldValue('reason', '');
|
||||||
formik.setFieldTouched('reason', false);
|
formik.setFieldTouched('reason', false);
|
||||||
setMaxSourceQuantity(0);
|
|
||||||
|
|
||||||
setStep(2);
|
setStep(2);
|
||||||
};
|
};
|
||||||
@@ -402,7 +404,6 @@ const TransferToLayingFormModal = () => {
|
|||||||
const flockSourceChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const flockSourceChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldValue('flockSource', val);
|
formik.setFieldValue('flockSource', val);
|
||||||
formik.setFieldValue('flockSourceKandangs', []);
|
formik.setFieldValue('flockSourceKandangs', []);
|
||||||
setMaxSourceQuantity(0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const flockDestinationChangeHandler = (
|
const flockDestinationChangeHandler = (
|
||||||
@@ -468,26 +469,6 @@ const TransferToLayingFormModal = () => {
|
|||||||
formik.setFieldValue('maxTotalQuantity', totalTransferedChicken);
|
formik.setFieldValue('maxTotalQuantity', totalTransferedChicken);
|
||||||
}, [totalTransferedChicken, formik.values.flockDestinationKandangs]);
|
}, [totalTransferedChicken, formik.values.flockDestinationKandangs]);
|
||||||
|
|
||||||
// Auto-fill source kandang quantity from total destination quantity
|
|
||||||
useEffect(() => {
|
|
||||||
if (formik.values.flockSourceKandangs.length > 0) {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'flockSourceKandangs.0.quantity',
|
|
||||||
totalTransferedChicken
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [totalTransferedChicken]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
formik.values.flockSourceKandangs.length > 0 &&
|
|
||||||
formik.values.flockSourceKandangs[0].maxQuantity &&
|
|
||||||
maxSourceQuantity === 0
|
|
||||||
) {
|
|
||||||
setMaxSourceQuantity(formik.values.flockSourceKandangs[0].maxQuantity);
|
|
||||||
}
|
|
||||||
}, [formik.values.flockSourceKandangs, maxSourceQuantity]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -602,9 +583,14 @@ const TransferToLayingFormModal = () => {
|
|||||||
k.kandang.value === item.project_flock_kandang_id
|
k.kandang.value === item.project_flock_kandang_id
|
||||||
);
|
);
|
||||||
|
|
||||||
const flockSourceKandangRadioChangeHandler = () => {
|
const flockSourceKandangCheckboxChangeHandler: FormEventHandler<
|
||||||
if (isAvailable) {
|
HTMLInputElement
|
||||||
|
> = (e) => {
|
||||||
|
const checked = (e.target as HTMLInputElement)
|
||||||
|
.checked;
|
||||||
|
if (checked) {
|
||||||
formik.setFieldValue('flockSourceKandangs', [
|
formik.setFieldValue('flockSourceKandangs', [
|
||||||
|
...formik.values.flockSourceKandangs,
|
||||||
{
|
{
|
||||||
kandang: {
|
kandang: {
|
||||||
value: item.project_flock_kandang_id,
|
value: item.project_flock_kandang_id,
|
||||||
@@ -614,7 +600,15 @@ const TransferToLayingFormModal = () => {
|
|||||||
maxQuantity: item.available_qty,
|
maxQuantity: item.available_qty,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setMaxSourceQuantity(item.available_qty);
|
} else {
|
||||||
|
formik.setFieldValue(
|
||||||
|
'flockSourceKandangs',
|
||||||
|
formik.values.flockSourceKandangs.filter(
|
||||||
|
(k) =>
|
||||||
|
k.kandang.value !==
|
||||||
|
item.project_flock_kandang_id
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -624,22 +618,28 @@ const TransferToLayingFormModal = () => {
|
|||||||
className='w-full p-3 flex flex-row items-center justify-between'
|
className='w-full p-3 flex flex-row items-center justify-between'
|
||||||
>
|
>
|
||||||
<div className='flex flex-row items-center gap-3'>
|
<div className='flex flex-row items-center gap-3'>
|
||||||
<input
|
<CheckboxInput
|
||||||
type='radio'
|
name={`flockSourceKandang.${itemIdx}.value`}
|
||||||
name='flockSourceKandang'
|
|
||||||
value={item.project_flock_kandang_id}
|
value={item.project_flock_kandang_id}
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onChange={flockSourceKandangRadioChangeHandler}
|
onChange={
|
||||||
|
flockSourceKandangCheckboxChangeHandler
|
||||||
|
}
|
||||||
|
size='md'
|
||||||
disabled={!isAvailable}
|
disabled={!isAvailable}
|
||||||
className={cn('radio radio-md radio-primary', {
|
classNames={{
|
||||||
'opacity-50 cursor-not-allowed': !isAvailable,
|
checkbox: cn({
|
||||||
})}
|
'bg-base-200 border border-base-content/10 opacity-100':
|
||||||
|
!isAvailable,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
|
htmlFor={`flockSourceKandang.${itemIdx}.value`}
|
||||||
className={cn('text-sm text-base-content/50', {
|
className={cn('text-sm text-base-content/50', {
|
||||||
'cursor-pointer': isAvailable,
|
'cursor-pointer': isAvailable,
|
||||||
'cursor-not-allowed opacity-50': !isAvailable,
|
'cursor-not-allowed': !isAvailable,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{item.kandang_name}{' '}
|
{item.kandang_name}{' '}
|
||||||
@@ -858,7 +858,7 @@ const TransferToLayingFormModal = () => {
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
key={`flockSourceKandangs-${item.kandang.value}-${index}`}
|
key={`flockSourceKandangs-${item.kandang.value}-${index}`}
|
||||||
name={`flockSourceKandangs.${index}.quantity`}
|
name={`flockSourceKandangs.${index}.quantity`}
|
||||||
placeholder='Masukkan Kuantitas pada Kandang Tujuan'
|
placeholder='Masukkan Kuantitas'
|
||||||
value={item.quantity}
|
value={item.quantity}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
isError={isInvalid}
|
isError={isInvalid}
|
||||||
@@ -875,8 +875,6 @@ const TransferToLayingFormModal = () => {
|
|||||||
<div className='w-px bg-base-content/10' />
|
<div className='w-px bg-base-content/10' />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
readOnly
|
|
||||||
disabled
|
|
||||||
className={{
|
className={{
|
||||||
inputPrefix:
|
inputPrefix:
|
||||||
'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0',
|
'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0',
|
||||||
@@ -1002,7 +1000,7 @@ const TransferToLayingFormModal = () => {
|
|||||||
isError={totalAvailableChickenForTransfer < 0}
|
isError={totalAvailableChickenForTransfer < 0}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
totalAvailableChickenForTransfer < 0
|
totalAvailableChickenForTransfer < 0
|
||||||
? `Jumlah transfer melebihi ketersediaan (${formatNumber(maxSourceQuantity, 'en-US')} ayam)`
|
? `Jumlah transfer melebihi ketersediaan (${formatNumber(totalEnteredChickenForTransfer, 'en-US')} ayam)`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
disabled
|
disabled
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ const RowOptionsMenu = ({
|
|||||||
popoverPosition: 'bottom' | 'top';
|
popoverPosition: 'bottom' | 'top';
|
||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const showEditButton = props.row.original.approval.action !== 'APPROVED';
|
const showEditButton =
|
||||||
|
props.row.original.approval.action !== 'APPROVED' &&
|
||||||
|
props.row.original.approval.action !== 'REJECTED';
|
||||||
|
|
||||||
const showDeleteButton =
|
const showDeleteButton = showEditButton;
|
||||||
props.row.original.approval.action === 'APPROVED' ||
|
|
||||||
props.row.original.approval.step_name.toLowerCase() === 'pengajuan';
|
|
||||||
|
|
||||||
const popoverId = `transferToLaying#${props.row.original.id}`;
|
const popoverId = `transferToLaying#${props.row.original.id}`;
|
||||||
const popoverAnchorName = `--anchor-transferToLaying#${props.row.original.id}`;
|
const popoverAnchorName = `--anchor-transferToLaying#${props.row.original.id}`;
|
||||||
@@ -463,7 +463,7 @@ const TransferToLayingsTable = () => {
|
|||||||
updateFilter('filter_by', '');
|
updateFilter('filter_by', '');
|
||||||
updateFilter('sort_by', '');
|
updateFilter('sort_by', '');
|
||||||
}
|
}
|
||||||
}, [sorting]);
|
}, [sorting, updateFilter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -60,25 +60,6 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
router.push(`/production/uniformity?action=reject&id=${initialValues.id}`);
|
router.push(`/production/uniformity?action=reject&id=${initialValues.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewUniformityDetails = () => {
|
|
||||||
if (!uniformity_details || uniformity_details.length === 0) {
|
|
||||||
setShouldFetchDetails(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setExpandedDrawerContent(
|
|
||||||
<UniformityDetailsPreview
|
|
||||||
info_umum={initialValues.info_umum}
|
|
||||||
uniformity_details={uniformity_details}
|
|
||||||
uniformityId={initialValues.id}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setExpandedDrawerOpen(true);
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
shouldFetchDetails &&
|
shouldFetchDetails &&
|
||||||
@@ -202,6 +183,25 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (id === 'document-name') {
|
if (id === 'document-name') {
|
||||||
|
const handleViewUniformityDetails = () => {
|
||||||
|
if (!uniformity_details || uniformity_details.length === 0) {
|
||||||
|
setShouldFetchDetails(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExpandedDrawerContent(
|
||||||
|
<UniformityDetailsPreview
|
||||||
|
info_umum={initialValues.info_umum}
|
||||||
|
uniformity_details={uniformity_details}
|
||||||
|
uniformityId={initialValues.id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setExpandedDrawerOpen(true);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<span>{valueMap[id]}</span>
|
<span>{valueMap[id]}</span>
|
||||||
@@ -231,7 +231,14 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[initialValues, handleViewUniformityDetails, isLoading]
|
[
|
||||||
|
initialValues,
|
||||||
|
isLoading,
|
||||||
|
uniformity_details,
|
||||||
|
setShouldFetchDetails,
|
||||||
|
setExpandedDrawerContent,
|
||||||
|
setExpandedDrawerOpen,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const samplingTableData: DetailOptionType[] = useMemo(() => {
|
const samplingTableData: DetailOptionType[] = useMemo(() => {
|
||||||
|
|||||||
@@ -301,8 +301,93 @@ export function DailyChecklistContent() {
|
|||||||
checkAndLoadChecklist();
|
checkAndLoadChecklist();
|
||||||
}, [date, kandangId, selectedCategory]);
|
}, [date, kandangId, selectedCategory]);
|
||||||
|
|
||||||
|
// Load employees when kandang changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (kandangId) {
|
||||||
|
// ✅ Clear selected employees ketika kandang berubah (reset ABK assignment)
|
||||||
|
setSelectedEmployees([]);
|
||||||
|
setAssignments({});
|
||||||
|
} else {
|
||||||
|
setSelectedEmployees([]);
|
||||||
|
setAssignments({});
|
||||||
|
}
|
||||||
|
}, [kandangId]);
|
||||||
|
|
||||||
// Load activities and tasks when phases change
|
// Load activities and tasks when phases change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const loadAssignments = async (taskIds: string[]) => {
|
||||||
|
if (taskIds.length === 0) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingDailyChecklist =
|
||||||
|
await DailyChecklistApi.getOneDailyChecklist(
|
||||||
|
String(dailyChecklistId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseError(existingDailyChecklist)) {
|
||||||
|
console.error(
|
||||||
|
'Error loading assignments:',
|
||||||
|
existingDailyChecklist.message
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set existing document
|
||||||
|
setExistingDocuments(existingDailyChecklist?.data.document_urls || []);
|
||||||
|
|
||||||
|
// Build assignments map
|
||||||
|
const assignmentMap: {
|
||||||
|
[taskId: string]: {
|
||||||
|
[employeeId: string]: { checked: boolean; note: string };
|
||||||
|
};
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
(existingDailyChecklist?.data.tasks || []).forEach(
|
||||||
|
(dailyChecklistTask) => {
|
||||||
|
if (!assignmentMap[dailyChecklistTask.id]) {
|
||||||
|
assignmentMap[dailyChecklistTask.id] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
dailyChecklistTask.assignments.forEach((assignment) => {
|
||||||
|
if (!assignmentMap[dailyChecklistTask.id]) {
|
||||||
|
assignmentMap[dailyChecklistTask.id] = {};
|
||||||
|
}
|
||||||
|
assignmentMap[dailyChecklistTask.id][assignment.employee.id] = {
|
||||||
|
checked: assignment.checked,
|
||||||
|
note: assignment.note || '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
setAssignments(assignmentMap);
|
||||||
|
|
||||||
|
// Load employees from assignments
|
||||||
|
const employeeIds = Array.from(
|
||||||
|
new Set(
|
||||||
|
(existingDailyChecklist?.data.assigned_employees || []).map(
|
||||||
|
(a) => a.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (employeeIds.length > 0) {
|
||||||
|
const existingDailyChecklist =
|
||||||
|
await DailyChecklistApi.getOneDailyChecklist(
|
||||||
|
String(dailyChecklistId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseSuccess(existingDailyChecklist)) {
|
||||||
|
setSelectedEmployees(
|
||||||
|
existingDailyChecklist.data.assigned_employees
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading assignments:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadActivitiesAndTasks = async () => {
|
const loadActivitiesAndTasks = async () => {
|
||||||
if (!dailyChecklistId || selectedPhaseIds.length === 0) {
|
if (!dailyChecklistId || selectedPhaseIds.length === 0) {
|
||||||
setActivitiesByPhase({});
|
setActivitiesByPhase({});
|
||||||
@@ -377,87 +462,6 @@ export function DailyChecklistContent() {
|
|||||||
loadActivitiesAndTasks();
|
loadActivitiesAndTasks();
|
||||||
}, [dailyChecklistId, selectedPhaseIds]);
|
}, [dailyChecklistId, selectedPhaseIds]);
|
||||||
|
|
||||||
// Load employees when kandang changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (kandangId) {
|
|
||||||
// ✅ Clear selected employees ketika kandang berubah (reset ABK assignment)
|
|
||||||
setSelectedEmployees([]);
|
|
||||||
setAssignments({});
|
|
||||||
} else {
|
|
||||||
setSelectedEmployees([]);
|
|
||||||
setAssignments({});
|
|
||||||
}
|
|
||||||
}, [kandangId]);
|
|
||||||
|
|
||||||
const loadAssignments = async (taskIds: string[]) => {
|
|
||||||
if (taskIds.length === 0) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const existingDailyChecklist =
|
|
||||||
await DailyChecklistApi.getOneDailyChecklist(String(dailyChecklistId));
|
|
||||||
|
|
||||||
if (isResponseError(existingDailyChecklist)) {
|
|
||||||
console.error(
|
|
||||||
'Error loading assignments:',
|
|
||||||
existingDailyChecklist.message
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set existing document
|
|
||||||
setExistingDocuments(existingDailyChecklist?.data.document_urls || []);
|
|
||||||
|
|
||||||
// Build assignments map
|
|
||||||
const assignmentMap: {
|
|
||||||
[taskId: string]: {
|
|
||||||
[employeeId: string]: { checked: boolean; note: string };
|
|
||||||
};
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
(existingDailyChecklist?.data.tasks || []).forEach(
|
|
||||||
(dailyChecklistTask) => {
|
|
||||||
if (!assignmentMap[dailyChecklistTask.id]) {
|
|
||||||
assignmentMap[dailyChecklistTask.id] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
dailyChecklistTask.assignments.forEach((assignment) => {
|
|
||||||
if (!assignmentMap[dailyChecklistTask.id]) {
|
|
||||||
assignmentMap[dailyChecklistTask.id] = {};
|
|
||||||
}
|
|
||||||
assignmentMap[dailyChecklistTask.id][assignment.employee.id] = {
|
|
||||||
checked: assignment.checked,
|
|
||||||
note: assignment.note || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
setAssignments(assignmentMap);
|
|
||||||
|
|
||||||
// Load employees from assignments
|
|
||||||
const employeeIds = Array.from(
|
|
||||||
new Set(
|
|
||||||
(existingDailyChecklist?.data.assigned_employees || []).map(
|
|
||||||
(a) => a.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (employeeIds.length > 0) {
|
|
||||||
const existingDailyChecklist =
|
|
||||||
await DailyChecklistApi.getOneDailyChecklist(
|
|
||||||
String(dailyChecklistId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isResponseSuccess(existingDailyChecklist)) {
|
|
||||||
setSelectedEmployees(existingDailyChecklist.data.assigned_employees);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading assignments:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Phase selection modal
|
// Phase selection modal
|
||||||
const handleAddPhase = () => {
|
const handleAddPhase = () => {
|
||||||
if (!selectedCategory) {
|
if (!selectedCategory) {
|
||||||
|
|||||||
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ArrowLeft, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
import { ArrowLeft, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/figma-make/components/base/card';
|
import { Card, CardContent } from '@/figma-make/components/base/card';
|
||||||
@@ -137,15 +137,7 @@ export function DetailDailyChecklistContent() {
|
|||||||
const [rejectReason, setRejectReason] = useState('');
|
const [rejectReason, setRejectReason] = useState('');
|
||||||
const [actionLoading, setActionLoading] = useState(false);
|
const [actionLoading, setActionLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchChecklistDetail = useCallback(async () => {
|
||||||
if (checklistId) {
|
|
||||||
fetchChecklistDetail();
|
|
||||||
} else {
|
|
||||||
router.push('/404');
|
|
||||||
}
|
|
||||||
}, [checklistId]);
|
|
||||||
|
|
||||||
const fetchChecklistDetail = async () => {
|
|
||||||
if (!checklistId) {
|
if (!checklistId) {
|
||||||
console.warn('checklistId missing');
|
console.warn('checklistId missing');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -320,7 +312,15 @@ export function DetailDailyChecklistContent() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [checklistId, router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (checklistId) {
|
||||||
|
fetchChecklistDetail();
|
||||||
|
} else {
|
||||||
|
router.push('/404');
|
||||||
|
}
|
||||||
|
}, [checklistId, fetchChecklistDetail, router]);
|
||||||
|
|
||||||
const groupDetailData = (rows: ChecklistDetailRow[]) => {
|
const groupDetailData = (rows: ChecklistDetailRow[]) => {
|
||||||
// Group by phase_id
|
// Group by phase_id
|
||||||
|
|||||||
@@ -305,3 +305,17 @@ export function transformConstants(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function omit<T extends Record<string, unknown>, K extends keyof T>(
|
||||||
|
obj: T,
|
||||||
|
keys: K | K[]
|
||||||
|
): Omit<T, K> {
|
||||||
|
const keysArray = Array.isArray(keys) ? keys : [keys];
|
||||||
|
const result = { ...obj };
|
||||||
|
|
||||||
|
keysArray.forEach((key) => {
|
||||||
|
delete result[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,16 +4,13 @@ import { create } from 'zustand';
|
|||||||
import { devtools } from 'zustand/middleware';
|
import { devtools } from 'zustand/middleware';
|
||||||
import { createChickinApprovalSlice } from '@/stores/production/chickin/slices/chickin-approval.slice';
|
import { createChickinApprovalSlice } from '@/stores/production/chickin/slices/chickin-approval.slice';
|
||||||
import { ChickinApprovalSlice } from '@/stores/production/chickin/slices/chickin-approval.slice';
|
import { ChickinApprovalSlice } from '@/stores/production/chickin/slices/chickin-approval.slice';
|
||||||
import { createChickinDeleteSlice } from '@/stores/production/chickin/slices/chickin-delete.slice';
|
|
||||||
import { ChickinDeleteSlice } from '@/stores/production/chickin/slices/chickin-delete.slice';
|
|
||||||
|
|
||||||
export type ChickinStore = ChickinApprovalSlice & ChickinDeleteSlice;
|
export type ChickinStore = ChickinApprovalSlice;
|
||||||
|
|
||||||
export const useChickinStore = create<ChickinStore>()(
|
export const useChickinStore = create<ChickinStore>()(
|
||||||
devtools(
|
devtools(
|
||||||
(...args) => ({
|
(...args) => ({
|
||||||
...createChickinApprovalSlice(...args),
|
...createChickinApprovalSlice(...args),
|
||||||
...createChickinDeleteSlice(...args),
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'ChickinStore',
|
name: 'ChickinStore',
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import { StateCreator } from 'zustand';
|
|
||||||
|
|
||||||
export type ChickinDeleteSlice = {
|
|
||||||
// State
|
|
||||||
isChickinDeleteModalOpen: boolean;
|
|
||||||
selectedChickinIdForDelete: number | null;
|
|
||||||
isChickinDeleteLoading: boolean;
|
|
||||||
chickinDeleteCallback: (() => Promise<void>) | null;
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
openChickinDeleteModal: (
|
|
||||||
chickinId: number,
|
|
||||||
callback: () => Promise<void>
|
|
||||||
) => void;
|
|
||||||
closeChickinDeleteModal: () => void;
|
|
||||||
setChickinDeleteLoading: (loading: boolean) => void;
|
|
||||||
resetChickinDelete: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createChickinDeleteSlice: StateCreator<
|
|
||||||
ChickinDeleteSlice,
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
ChickinDeleteSlice
|
|
||||||
> = (set) => ({
|
|
||||||
// Initial state
|
|
||||||
isChickinDeleteModalOpen: false,
|
|
||||||
selectedChickinIdForDelete: null,
|
|
||||||
isChickinDeleteLoading: false,
|
|
||||||
chickinDeleteCallback: null,
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
openChickinDeleteModal: (chickinId, callback) =>
|
|
||||||
set({
|
|
||||||
isChickinDeleteModalOpen: true,
|
|
||||||
selectedChickinIdForDelete: chickinId,
|
|
||||||
chickinDeleteCallback: callback,
|
|
||||||
}),
|
|
||||||
|
|
||||||
closeChickinDeleteModal: () =>
|
|
||||||
set({
|
|
||||||
isChickinDeleteModalOpen: false,
|
|
||||||
selectedChickinIdForDelete: null,
|
|
||||||
chickinDeleteCallback: null,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setChickinDeleteLoading: (loading) =>
|
|
||||||
set({ isChickinDeleteLoading: loading }),
|
|
||||||
|
|
||||||
resetChickinDelete: () =>
|
|
||||||
set({
|
|
||||||
isChickinDeleteModalOpen: false,
|
|
||||||
selectedChickinIdForDelete: null,
|
|
||||||
isChickinDeleteLoading: false,
|
|
||||||
chickinDeleteCallback: null,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { TabActionsSlice } from '@/stores/tab-actions/tab-actions.store';
|
import { TabActionsSlice } from '@/stores/tab-actions/tab-actions.store';
|
||||||
|
import { omit } from '@/lib/helper';
|
||||||
import { StateCreator } from 'zustand';
|
import { StateCreator } from 'zustand';
|
||||||
|
|
||||||
export const createTabActionsSlice: StateCreator<
|
export const createTabActionsSlice: StateCreator<
|
||||||
@@ -20,10 +21,9 @@ export const createTabActionsSlice: StateCreator<
|
|||||||
})),
|
})),
|
||||||
|
|
||||||
clearTabActions: (tabId) =>
|
clearTabActions: (tabId) =>
|
||||||
set((state) => {
|
set((state) => ({
|
||||||
const { [tabId]: _, ...rest } = state.tabActions;
|
tabActions: omit(state.tabActions, tabId),
|
||||||
return { tabActions: rest };
|
})),
|
||||||
}),
|
|
||||||
|
|
||||||
clearAllTabActions: () => set({ tabActions: {} }),
|
clearAllTabActions: () => set({ tabActions: {} }),
|
||||||
});
|
});
|
||||||
|
|||||||
-2
@@ -49,8 +49,6 @@ export type BaseRecording = {
|
|||||||
project_flock: ProjectFlock;
|
project_flock: ProjectFlock;
|
||||||
record_datetime: string;
|
record_datetime: string;
|
||||||
day: number;
|
day: number;
|
||||||
population_can_change: boolean;
|
|
||||||
transfer_executed: boolean;
|
|
||||||
} & ProductionMetrics;
|
} & ProductionMetrics;
|
||||||
|
|
||||||
export type RecordingDepletion = {
|
export type RecordingDepletion = {
|
||||||
|
|||||||
Reference in New Issue
Block a user