mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 06:45:46 +00:00
Merge branch 'development' into 'staging'
Development See merge request mbugroup/lti-web-client!243
This commit is contained in:
@@ -13,7 +13,6 @@ interface HppExpeditionReportTableProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HppExpeditionReportTable = ({
|
const HppExpeditionReportTable = ({
|
||||||
type = 'detail',
|
|
||||||
initialValues,
|
initialValues,
|
||||||
}: HppExpeditionReportTableProps) => {
|
}: HppExpeditionReportTableProps) => {
|
||||||
const costOfRevenueExpeditionData: BaseExpeditionCost[] = useMemo(() => {
|
const costOfRevenueExpeditionData: BaseExpeditionCost[] = useMemo(() => {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import React, { useMemo } from 'react';
|
|||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import Badge from '@/components/Badge';
|
|
||||||
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
|
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
|
||||||
import {
|
import {
|
||||||
BaseClosingSales,
|
BaseClosingSales,
|
||||||
@@ -20,10 +19,7 @@ interface SalesReportTableProps {
|
|||||||
initialValues?: BaseClosingSales;
|
initialValues?: BaseClosingSales;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SalesReportTable = ({
|
const SalesReportTable = ({ initialValues }: SalesReportTableProps) => {
|
||||||
type = 'detail',
|
|
||||||
initialValues,
|
|
||||||
}: SalesReportTableProps) => {
|
|
||||||
const salesData: BaseSales[] = useMemo(() => {
|
const salesData: BaseSales[] = useMemo(() => {
|
||||||
return initialValues?.sales || [];
|
return initialValues?.sales || [];
|
||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import useSWR from 'swr';
|
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -57,10 +56,6 @@ const ExpenseRequestContent = ({
|
|||||||
const isLatestApprovalRejected =
|
const isLatestApprovalRejected =
|
||||||
initialValues?.latest_approval.action === 'REJECTED';
|
initialValues?.latest_approval.action === 'REJECTED';
|
||||||
|
|
||||||
const isLatestApprovalRejectedOrDone =
|
|
||||||
isLatestApprovalRejected ||
|
|
||||||
initialValues?.latest_approval.step_number === 6;
|
|
||||||
|
|
||||||
const isCurrentApprovalOnHeadArea =
|
const isCurrentApprovalOnHeadArea =
|
||||||
!isLatestApprovalRejected &&
|
!isLatestApprovalRejected &&
|
||||||
initialValues?.latest_approval.step_number === 1;
|
initialValues?.latest_approval.step_number === 1;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import { ExpenseApi } from '@/services/api/expense';
|
|||||||
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
|
||||||
import { LocationApi, SupplierApi } from '@/services/api/master-data';
|
import { LocationApi, SupplierApi } from '@/services/api/master-data';
|
||||||
import { Location } from '@/types/api/master-data/location';
|
import { Location } from '@/types/api/master-data/location';
|
||||||
import { Supplier } from '@/types/api/master-data/supplier';
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
@@ -44,8 +43,6 @@ import { BaseApiResponse } from '@/types/api/api-general';
|
|||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
props,
|
props,
|
||||||
approveClickHandler,
|
|
||||||
rejectClickHandler,
|
|
||||||
deleteClickHandler,
|
deleteClickHandler,
|
||||||
}: {
|
}: {
|
||||||
type: 'dropdown' | 'collapse';
|
type: 'dropdown' | 'collapse';
|
||||||
@@ -186,7 +183,6 @@ const ExpensesTable = () => {
|
|||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isCompleteLoading, setIsCompleteLoading] = useState(false);
|
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
|
|
||||||
@@ -247,23 +243,6 @@ const ExpensesTable = () => {
|
|||||||
});
|
});
|
||||||
}, [expenses, selectedRowIds]);
|
}, [expenses, selectedRowIds]);
|
||||||
|
|
||||||
const isAllSelectedRowLatestApprovalOnRealization = useMemo(() => {
|
|
||||||
return selectedRowIds.every((rowId) => {
|
|
||||||
if (!isResponseSuccess(expenses)) return false;
|
|
||||||
|
|
||||||
const expenseItem = expenses.data.find((item) => item.id === rowId);
|
|
||||||
|
|
||||||
const isLatestApprovalRejected =
|
|
||||||
expenseItem?.latest_approval.action === 'REJECTED';
|
|
||||||
|
|
||||||
const isCurrentApprovalOnRealization =
|
|
||||||
!isLatestApprovalRejected &&
|
|
||||||
expenseItem?.latest_approval.step_number === 5;
|
|
||||||
|
|
||||||
return isCurrentApprovalOnRealization;
|
|
||||||
});
|
|
||||||
}, [expenses, selectedRowIds]);
|
|
||||||
|
|
||||||
const expensesColumns: ColumnDef<Expense>[] = [
|
const expensesColumns: ColumnDef<Expense>[] = [
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
@@ -589,12 +568,6 @@ const ExpensesTable = () => {
|
|||||||
updateFilter('realizationDate', e.target.value);
|
updateFilter('realizationDate', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
|
||||||
const newVal = val as OptionType;
|
|
||||||
|
|
||||||
setPageSize(newVal.value as number);
|
|
||||||
};
|
|
||||||
|
|
||||||
// track sorting
|
// track sorting
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
|
const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ interface ExpenseRealizationKandangDetailExpenseProps {
|
|||||||
|
|
||||||
const ExpenseRealizationKandangDetailExpense: React.FC<
|
const ExpenseRealizationKandangDetailExpense: React.FC<
|
||||||
ExpenseRealizationKandangDetailExpenseProps
|
ExpenseRealizationKandangDetailExpenseProps
|
||||||
> = ({ type, formik, supplierId, location, className }) => {
|
> = ({ formik, supplierId, location, className }) => {
|
||||||
const {
|
const {
|
||||||
setInputValue: setNonstockInputValue,
|
setInputValue: setNonstockInputValue,
|
||||||
options: nonstockOptions,
|
options: nonstockOptions,
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { ChangeEventHandler, useMemo, useState } from 'react';
|
import {
|
||||||
|
ChangeEventHandler,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { CellContext } from '@tanstack/react-table';
|
import { CellContext } from '@tanstack/react-table';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
@@ -33,6 +39,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
@@ -133,6 +140,9 @@ const RowOptionsMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FinanceTable = () => {
|
const FinanceTable = () => {
|
||||||
|
const { searchValue, setSearchValue, resetSearchValue } = useUiStore();
|
||||||
|
const previousPathRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: tableFilterState,
|
state: tableFilterState,
|
||||||
updateFilter,
|
updateFilter,
|
||||||
@@ -141,7 +151,7 @@ const FinanceTable = () => {
|
|||||||
toQueryString: getTableFilterQueryString,
|
toQueryString: getTableFilterQueryString,
|
||||||
} = useTableFilter({
|
} = useTableFilter({
|
||||||
initial: {
|
initial: {
|
||||||
search: '',
|
search: searchValue,
|
||||||
transactionType: '',
|
transactionType: '',
|
||||||
bankId: '',
|
bankId: '',
|
||||||
customerId: '',
|
customerId: '',
|
||||||
@@ -167,7 +177,7 @@ const FinanceTable = () => {
|
|||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const [pendingFilters, setPendingFilters] = useState({
|
const [pendingFilters, setPendingFilters] = useState({
|
||||||
search: '',
|
search: searchValue,
|
||||||
transactionType: '',
|
transactionType: '',
|
||||||
bankId: '',
|
bankId: '',
|
||||||
customerId: '',
|
customerId: '',
|
||||||
@@ -296,6 +306,7 @@ const FinanceTable = () => {
|
|||||||
};
|
};
|
||||||
const submitFilterHandler = () => {
|
const submitFilterHandler = () => {
|
||||||
updateFilter('search', pendingFilters.search);
|
updateFilter('search', pendingFilters.search);
|
||||||
|
setSearchValue(pendingFilters.search);
|
||||||
updateFilter('transactionType', pendingFilters.transactionType);
|
updateFilter('transactionType', pendingFilters.transactionType);
|
||||||
updateFilter('bankId', pendingFilters.bankId);
|
updateFilter('bankId', pendingFilters.bankId);
|
||||||
updateFilter('customerId', pendingFilters.customerId);
|
updateFilter('customerId', pendingFilters.customerId);
|
||||||
@@ -324,6 +335,7 @@ const FinanceTable = () => {
|
|||||||
setPendingFilters(emptyFilters);
|
setPendingFilters(emptyFilters);
|
||||||
|
|
||||||
updateFilter('search', '');
|
updateFilter('search', '');
|
||||||
|
resetSearchValue();
|
||||||
updateFilter('transactionType', '');
|
updateFilter('transactionType', '');
|
||||||
updateFilter('bankId', '');
|
updateFilter('bankId', '');
|
||||||
updateFilter('customerId', '');
|
updateFilter('customerId', '');
|
||||||
@@ -447,6 +459,26 @@ const FinanceTable = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Store current path on mount
|
||||||
|
previousPathRef.current = window.location.pathname;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
// if both paths are within /finance module
|
||||||
|
const isCurrentPathFinance = currentPath.includes('/finance');
|
||||||
|
const isPreviousPathFinance =
|
||||||
|
previousPathRef.current?.includes('/finance');
|
||||||
|
|
||||||
|
// reset if we outside finance module entirely
|
||||||
|
if (isPreviousPathFinance && !isCurrentPathFinance) {
|
||||||
|
resetSearchValue();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [resetSearchValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='size-full p-6 flex flex-col gap-6'>
|
<section className='size-full p-6 flex flex-col gap-6'>
|
||||||
<div className='flex justify-end gap-2'>
|
<div className='flex justify-end gap-2'>
|
||||||
|
|||||||
@@ -251,7 +251,11 @@ const FormFinanceAdd = ({
|
|||||||
}
|
}
|
||||||
required
|
required
|
||||||
isClearable
|
isClearable
|
||||||
isDisabled={!formik.values.party_type_option?.value}
|
isDisabled={
|
||||||
|
!formik.values.party_type_option?.value ||
|
||||||
|
(type === 'edit' &&
|
||||||
|
formik.values.party_type_option.value == 'SUPPLIER')
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<DateInput
|
<DateInput
|
||||||
label='Tanggal'
|
label='Tanggal'
|
||||||
@@ -423,7 +427,7 @@ const FormFinanceAdd = ({
|
|||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
className='w-min-24'
|
className='w-min-24'
|
||||||
disabled={formik.isSubmitting || !formik.isValid}
|
disabled={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ const FormFinanceAddInitialBalance = ({
|
|||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
className='w-min-24'
|
className='w-min-24'
|
||||||
disabled={formik.isSubmitting || !formik.isValid}
|
disabled={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ const FormFinanceInjection = ({
|
|||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
className='w-min-24'
|
className='w-min-24'
|
||||||
disabled={formik.isSubmitting || !formik.isValid}
|
disabled={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import useSWR from 'swr';
|
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@@ -18,6 +17,7 @@ import {
|
|||||||
Movement,
|
Movement,
|
||||||
} from '@/types/api/inventory/movement';
|
} from '@/types/api/inventory/movement';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { formatNumber } from '@/lib/helper';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
MovementFormSchema,
|
MovementFormSchema,
|
||||||
@@ -54,6 +54,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
const [selectedProducts, setSelectedProducts] = useState<number[]>([]);
|
const [selectedProducts, setSelectedProducts] = useState<number[]>([]);
|
||||||
const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]);
|
const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]);
|
||||||
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
||||||
|
const [productQtyErrorShown, setProductQtyErrorShown] = useState(false);
|
||||||
|
const [deliveryQtyErrorShown, setDeliveryQtyErrorShown] = useState(false);
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
|
||||||
// ===== FORM HANDLERS =====
|
// ===== FORM HANDLERS =====
|
||||||
const createMovementHandler = useCallback(
|
const createMovementHandler = useCallback(
|
||||||
@@ -82,22 +85,21 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== API DATA FETCHING =====
|
|
||||||
const allProductWarehousesUrl = `${ProductWarehouseApi.basePath}`;
|
|
||||||
const { data: allProductWarehouses } = useSWR(
|
|
||||||
allProductWarehousesUrl,
|
|
||||||
ProductWarehouseApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== USE SELECT HOOKS =====
|
// ===== USE SELECT HOOKS =====
|
||||||
const {
|
const {
|
||||||
setInputValue: setWarehouseSelectInputValue,
|
setInputValue: setWarehouseSelectInputValue,
|
||||||
isLoadingOptions: isLoadingWarehouses,
|
isLoadingOptions: isLoadingWarehouses,
|
||||||
loadMore: loadMoreWarehouses,
|
loadMore: loadMoreWarehouses,
|
||||||
rawData: warehouses,
|
rawData: warehouses,
|
||||||
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name', 'search', {
|
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name', 'search');
|
||||||
flag: 'EKSPEDISI',
|
|
||||||
});
|
const { rawData: allProductWarehouses } = useSelect<ProductWarehouse>(
|
||||||
|
ProductWarehouseApi.basePath,
|
||||||
|
'id',
|
||||||
|
'product.name',
|
||||||
|
'search',
|
||||||
|
{ limit: '100' }
|
||||||
|
);
|
||||||
|
|
||||||
// ===== SELECT INPUT DATA =====
|
// ===== SELECT INPUT DATA =====
|
||||||
const {
|
const {
|
||||||
@@ -106,6 +108,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
isLoadingOptions: isLoadingSuppliers,
|
isLoadingOptions: isLoadingSuppliers,
|
||||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
|
||||||
category: 'BOP',
|
category: 'BOP',
|
||||||
|
flag: 'EKSPEDISI',
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== DATA PROCESSING =====
|
// ===== DATA PROCESSING =====
|
||||||
@@ -322,16 +325,18 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const productWarehouseOptions = isResponseSuccess(productWarehouses)
|
const productWarehouseOptions = useMemo(() => {
|
||||||
? productWarehouses?.data.map((pw) => ({
|
return isResponseSuccess(productWarehouses)
|
||||||
value: pw.product.id,
|
? productWarehouses?.data.map((pw) => ({
|
||||||
label: pw.product.name,
|
value: pw.product.id,
|
||||||
product_id: pw.product.id,
|
label: pw.product.name,
|
||||||
warehouse_id: pw.warehouse.id,
|
product_id: pw.product.id,
|
||||||
warehouse_name: pw.warehouse.name,
|
warehouse_id: pw.warehouse.id,
|
||||||
quantity: pw.quantity,
|
warehouse_name: pw.warehouse.name,
|
||||||
}))
|
quantity: pw.quantity,
|
||||||
: [];
|
}))
|
||||||
|
: [];
|
||||||
|
}, [productWarehouses]);
|
||||||
|
|
||||||
// ===== HELPER FUNCTIONS =====
|
// ===== HELPER FUNCTIONS =====
|
||||||
const isRepeaterInputError = <T extends 'products' | 'deliveries'>(
|
const isRepeaterInputError = <T extends 'products' | 'deliveries'>(
|
||||||
@@ -464,19 +469,24 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
formik.setFieldValue('products', newProducts);
|
formik.setFieldValue('products', newProducts);
|
||||||
}, []);
|
}, [formik.values.products]);
|
||||||
|
|
||||||
const removeProduct = useCallback((i: number) => {
|
const removeProduct = useCallback(
|
||||||
const updatedProducts =
|
(i: number) => {
|
||||||
formik.values.products?.reduce((acc: ProductSchema[], item, index) => {
|
const updatedProducts = formik.values.products?.filter(
|
||||||
if (index !== i) {
|
(_, idx) => idx !== i
|
||||||
acc.push(item);
|
);
|
||||||
}
|
formik.setFieldValue('products', updatedProducts);
|
||||||
return acc;
|
|
||||||
}, []) ?? [];
|
|
||||||
|
|
||||||
formik.setFieldValue('products', updatedProducts);
|
setSelectedProducts([]);
|
||||||
}, []);
|
|
||||||
|
if (productQtyErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setProductQtyErrorShown(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formik.values.products, productQtyErrorShown, setSelectedProducts]
|
||||||
|
);
|
||||||
|
|
||||||
const bulkRemoveProduct = useCallback(() => {
|
const bulkRemoveProduct = useCallback(() => {
|
||||||
const updatedProducts =
|
const updatedProducts =
|
||||||
@@ -485,7 +495,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
) ?? [];
|
) ?? [];
|
||||||
formik.setFieldValue('products', updatedProducts);
|
formik.setFieldValue('products', updatedProducts);
|
||||||
setSelectedProducts([]);
|
setSelectedProducts([]);
|
||||||
}, [formik, selectedProducts, setSelectedProducts]);
|
|
||||||
|
if (productQtyErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setProductQtyErrorShown(false);
|
||||||
|
}
|
||||||
|
}, [formik, selectedProducts, setSelectedProducts, productQtyErrorShown]);
|
||||||
|
|
||||||
const handleProductChange = useCallback(
|
const handleProductChange = useCallback(
|
||||||
(idx: number, val: OptionType | OptionType[] | null) => {
|
(idx: number, val: OptionType | OptionType[] | null) => {
|
||||||
@@ -543,19 +558,24 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}, []);
|
}, [formik.values.deliveries]);
|
||||||
|
|
||||||
const removeDelivery = useCallback((i: number) => {
|
const removeDelivery = useCallback(
|
||||||
const updatedDeliveries =
|
(i: number) => {
|
||||||
formik.values.deliveries?.reduce((acc: DeliverySchema[], item, index) => {
|
const updatedDeliveries = formik.values.deliveries?.filter(
|
||||||
if (index !== i) {
|
(_, idx) => idx !== i
|
||||||
acc.push(item);
|
);
|
||||||
}
|
formik.setFieldValue('deliveries', updatedDeliveries);
|
||||||
return acc;
|
|
||||||
}, []) ?? [];
|
|
||||||
|
|
||||||
formik.setFieldValue('deliveries', updatedDeliveries);
|
setSelectedDeliveries([]);
|
||||||
}, []);
|
|
||||||
|
if (deliveryQtyErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDeliveryQtyErrorShown(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formik.values.deliveries, deliveryQtyErrorShown, setSelectedDeliveries]
|
||||||
|
);
|
||||||
|
|
||||||
const bulkRemoveDelivery = useCallback(() => {
|
const bulkRemoveDelivery = useCallback(() => {
|
||||||
const updatedDeliveries =
|
const updatedDeliveries =
|
||||||
@@ -564,7 +584,17 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
) ?? [];
|
) ?? [];
|
||||||
formik.setFieldValue('deliveries', updatedDeliveries);
|
formik.setFieldValue('deliveries', updatedDeliveries);
|
||||||
setSelectedDeliveries([]);
|
setSelectedDeliveries([]);
|
||||||
}, [formik, selectedDeliveries, setSelectedDeliveries]);
|
|
||||||
|
if (deliveryQtyErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDeliveryQtyErrorShown(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
formik,
|
||||||
|
selectedDeliveries,
|
||||||
|
setSelectedDeliveries,
|
||||||
|
deliveryQtyErrorShown,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleDeliverySelectAllChange = useCallback(
|
const handleDeliverySelectAllChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -638,26 +668,29 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliveryCostChange = useCallback((idx: number, value: number) => {
|
const handleDeliveryCostChange = useCallback(
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value);
|
(idx: number, value: number) => {
|
||||||
|
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value);
|
||||||
|
|
||||||
const delivery = formik.values.deliveries?.[idx];
|
const delivery = formik.values.deliveries?.[idx];
|
||||||
if (delivery) {
|
if (delivery) {
|
||||||
const productQty = delivery.products.reduce(
|
const productQty = delivery.products.reduce(
|
||||||
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
0
|
0
|
||||||
);
|
|
||||||
if (productQty > 0 && value > 0) {
|
|
||||||
const perItem = value / productQty;
|
|
||||||
formik.setFieldValue(
|
|
||||||
`deliveries.${idx}.delivery_cost_per_item`,
|
|
||||||
perItem
|
|
||||||
);
|
);
|
||||||
} else if (value === 0) {
|
if (productQty > 0 && value > 0) {
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0);
|
const perItem = value / productQty;
|
||||||
|
formik.setFieldValue(
|
||||||
|
`deliveries.${idx}.delivery_cost_per_item`,
|
||||||
|
perItem
|
||||||
|
);
|
||||||
|
} else if (value === 0) {
|
||||||
|
formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, []);
|
[formik.values.deliveries]
|
||||||
|
);
|
||||||
|
|
||||||
const handleDeliveryCostPerItemChange = useCallback(
|
const handleDeliveryCostPerItemChange = useCallback(
|
||||||
(idx: number, value: number) => {
|
(idx: number, value: number) => {
|
||||||
@@ -677,7 +710,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[formik.values.deliveries]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeliveryCostChangeWrapper = useCallback(
|
const handleDeliveryCostChangeWrapper = useCallback(
|
||||||
@@ -696,17 +729,52 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
[handleDeliveryCostPerItemChange]
|
[handleDeliveryCostPerItemChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
// UTILITY FUNCTIONS
|
const getAvailableProductOptions = useCallback(
|
||||||
|
(currentIdx: number) => {
|
||||||
|
const selectedProductIds =
|
||||||
|
formik.values.products
|
||||||
|
?.filter((p, idx) => {
|
||||||
|
return idx !== currentIdx && p.product_id && p.product_id !== 0;
|
||||||
|
})
|
||||||
|
.map((p) => p.product_id) || [];
|
||||||
|
|
||||||
|
return productWarehouseOptions.filter(
|
||||||
|
(pw) => !selectedProductIds.includes(pw.product_id)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[formik.values.products, productWarehouseOptions]
|
||||||
|
);
|
||||||
|
|
||||||
const getFilteredProductWarehouseOptions = useCallback(() => {
|
const getFilteredProductWarehouseOptions = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
formik.values.products
|
formik.values.products
|
||||||
?.filter((p) => p.product)
|
?.filter((p) => p.product)
|
||||||
.map((p) => ({
|
.map((p) => {
|
||||||
value: p.product_id,
|
const totalQtyUsed =
|
||||||
label: (p.product as OptionType)?.label,
|
formik.values.deliveries?.reduce((total, d) => {
|
||||||
})) ?? []
|
const productQty = d.products.reduce((sum, deliveryProduct) => {
|
||||||
|
if (deliveryProduct.product_id === p.product_id) {
|
||||||
|
return sum + (Number(deliveryProduct.product_qty) || 0);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}, 0);
|
||||||
|
return total + productQty;
|
||||||
|
}, 0) || 0;
|
||||||
|
|
||||||
|
const availableQty = Number(p.product_qty) - totalQtyUsed;
|
||||||
|
|
||||||
|
if (availableQty > 0) {
|
||||||
|
return {
|
||||||
|
value: p.product_id,
|
||||||
|
label: (p.product as OptionType)?.label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((option) => option !== null) ?? []
|
||||||
);
|
);
|
||||||
}, [formik.values.products]);
|
}, [formik.values.products, formik.values.deliveries]);
|
||||||
|
|
||||||
const getAvailableStock = useCallback(
|
const getAvailableStock = useCallback(
|
||||||
(productId: number) => {
|
(productId: number) => {
|
||||||
@@ -730,10 +798,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
const remainingStock = availableStock - requestedQty;
|
const remainingStock = availableStock - requestedQty;
|
||||||
|
|
||||||
if (requestedQty > 0) {
|
if (requestedQty > 0) {
|
||||||
return `Sisa: ${remainingStock.toLocaleString('en-US')}`;
|
return `Sisa: ${formatNumber(remainingStock)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Tersedia: ${availableStock.toLocaleString('en-US')}`;
|
return `Tersedia: ${formatNumber(availableStock)}`;
|
||||||
},
|
},
|
||||||
[formik.values.products, getAvailableStock, type]
|
[formik.values.products, getAvailableStock, type]
|
||||||
);
|
);
|
||||||
@@ -753,12 +821,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
if (!relatedProduct) return undefined;
|
if (!relatedProduct) return undefined;
|
||||||
|
|
||||||
const totalQtyUsed =
|
const totalQtyUsed =
|
||||||
formik.values.deliveries?.reduce((total, d, dIdx) => {
|
formik.values.deliveries?.reduce((total, d) => {
|
||||||
const productQty = d.products.reduce((sum, p, pIdx) => {
|
const productQty = d.products.reduce((sum, p) => {
|
||||||
if (
|
if (p.product_id === deliveryProduct.product_id) {
|
||||||
p.product_id === deliveryProduct.product_id &&
|
|
||||||
!(dIdx === deliveryIdx && pIdx === productIdx)
|
|
||||||
) {
|
|
||||||
return sum + (Number(p.product_qty) || 0);
|
return sum + (Number(p.product_qty) || 0);
|
||||||
}
|
}
|
||||||
return sum;
|
return sum;
|
||||||
@@ -767,7 +832,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
}, 0) || 0;
|
}, 0) || 0;
|
||||||
|
|
||||||
const availableQty = Number(relatedProduct.product_qty) - totalQtyUsed;
|
const availableQty = Number(relatedProduct.product_qty) - totalQtyUsed;
|
||||||
return `Tersedia: ${availableQty.toLocaleString('en-US')}`;
|
|
||||||
|
const displayQty = availableQty > 0 ? availableQty : 0;
|
||||||
|
|
||||||
|
return `Tersedia: ${formatNumber(displayQty)}`;
|
||||||
},
|
},
|
||||||
[formik.values.deliveries, formik.values.products, type]
|
[formik.values.deliveries, formik.values.products, type]
|
||||||
);
|
);
|
||||||
@@ -817,7 +885,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
const requestedQty = Number(product.product_qty) || 0;
|
const requestedQty = Number(product.product_qty) || 0;
|
||||||
|
|
||||||
if (requestedQty > availableStock) {
|
if (requestedQty > availableStock) {
|
||||||
return `Qty melebihi stok tersedia! Maksimal: ${availableStock.toLocaleString('en-US')}`;
|
return `Qty melebihi stok tersedia! Maksimal: ${formatNumber(availableStock)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -966,20 +1034,29 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
formik.values.deliveries
|
formik.values.deliveries
|
||||||
?.map((d) =>
|
?.map((d, idx) => ({
|
||||||
d.products.reduce(
|
idx,
|
||||||
|
productQty: d.products.reduce(
|
||||||
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
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(','),
|
.join('|'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
formik.values.source_warehouse_id &&
|
formik.values.source_warehouse_id &&
|
||||||
type !== 'edit' &&
|
type !== 'edit' &&
|
||||||
type !== 'detail'
|
type !== 'detail' &&
|
||||||
|
!isInitialized
|
||||||
) {
|
) {
|
||||||
if (formik.values.products.length === 0) {
|
if (formik.values.products.length === 0) {
|
||||||
formik.setFieldValue('products', [
|
formik.setFieldValue('products', [
|
||||||
@@ -1011,8 +1088,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
setIsInitialized(true);
|
||||||
}
|
}
|
||||||
}, [formik.values.source_warehouse_id]);
|
}, [formik.values.source_warehouse_id, isInitialized, type]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -1039,6 +1117,113 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
formik.errors.destination_warehouse_id,
|
formik.errors.destination_warehouse_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formik.values.products && formik.values.deliveries) {
|
||||||
|
const productIds = formik.values.products.map((p) => p.product_id);
|
||||||
|
|
||||||
|
const updatedDeliveries = formik.values.deliveries.map((delivery) => {
|
||||||
|
const deliveryProduct = delivery.products[0];
|
||||||
|
if (deliveryProduct && deliveryProduct.product_id !== 0) {
|
||||||
|
if (!productIds.includes(deliveryProduct.product_id)) {
|
||||||
|
return {
|
||||||
|
...delivery,
|
||||||
|
products: [
|
||||||
|
{
|
||||||
|
product: null,
|
||||||
|
product_id: 0,
|
||||||
|
product_qty: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
delivery_cost: '',
|
||||||
|
delivery_cost_per_item: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return delivery;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasChanges = formik.values.deliveries.some(
|
||||||
|
(delivery, idx) =>
|
||||||
|
delivery.products[0]?.product_id !==
|
||||||
|
updatedDeliveries[idx]?.products[0]?.product_id ||
|
||||||
|
delivery.delivery_cost !== updatedDeliveries[idx]?.delivery_cost
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
formik.setFieldValue('deliveries', updatedDeliveries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [formik.values.products]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (productQtyErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setProductQtyErrorShown(false);
|
||||||
|
}
|
||||||
|
}, [formik.values.products?.map((p) => p.product_qty).join(',')]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (deliveryQtyErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDeliveryQtyErrorShown(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
formik.values.deliveries
|
||||||
|
?.map((d) => d.products.map((p) => p.product_qty).join(','))
|
||||||
|
.join('|'),
|
||||||
|
formik.values.products?.map((p) => p.product_qty).join(','),
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasExceededStock && !productQtyErrorShown && type !== 'detail') {
|
||||||
|
const firstErrorIndex = formik.values.products?.findIndex(
|
||||||
|
(product, idx) => getProductQtyError(idx) !== null
|
||||||
|
);
|
||||||
|
if (firstErrorIndex !== undefined && firstErrorIndex >= 0) {
|
||||||
|
const errorMsg = getProductQtyError(firstErrorIndex);
|
||||||
|
if (errorMsg) {
|
||||||
|
toast.error(errorMsg, { duration: Infinity });
|
||||||
|
setProductQtyErrorShown(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
hasExceededStock,
|
||||||
|
productQtyErrorShown,
|
||||||
|
type,
|
||||||
|
formik.values.products,
|
||||||
|
getProductQtyError,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasInvalidQty && !deliveryQtyErrorShown && type !== 'detail') {
|
||||||
|
const firstError = formik.values.deliveries?.find(
|
||||||
|
(delivery, deliveryIdx) =>
|
||||||
|
delivery.products.some(
|
||||||
|
(product, productIdx) =>
|
||||||
|
getDeliveryQtyError(deliveryIdx, productIdx) !== null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (firstError) {
|
||||||
|
const deliveryIdx = formik.values.deliveries?.indexOf(firstError);
|
||||||
|
if (deliveryIdx !== undefined && deliveryIdx >= 0) {
|
||||||
|
const errorMsg = getDeliveryQtyError(deliveryIdx, 0);
|
||||||
|
if (errorMsg) {
|
||||||
|
toast.error(errorMsg, { duration: Infinity });
|
||||||
|
setDeliveryQtyErrorShown(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
hasInvalidQty,
|
||||||
|
deliveryQtyErrorShown,
|
||||||
|
type,
|
||||||
|
formik.values.deliveries,
|
||||||
|
formik.values.products,
|
||||||
|
getDeliveryQtyError,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleValidateForm = async () => {
|
const handleValidateForm = async () => {
|
||||||
const errors = await formik.validateForm();
|
const errors = await formik.validateForm();
|
||||||
|
|
||||||
@@ -1340,7 +1525,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
required
|
required
|
||||||
value={product.product ?? undefined}
|
value={product.product ?? undefined}
|
||||||
onChange={(val) => handleProductChange(idx, val)}
|
onChange={(val) => handleProductChange(idx, val)}
|
||||||
options={productWarehouseOptions}
|
options={getAvailableProductOptions(idx)}
|
||||||
onInputChange={setProductWarehouseSelectInputValue}
|
onInputChange={setProductWarehouseSelectInputValue}
|
||||||
onMenuScrollToBottom={loadMoreProductWarehouses}
|
onMenuScrollToBottom={loadMoreProductWarehouses}
|
||||||
isLoading={isLoadingProductWarehouses}
|
isLoading={isLoadingProductWarehouses}
|
||||||
@@ -1543,7 +1728,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{formik.values.deliveries?.map((delivery, idx) => (
|
{formik.values.deliveries?.map((delivery, idx) => (
|
||||||
<tr key={`delivery-row-${idx}`}>
|
<tr
|
||||||
|
key={`delivery-row-${idx}-${delivery.supplier_id}-${delivery.vehicle_plate}`}
|
||||||
|
>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td className='align-middle!'>
|
<td className='align-middle!'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
@@ -1857,8 +2044,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
className='px-4'
|
className='px-4'
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
disabled={
|
disabled={
|
||||||
hasInvalidQty ||
|
|
||||||
hasExceededStock ||
|
|
||||||
formik.isSubmitting ||
|
formik.isSubmitting ||
|
||||||
(formik.values.source_warehouse_id ===
|
(formik.values.source_warehouse_id ===
|
||||||
formik.values.destination_warehouse_id &&
|
formik.values.destination_warehouse_id &&
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@@ -17,7 +16,6 @@ import SelectInput, {
|
|||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +23,7 @@ import {
|
|||||||
ProductFormValues,
|
ProductFormValues,
|
||||||
UpdateProductFormSchema,
|
UpdateProductFormSchema,
|
||||||
} from '@/components/pages/master-data/product/form/ProductForm.schema';
|
} from '@/components/pages/master-data/product/form/ProductForm.schema';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError } from '@/lib/api-helper';
|
||||||
import {
|
import {
|
||||||
Product,
|
Product,
|
||||||
CreateProductPayload,
|
CreateProductPayload,
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ import {
|
|||||||
|
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||||
import { formatDate, formatNumber } from '@/lib/helper';
|
import { formatDate, formatNumber } from '@/lib/helper';
|
||||||
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import ApprovalSteps, {
|
import ApprovalSteps, {
|
||||||
useApprovalSteps,
|
useApprovalSteps,
|
||||||
@@ -423,7 +422,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocations,
|
isLoadingOptions: isLoadingLocations,
|
||||||
loadMore: loadMoreLocations,
|
loadMore: loadMoreLocations,
|
||||||
hasMore: hasMoreLocations,
|
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -432,7 +430,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
rawData: projectFlocksRawData,
|
rawData: projectFlocksRawData,
|
||||||
isLoadingOptions: isLoadingProjectFlocks,
|
isLoadingOptions: isLoadingProjectFlocks,
|
||||||
loadMore: loadMoreProjectFlocks,
|
loadMore: loadMoreProjectFlocks,
|
||||||
hasMore: hasMoreProjectFlocks,
|
|
||||||
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
|
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
|
||||||
location_id: selectedProjectFlockLocationId,
|
location_id: selectedProjectFlockLocationId,
|
||||||
});
|
});
|
||||||
@@ -531,7 +528,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
rawData: stockProducts,
|
rawData: stockProducts,
|
||||||
isLoadingOptions: isLoadingStockProducts,
|
isLoadingOptions: isLoadingStockProducts,
|
||||||
loadMore: loadMoreStockProducts,
|
loadMore: loadMoreStockProducts,
|
||||||
hasMore: hasMoreStockProducts,
|
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
|
||||||
flags: 'PAKAN,OVK',
|
flags: 'PAKAN,OVK',
|
||||||
location_id: stockProductsLocationId,
|
location_id: stockProductsLocationId,
|
||||||
@@ -539,11 +535,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: depletionProductOptions,
|
|
||||||
rawData: depletionProductsData,
|
rawData: depletionProductsData,
|
||||||
isLoadingOptions: isLoadingDepletionProducts,
|
isLoadingOptions: isLoadingDepletionProducts,
|
||||||
loadMore: loadMoreDepletionProducts,
|
loadMore: loadMoreDepletionProducts,
|
||||||
hasMore: hasMoreDepletionProducts,
|
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
|
||||||
location_id: depletionProductsLocationId,
|
location_id: depletionProductsLocationId,
|
||||||
kandang_id: depletionProductsKandangId,
|
kandang_id: depletionProductsKandangId,
|
||||||
@@ -584,11 +578,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}, [nextDayRecordingData]);
|
}, [nextDayRecordingData]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: eggProductOptions,
|
|
||||||
rawData: eggProductsData,
|
rawData: eggProductsData,
|
||||||
isLoadingOptions: isLoadingEggProducts,
|
isLoadingOptions: isLoadingEggProducts,
|
||||||
loadMore: loadMoreEggProducts,
|
loadMore: loadMoreEggProducts,
|
||||||
hasMore: hasMoreEggProducts,
|
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
||||||
search: 'telur',
|
search: 'telur',
|
||||||
location_id: eggProductsLocationId,
|
location_id: eggProductsLocationId,
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ import { generateUniformityExcel } from '@/components/pages/production/uniformit
|
|||||||
import Dropdown from '@/components/Dropdown';
|
import Dropdown from '@/components/Dropdown';
|
||||||
import Menu from '@/components/menu/Menu';
|
import Menu from '@/components/menu/Menu';
|
||||||
import MenuItem from '@/components/menu/MenuItem';
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import {
|
||||||
|
UniformityTableFilterSchema,
|
||||||
|
type UniformityTableFilterValues,
|
||||||
|
} from '@/components/pages/production/uniformity/UniformityTableFilter.schema';
|
||||||
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||||
|
|
||||||
const UniformityConfirmationPreview = ({
|
const UniformityConfirmationPreview = ({
|
||||||
uniformity,
|
uniformity,
|
||||||
@@ -241,7 +248,6 @@ const UniformityTable = () => {
|
|||||||
options: filterLocationOptions,
|
options: filterLocationOptions,
|
||||||
isLoadingOptions: isLoadingFilterLocations,
|
isLoadingOptions: isLoadingFilterLocations,
|
||||||
loadMore: loadMoreFilterLocations,
|
loadMore: loadMoreFilterLocations,
|
||||||
hasMore: hasMoreFilterLocations,
|
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
// ===== FETCH PROJECT FLOCKS DATA FOR FILTER =====
|
// ===== FETCH PROJECT FLOCKS DATA FOR FILTER =====
|
||||||
@@ -251,7 +257,6 @@ const UniformityTable = () => {
|
|||||||
rawData: filterProjectFlocksRawData,
|
rawData: filterProjectFlocksRawData,
|
||||||
isLoadingOptions: isLoadingFilterProjectFlocks,
|
isLoadingOptions: isLoadingFilterProjectFlocks,
|
||||||
loadMore: loadMoreFilterProjectFlocks,
|
loadMore: loadMoreFilterProjectFlocks,
|
||||||
hasMore: hasMoreFilterProjectFlocks,
|
|
||||||
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
|
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
|
||||||
location_id: filterProjectFlockLocationId,
|
location_id: filterProjectFlockLocationId,
|
||||||
});
|
});
|
||||||
@@ -316,6 +321,34 @@ const UniformityTable = () => {
|
|||||||
}
|
}
|
||||||
}, [projectFlockKandangLookup]);
|
}, [projectFlockKandangLookup]);
|
||||||
|
|
||||||
|
// ===== FORMIK FILTER =====
|
||||||
|
const filterFormik = useFormik<UniformityTableFilterValues>({
|
||||||
|
initialValues: {
|
||||||
|
start_date: filterStartDate,
|
||||||
|
end_date: filterEndDate,
|
||||||
|
location: filterLocation,
|
||||||
|
project_flock: filterProjectFlock,
|
||||||
|
project_flock_kandang_id: filterProjectFlockKandangId,
|
||||||
|
kandang: filterKandang,
|
||||||
|
},
|
||||||
|
validationSchema: UniformityTableFilterSchema,
|
||||||
|
enableReinitialize: true,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
setFilterStartDate(values.start_date);
|
||||||
|
setFilterEndDate(values.end_date);
|
||||||
|
setFilterLocation(values.location ?? null);
|
||||||
|
setFilterProjectFlock(values.project_flock ?? null);
|
||||||
|
setFilterKandang(values.kandang ?? null);
|
||||||
|
|
||||||
|
setIsSubmitted(true);
|
||||||
|
filterModal.closeModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== FORMIK ERROR LIST =====
|
||||||
|
const { formErrorList, close, handleFormSubmit } =
|
||||||
|
useFormikErrorList(filterFormik);
|
||||||
|
|
||||||
// ===== BUILD SWR KEY WITH FILTERS =====
|
// ===== BUILD SWR KEY WITH FILTERS =====
|
||||||
const uniformitySwrKey = useMemo(() => {
|
const uniformitySwrKey = useMemo(() => {
|
||||||
const basePath = UniformityApi.basePath;
|
const basePath = UniformityApi.basePath;
|
||||||
@@ -372,29 +405,54 @@ const UniformityTable = () => {
|
|||||||
const handleFilterLocationChange = useCallback(
|
const handleFilterLocationChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(val: OptionType | OptionType[] | null) => {
|
||||||
const location = val as OptionType | null;
|
const location = val as OptionType | null;
|
||||||
|
const locationId = Number(location?.value) || 0;
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('location', location);
|
||||||
|
filterFormik.setFieldValue('location_id', locationId);
|
||||||
|
|
||||||
setFilterLocation(location);
|
setFilterLocation(location);
|
||||||
setFilterProjectFlock(null);
|
setFilterProjectFlock(null);
|
||||||
setFilterKandang(null);
|
setFilterKandang(null);
|
||||||
setFilterProjectFlockLocationId(
|
setFilterProjectFlockLocationId(
|
||||||
location ? location.value.toString() : ''
|
location ? location.value.toString() : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('project_flock', null);
|
||||||
|
filterFormik.setFieldValue('project_flock_id', 0);
|
||||||
|
filterFormik.setFieldValue('kandang', null);
|
||||||
|
filterFormik.setFieldValue('kandang_id', 0);
|
||||||
},
|
},
|
||||||
[]
|
[filterFormik]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilterProjectFlockChange = useCallback(
|
const handleFilterProjectFlockChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(val: OptionType | OptionType[] | null) => {
|
||||||
setFilterProjectFlock(val as OptionType | null);
|
const projectFlock = val as OptionType | null;
|
||||||
|
const projectFlockId = Number(projectFlock?.value) || 0;
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('project_flock', projectFlock);
|
||||||
|
filterFormik.setFieldValue('project_flock_id', projectFlockId);
|
||||||
|
|
||||||
|
setFilterProjectFlock(projectFlock);
|
||||||
setFilterKandang(null);
|
setFilterKandang(null);
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('kandang', null);
|
||||||
|
filterFormik.setFieldValue('kandang_id', 0);
|
||||||
},
|
},
|
||||||
[]
|
[filterFormik]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilterKandangChange = useCallback(
|
const handleFilterKandangChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(val: OptionType | OptionType[] | null) => {
|
||||||
setFilterKandang(val as OptionType | null);
|
const kandang = val as OptionType | null;
|
||||||
|
const kandangId = Number(kandang?.value) || 0;
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('kandang', kandang);
|
||||||
|
filterFormik.setFieldValue('kandang_id', kandangId);
|
||||||
|
|
||||||
|
setFilterKandang(kandang);
|
||||||
},
|
},
|
||||||
[]
|
[filterFormik]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleResetFilters = useCallback(() => {
|
const handleResetFilters = useCallback(() => {
|
||||||
@@ -405,41 +463,34 @@ const UniformityTable = () => {
|
|||||||
setFilterProjectFlockKandangId(undefined);
|
setFilterProjectFlockKandangId(undefined);
|
||||||
setFilterStartDate('');
|
setFilterStartDate('');
|
||||||
setFilterEndDate('');
|
setFilterEndDate('');
|
||||||
}, []);
|
setFilterErrors({});
|
||||||
|
|
||||||
|
filterFormik.resetForm();
|
||||||
|
}, [filterFormik]);
|
||||||
|
|
||||||
|
const handleFilterStartDateChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterStartDate(value);
|
||||||
|
filterFormik.setFieldValue('start_date', value);
|
||||||
|
},
|
||||||
|
[filterFormik]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFilterEndDateChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterEndDate(value);
|
||||||
|
filterFormik.setFieldValue('end_date', value);
|
||||||
|
},
|
||||||
|
[filterFormik]
|
||||||
|
);
|
||||||
|
|
||||||
const handleApplyFilters = useCallback(() => {
|
const handleApplyFilters = useCallback(() => {
|
||||||
const errors: Record<string, string> = {};
|
handleFormSubmit(
|
||||||
|
new Event('submit') as unknown as React.FormEvent<HTMLFormElement>
|
||||||
if (!filterStartDate) {
|
);
|
||||||
errors.start_date = 'Tanggal mulai wajib diisi';
|
}, [handleFormSubmit]);
|
||||||
}
|
|
||||||
if (!filterEndDate) {
|
|
||||||
errors.end_date = 'Tanggal akhir wajib diisi';
|
|
||||||
}
|
|
||||||
if (!filterLocation) {
|
|
||||||
errors.location = 'Lokasi wajib dipilih';
|
|
||||||
}
|
|
||||||
if (!filterProjectFlock) {
|
|
||||||
errors.project_flock = 'Project Flock wajib dipilih';
|
|
||||||
}
|
|
||||||
if (!filterKandang) {
|
|
||||||
errors.kandang = 'Kandang wajib dipilih';
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilterErrors(errors);
|
|
||||||
|
|
||||||
if (Object.keys(errors).length === 0) {
|
|
||||||
setIsSubmitted(true);
|
|
||||||
filterModal.closeModal();
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
filterModal,
|
|
||||||
filterStartDate,
|
|
||||||
filterEndDate,
|
|
||||||
filterLocation,
|
|
||||||
filterProjectFlock,
|
|
||||||
filterKandang,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const selectedRowIds = useMemo(() => {
|
const selectedRowIds = useMemo(() => {
|
||||||
return Object.keys(rowSelection)
|
return Object.keys(rowSelection)
|
||||||
@@ -1136,108 +1187,117 @@ const UniformityTable = () => {
|
|||||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error List Alert */}
|
||||||
|
{formErrorList.length > 0 && (
|
||||||
|
<div className='w-full px-4'>
|
||||||
|
<AlertErrorList formErrorList={formErrorList} onClose={close} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='space-y-4 px-4'>
|
<div className='space-y-4 px-4'>
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
||||||
<div>
|
<div>
|
||||||
<DateInput
|
<DateInput
|
||||||
label='Tanggal'
|
required
|
||||||
|
label='Tanggal mulai'
|
||||||
name='start_date'
|
name='start_date'
|
||||||
value={filterStartDate}
|
value={filterFormik.values.start_date}
|
||||||
onChange={(e) => {
|
onChange={handleFilterStartDateChange}
|
||||||
setFilterStartDate(e.target.value);
|
onBlur={filterFormik.handleBlur}
|
||||||
setFilterErrors((prev) => ({ ...prev, start_date: '' }));
|
isError={
|
||||||
}}
|
filterFormik.touched.start_date &&
|
||||||
|
Boolean(filterFormik.errors.start_date)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.start_date}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.start_date && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.start_date}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<DateInput
|
<DateInput
|
||||||
label=' '
|
required
|
||||||
|
label='Tanggal akhir'
|
||||||
name='end_date'
|
name='end_date'
|
||||||
value={filterEndDate}
|
value={filterFormik.values.end_date}
|
||||||
onChange={(e) => {
|
onChange={handleFilterEndDateChange}
|
||||||
setFilterEndDate(e.target.value);
|
onBlur={filterFormik.handleBlur}
|
||||||
setFilterErrors((prev) => ({ ...prev, end_date: '' }));
|
isError={
|
||||||
}}
|
filterFormik.touched.end_date &&
|
||||||
|
Boolean(filterFormik.errors.end_date)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.end_date}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.end_date && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.end_date}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
|
required
|
||||||
label='Lokasi'
|
label='Lokasi'
|
||||||
placeholder='Pilih Lokasi...'
|
placeholder='Pilih Lokasi...'
|
||||||
value={filterLocation}
|
value={filterFormik.values.location}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleFilterLocationChange(value);
|
handleFilterLocationChange(value);
|
||||||
setFilterErrors((prev) => ({ ...prev, location: '' }));
|
|
||||||
}}
|
}}
|
||||||
options={filterLocationOptions}
|
options={filterLocationOptions}
|
||||||
onInputChange={setFilterLocationInputValue}
|
onInputChange={setFilterLocationInputValue}
|
||||||
isLoading={isLoadingFilterLocations}
|
isLoading={isLoadingFilterLocations}
|
||||||
onMenuScrollToBottom={loadMoreFilterLocations}
|
onMenuScrollToBottom={loadMoreFilterLocations}
|
||||||
|
isError={
|
||||||
|
filterFormik.touched.location &&
|
||||||
|
Boolean(filterFormik.errors.location)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.location}
|
||||||
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.location && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.location}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
|
required
|
||||||
label='Project Flock'
|
label='Project Flock'
|
||||||
placeholder='Pilih Project Flock...'
|
placeholder='Pilih Project Flock...'
|
||||||
value={filterProjectFlock}
|
value={filterFormik.values.project_flock}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleFilterProjectFlockChange(value);
|
handleFilterProjectFlockChange(value);
|
||||||
setFilterErrors((prev) => ({ ...prev, project_flock: '' }));
|
|
||||||
}}
|
}}
|
||||||
options={filterProjectFlockOptions}
|
options={filterProjectFlockOptions}
|
||||||
onInputChange={setFilterProjectFlockSearchValue}
|
onInputChange={setFilterProjectFlockSearchValue}
|
||||||
isLoading={isLoadingFilterProjectFlocks}
|
isLoading={isLoadingFilterProjectFlocks}
|
||||||
onMenuScrollToBottom={loadMoreFilterProjectFlocks}
|
onMenuScrollToBottom={loadMoreFilterProjectFlocks}
|
||||||
isDisabled={!filterLocation}
|
isDisabled={!filterFormik.values.location}
|
||||||
|
isError={
|
||||||
|
filterFormik.touched.project_flock &&
|
||||||
|
Boolean(filterFormik.errors.project_flock)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.project_flock}
|
||||||
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.project_flock && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.project_flock}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
|
required
|
||||||
label='Kandang'
|
label='Kandang'
|
||||||
placeholder='Pilih Kandang...'
|
placeholder='Pilih Kandang...'
|
||||||
value={filterKandang}
|
value={filterFormik.values.kandang}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleFilterKandangChange(value);
|
handleFilterKandangChange(value);
|
||||||
setFilterErrors((prev) => ({ ...prev, kandang: '' }));
|
|
||||||
}}
|
}}
|
||||||
options={filterKandangOptions}
|
options={filterKandangOptions}
|
||||||
isDisabled={!filterProjectFlock}
|
isDisabled={!filterFormik.values.project_flock}
|
||||||
|
isError={
|
||||||
|
filterFormik.touched.kandang &&
|
||||||
|
Boolean(filterFormik.errors.kandang)
|
||||||
|
}
|
||||||
|
errorMessage={filterFormik.errors.kandang}
|
||||||
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
{filterErrors.kandang && (
|
|
||||||
<p className='text-red-500 text-sm mt-1'>
|
|
||||||
{filterErrors.kandang}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
export type UniformityTableFilterType = {
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
location: OptionType | null;
|
||||||
|
project_flock: OptionType | null;
|
||||||
|
project_flock_kandang_id: number | undefined;
|
||||||
|
kandang: OptionType | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UniformityTableFilterSchema = yup.object({
|
||||||
|
start_date: yup.string().required('Tanggal mulai wajib diisi'),
|
||||||
|
end_date: yup
|
||||||
|
.string()
|
||||||
|
.required('Tanggal akhir wajib diisi')
|
||||||
|
.test(
|
||||||
|
'is-greater-than-start',
|
||||||
|
'Tanggal akhir tidak boleh masa lampau',
|
||||||
|
function (value) {
|
||||||
|
const { start_date } = this.parent;
|
||||||
|
if (!start_date || !value) return true;
|
||||||
|
return new Date(value) >= new Date(start_date);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
location: yup
|
||||||
|
.mixed<OptionType>()
|
||||||
|
.required('Lokasi wajib dipilih')
|
||||||
|
.test('is-not-empty', 'Lokasi wajib dipilih', (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return !!value;
|
||||||
|
}),
|
||||||
|
project_flock: yup
|
||||||
|
.mixed<OptionType>()
|
||||||
|
.required('Project Flock wajib dipilih')
|
||||||
|
.test('is-not-empty', 'Project Flock wajib dipilih', (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return !!value;
|
||||||
|
}),
|
||||||
|
project_flock_kandang_id: yup.number().optional(),
|
||||||
|
kandang: yup
|
||||||
|
.mixed<OptionType>()
|
||||||
|
.required('Kandang wajib dipilih')
|
||||||
|
.test('is-not-empty', 'Kandang wajib dipilih', (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return !!value;
|
||||||
|
}),
|
||||||
|
}) as yup.ObjectSchema<UniformityTableFilterType>;
|
||||||
|
|
||||||
|
export type UniformityTableFilterValues = yup.InferType<
|
||||||
|
typeof UniformityTableFilterSchema
|
||||||
|
>;
|
||||||
@@ -49,7 +49,7 @@ function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
|
|||||||
<div className='flex flex-col gap-2 mt-2'>
|
<div className='flex flex-col gap-2 mt-2'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
|
<div className='w-5 h-5 bg-primary rounded-md'></div>
|
||||||
<span className='text-sm'>Ideal</span>
|
<span className='text-sm'>Ideal</span>
|
||||||
</div>
|
</div>
|
||||||
<span className='text-sm font-medium'>
|
<span className='text-sm font-medium'>
|
||||||
@@ -84,7 +84,7 @@ function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
|
|||||||
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
|
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
|
||||||
<div className='flex items-center gap-2 mt-2 justify-between'>
|
<div className='flex items-center gap-2 mt-2 justify-between'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
|
<div className='w-5 h-5 bg-primary rounded-md'></div>
|
||||||
<span className='text-sm'>Ideal</span>
|
<span className='text-sm'>Ideal</span>
|
||||||
</div>
|
</div>
|
||||||
<span className='text-sm font-medium'>{chartData.idealRange}</span>
|
<span className='text-sm font-medium'>{chartData.idealRange}</span>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
|
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import { formatNumber } from '@/lib/helper';
|
import { formatNumber } from '@/lib/helper';
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useFormik } from 'formik';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import moment from 'moment';
|
|
||||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
|
import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
|
||||||
@@ -28,6 +27,7 @@ import { LocationApi } from '@/services/api/master-data';
|
|||||||
import {
|
import {
|
||||||
ProjectFlockApi,
|
ProjectFlockApi,
|
||||||
ProjectFlockKandangApi,
|
ProjectFlockKandangApi,
|
||||||
|
RecordingApi,
|
||||||
} from '@/services/api/production';
|
} from '@/services/api/production';
|
||||||
import { UniformityApi } from '@/services/api/uniformity';
|
import { UniformityApi } from '@/services/api/uniformity';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
ProjectFlockKandangLookup,
|
ProjectFlockKandangLookup,
|
||||||
ProjectFlock,
|
ProjectFlock,
|
||||||
} from '@/types/api/production/project-flock';
|
} from '@/types/api/production/project-flock';
|
||||||
|
import { Recording } from '@/types/api/production/recording';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm';
|
import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm';
|
||||||
import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm';
|
import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm';
|
||||||
@@ -87,9 +88,7 @@ const UniformityForm = ({
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// ===== SELECT INPUT DATA =====
|
// ===== SELECT INPUT DATA =====
|
||||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
const [, setSelectedLocation] = useState<OptionType | null>(null);
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
|
const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
|
||||||
useState<string>('');
|
useState<string>('');
|
||||||
@@ -106,7 +105,6 @@ const UniformityForm = ({
|
|||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocations,
|
isLoadingOptions: isLoadingLocations,
|
||||||
loadMore: loadMoreLocations,
|
loadMore: loadMoreLocations,
|
||||||
hasMore: hasMoreLocations,
|
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -115,7 +113,6 @@ const UniformityForm = ({
|
|||||||
rawData: projectFlocksRawData,
|
rawData: projectFlocksRawData,
|
||||||
isLoadingOptions: isLoadingProjectFlocks,
|
isLoadingOptions: isLoadingProjectFlocks,
|
||||||
loadMore: loadMoreProjectFlocks,
|
loadMore: loadMoreProjectFlocks,
|
||||||
hasMore: hasMoreProjectFlocks,
|
|
||||||
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
|
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
|
||||||
location_id: selectedProjectFlockLocationId,
|
location_id: selectedProjectFlockLocationId,
|
||||||
});
|
});
|
||||||
@@ -204,6 +201,20 @@ const UniformityForm = ({
|
|||||||
? projectFlockKandangLookupData.data
|
? projectFlockKandangLookupData.data
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
// ===== RECORDINGS DATA (FOR WEEK CALCULATION) =====
|
||||||
|
const recordingsUrl = useMemo(() => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: '1',
|
||||||
|
limit: '100',
|
||||||
|
});
|
||||||
|
return `${RecordingApi.basePath}?${params.toString()}`;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { data: recordingsData } = useSWR(
|
||||||
|
recordingsUrl,
|
||||||
|
RecordingApi.getAllFetcher
|
||||||
|
);
|
||||||
|
|
||||||
// ===== FORM CONFIGURATION =====
|
// ===== FORM CONFIGURATION =====
|
||||||
const formikInitialValues = useMemo<UniformityFormValues>(
|
const formikInitialValues = useMemo<UniformityFormValues>(
|
||||||
() => getUniformityFormInitialValues(initialValues),
|
() => getUniformityFormInitialValues(initialValues),
|
||||||
@@ -387,14 +398,24 @@ const UniformityForm = ({
|
|||||||
|
|
||||||
// ===== SIDE EFFECTS =====
|
// ===== SIDE EFFECTS =====
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formik.values.date) {
|
if (
|
||||||
const date = moment(formik.values.date);
|
projectFlockKandangLookup?.project_flock_kandang_id &&
|
||||||
const weekNumber = date.week() - moment(date).startOf('month').week() + 1;
|
isResponseSuccess(recordingsData) &&
|
||||||
const adjustedWeekNumber = weekNumber <= 0 ? weekNumber + 52 : weekNumber;
|
recordingsData.data
|
||||||
|
) {
|
||||||
|
const matchingRecording = recordingsData.data.find(
|
||||||
|
(recording: Recording) =>
|
||||||
|
recording.project_flock?.project_flock_kandang_id ===
|
||||||
|
projectFlockKandangLookup.project_flock_kandang_id
|
||||||
|
);
|
||||||
|
|
||||||
formik.setFieldValue('week', adjustedWeekNumber);
|
if (matchingRecording?.project_flock?.production_standart?.week) {
|
||||||
|
const weekValue =
|
||||||
|
matchingRecording.project_flock.production_standart.week;
|
||||||
|
formik.setFieldValue('week', weekValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [formik.values.date]);
|
}, [projectFlockKandangLookup?.project_flock_kandang_id, recordingsData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsub = subscribeValidate(() => {
|
const unsub = subscribeValidate(() => {
|
||||||
@@ -598,7 +619,7 @@ const UniformityForm = ({
|
|||||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
className='rounded-2xl border border-sky-500 bg-[#0069E0] text-white'
|
className='rounded-2xl border border-sky-500 bg-primary text-white'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
document.getElementById('file-upload-input')?.click();
|
document.getElementById('file-upload-input')?.click();
|
||||||
@@ -622,7 +643,7 @@ const UniformityForm = ({
|
|||||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
className='rounded-2xl border border-sky-500 bg-[#0069E0] text-white'
|
className='rounded-2xl border border-sky-500 bg-primary text-white'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
document
|
document
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const EmptyState = () => {
|
|||||||
<>
|
<>
|
||||||
<div className='absolute inset-0 flex flex-col items-center justify-center z-10 gap-2'>
|
<div className='absolute inset-0 flex flex-col items-center justify-center z-10 gap-2'>
|
||||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center my-2'>
|
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center my-2'>
|
||||||
<Button className='rounded-2xl border border-sky-500 bg-[#0069E0] text-white'>
|
<Button className='rounded-2xl border border-sky-500 bg-primary text-white'>
|
||||||
<Icon icon={'heroicons:funnel'} className='text-4xl text-whitd' />
|
<Icon icon={'heroicons:funnel'} className='text-4xl text-whitd' />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ const UniformityGaugeChartSkeleton: React.FC<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col w-full items-center'>
|
<div className='flex flex-col w-full items-center'>
|
||||||
<div className='h-64 w-full relative flex justify-center min-h-[256px]'>
|
<div className='h-64 w-full relative flex justify-center min-h-64'>
|
||||||
<div className='relative w-full h-full flex flex-col items-center justify-end min-w-0'>
|
<div className='relative w-full h-full flex flex-col items-center justify-end min-w-0'>
|
||||||
<ResponsiveContainer width='100%' height={256}>
|
<ResponsiveContainer width='100%' height={256}>
|
||||||
<PieChart>
|
<PieChart>
|
||||||
@@ -57,7 +57,7 @@ const UniformityGaugeChartSkeleton: React.FC<
|
|||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<div className='absolute inset-x-0 top-24 flex flex-col items-center justify-center'>
|
<div className='absolute inset-x-0 top-24 flex flex-col items-center justify-center'>
|
||||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center mt-5'>
|
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center mt-5'>
|
||||||
<Button className='rounded-2xl border border-sky-500 bg-[#0069E0] text-white'>
|
<Button className='rounded-2xl border border-sky-500 bg-primary text-white'>
|
||||||
<Icon
|
<Icon
|
||||||
icon={'heroicons:funnel'}
|
icon={'heroicons:funnel'}
|
||||||
className='text-4xl text-whitd'
|
className='text-4xl text-whitd'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const UniformityTableSkeleton = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center justify-center gap-2 my-20'>
|
<div className='flex flex-col items-center justify-center gap-2 my-20'>
|
||||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
||||||
<Button className='rounded-2xl border border-sky-500 bg-[#0069E0] text-white'>
|
<Button className='rounded-2xl border border-sky-500 bg-primary text-white'>
|
||||||
<Icon
|
<Icon
|
||||||
icon={'heroicons-outline:chart-bar'}
|
icon={'heroicons-outline:chart-bar'}
|
||||||
className='text-4xl text-whitd'
|
className='text-4xl text-whitd'
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ const PurchaseRequestForm = ({
|
|||||||
options: supplierOptions,
|
options: supplierOptions,
|
||||||
isLoadingOptions: isLoadingSuppliers,
|
isLoadingOptions: isLoadingSuppliers,
|
||||||
rawData: supplierRawData,
|
rawData: supplierRawData,
|
||||||
|
loadMore: loadMoreSuppliers,
|
||||||
|
hasMore: hasMoreSuppliers,
|
||||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
|
||||||
category: 'SAPRONAK',
|
category: 'SAPRONAK',
|
||||||
});
|
});
|
||||||
@@ -528,6 +530,7 @@ const PurchaseRequestForm = ({
|
|||||||
onChange={handleSupplierChange}
|
onChange={handleSupplierChange}
|
||||||
options={supplierOptions}
|
options={supplierOptions}
|
||||||
onInputChange={setSupplierSelectInputValue}
|
onInputChange={setSupplierSelectInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreSuppliers}
|
||||||
isLoading={isLoadingSuppliers}
|
isLoading={isLoadingSuppliers}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.supplier_id &&
|
formik.touched.supplier_id &&
|
||||||
|
|||||||
@@ -320,110 +320,203 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
|
|||||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||||
<Text>Pengambilan</Text>
|
<Text>Pengambilan</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellHeader,
|
||||||
|
{ flex: 1.5, borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Sales</Text>
|
<Text>Sales</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Table Body */}
|
{/* Table Body */}
|
||||||
{customerReport.rows.map((item, index) => (
|
<>
|
||||||
<View
|
{/* Initial Balance Row */}
|
||||||
key={index}
|
<View style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}>
|
||||||
style={[
|
|
||||||
pdfStyles.tableRow,
|
|
||||||
index < customerReport.rows.length - 1
|
|
||||||
? pdfStyles.tableBorderBottom
|
|
||||||
: {},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||||
<Text>{index + 1}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||||
<Text>
|
<Text></Text>
|
||||||
{item.trans_date
|
|
||||||
? formatDate(item.trans_date, 'DD MMM YY')
|
|
||||||
: '-'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||||
<Text>
|
<Text></Text>
|
||||||
{item.delivery_date
|
|
||||||
? formatDate(item.delivery_date, 'DD MMM YY')
|
|
||||||
: '-'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
|
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
|
||||||
<Text>
|
<Text></Text>
|
||||||
{item.aging_day ? formatNumber(item.aging_day) : '-'} hari
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||||
<Text>{item.reference || '-'}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||||
<Text>
|
<Text></Text>
|
||||||
{Array.isArray(item.vehicle_numbers)
|
|
||||||
? item.vehicle_numbers.join(', ')
|
|
||||||
: item.vehicle_numbers || '-'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||||
<Text>{formatNumber(item.qty)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||||
<Text>{formatNumber(item.weight)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||||
<Text>{formatNumber(item.average_weight)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
<Text>{formatCurrency(item.unit_price)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
<Text>{formatCurrency(item.final_price)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
<Text>{formatCurrency(item.total_price)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
<Text>{formatCurrency(item.payment_amount)}</Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
<View
|
||||||
<Text style={pdfStyles.textError}>
|
style={[
|
||||||
{formatCurrency(item.accounts_receivable)}
|
pdfStyles.tableCellRight,
|
||||||
|
{
|
||||||
|
flex: 1.2,
|
||||||
|
color:
|
||||||
|
typeof customerReport.initial_balance === 'number' &&
|
||||||
|
customerReport.initial_balance < 0
|
||||||
|
? 'red'
|
||||||
|
: 'black',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
{formatCurrency(customerReport.initial_balance || 0)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||||
{item.status ? (
|
<Text></Text>
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
pdfStyles.badge,
|
|
||||||
item.status === 'LUNAS'
|
|
||||||
? pdfStyles.badgeLunas
|
|
||||||
: pdfStyles.badgeBelumLunas,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text>
|
|
||||||
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<Text>-</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||||
<Text>
|
<Text></Text>
|
||||||
{Array.isArray(item.pickup_info)
|
|
||||||
? item.pickup_info.join(', ')
|
|
||||||
: item.pickup_info || '-'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
<View
|
||||||
<Text>{item.sales_person || '-'}</Text>
|
style={[
|
||||||
|
pdfStyles.tableCell,
|
||||||
|
{ flex: 1.5, borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
|
||||||
|
{/* Data Rows */}
|
||||||
|
{customerReport.rows.map((item, index) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableRow,
|
||||||
|
index < customerReport.rows.length - 1
|
||||||
|
? pdfStyles.tableBorderBottom
|
||||||
|
: {},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||||
|
<Text>{index + 1}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||||
|
<Text>
|
||||||
|
{item.trans_date
|
||||||
|
? formatDate(item.trans_date, 'DD MMM YY')
|
||||||
|
: '-'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
|
||||||
|
<Text>
|
||||||
|
{item.delivery_date
|
||||||
|
? formatDate(item.delivery_date, 'DD MMM YY')
|
||||||
|
: '-'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
|
||||||
|
<Text>
|
||||||
|
{item.aging_day != null
|
||||||
|
? `${formatNumber(item.aging_day)} hari`
|
||||||
|
: '-'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||||
|
<Text>{item.reference || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||||
|
<Text>
|
||||||
|
{Array.isArray(item.vehicle_numbers)
|
||||||
|
? item.vehicle_numbers.length > 0
|
||||||
|
? item.vehicle_numbers.join(', ')
|
||||||
|
: '-'
|
||||||
|
: '-'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||||
|
<Text>{formatNumber(item.qty)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
|
||||||
|
<Text>{formatNumber(item.weight)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
|
||||||
|
<Text>{formatNumber(item.average_weight)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
|
<Text>{formatCurrency(item.unit_price)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
|
<Text>{formatCurrency(item.final_price)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
|
<Text>{formatCurrency(item.total_price)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
|
<Text>{formatCurrency(item.payment_amount)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
|
||||||
|
<Text style={pdfStyles.textError}>
|
||||||
|
{formatCurrency(item.accounts_receivable)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCellCenter, { flex: 1.5 }]}>
|
||||||
|
{item.status ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.badge,
|
||||||
|
item.status === 'LUNAS'
|
||||||
|
? pdfStyles.badgeLunas
|
||||||
|
: pdfStyles.badgeBelumLunas,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<Text>-</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||||
|
<Text>
|
||||||
|
{Array.isArray(item.pickup_info)
|
||||||
|
? item.pickup_info.length > 0
|
||||||
|
? item.pickup_info.join(', ')
|
||||||
|
: '-'
|
||||||
|
: '-'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCell,
|
||||||
|
{ flex: 1.5, borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>{item.sales_person || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
|
||||||
{/* Summary Row */}
|
{/* Summary Row */}
|
||||||
{customerReport.summary && (
|
{customerReport.summary && (
|
||||||
@@ -488,7 +581,12 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
|
|||||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[pdfStyles.tableCellLast, { flex: 1.5 }]}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCell,
|
||||||
|
{ flex: 1.5, borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -44,20 +44,50 @@ export const generateCustomerPaymentExcel = async (
|
|||||||
const worksheet = workbook.addWorksheet(customerName.substring(0, 31));
|
const worksheet = workbook.addWorksheet(customerName.substring(0, 31));
|
||||||
worksheet.columns = columns;
|
worksheet.columns = columns;
|
||||||
|
|
||||||
|
const initialRow = worksheet.addRow({
|
||||||
|
no: '',
|
||||||
|
transDate: '',
|
||||||
|
deliveryDate: '',
|
||||||
|
aging: '',
|
||||||
|
reference: '',
|
||||||
|
vehicleNumbers: '',
|
||||||
|
qty: '',
|
||||||
|
weight: '',
|
||||||
|
avgWeight: '',
|
||||||
|
unitPrice: '',
|
||||||
|
finalPrice: '',
|
||||||
|
totalPrice: '',
|
||||||
|
paymentAmount: '',
|
||||||
|
accountsReceivable: formatCurrency(customerReport.initial_balance || 0),
|
||||||
|
status: '',
|
||||||
|
pickupInfo: '',
|
||||||
|
salesPerson: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialBalanceCell = initialRow.getCell('accountsReceivable');
|
||||||
|
if (
|
||||||
|
typeof customerReport.initial_balance === 'number' &&
|
||||||
|
customerReport.initial_balance < 0
|
||||||
|
) {
|
||||||
|
initialBalanceCell.font = { color: { argb: 'FFFF0000' } };
|
||||||
|
}
|
||||||
|
|
||||||
customerData.forEach((item, index) => {
|
customerData.forEach((item, index) => {
|
||||||
const row = worksheet.addRow({
|
const row = worksheet.addRow({
|
||||||
no: index + 1,
|
no: index + 1,
|
||||||
transDate: item.trans_date
|
transDate: item.trans_date
|
||||||
? formatDate(item.trans_date, 'DD MMM YYYY')
|
? formatDate(item.trans_date, 'DD MMM YYYY')
|
||||||
: '',
|
: '-',
|
||||||
deliveryDate: item.delivery_date
|
deliveryDate: item.delivery_date
|
||||||
? formatDate(item.delivery_date, 'DD MMM YYYY')
|
? formatDate(item.delivery_date, 'DD MMM YYYY')
|
||||||
: '',
|
: '-',
|
||||||
aging: formatNumber(item.aging_day || 0),
|
aging: item.aging_day != null ? formatNumber(item.aging_day) : '-',
|
||||||
reference: item.reference || '',
|
reference: item.reference || '-',
|
||||||
vehicleNumbers: Array.isArray(item.vehicle_numbers)
|
vehicleNumbers: Array.isArray(item.vehicle_numbers)
|
||||||
? item.vehicle_numbers.join(', ')
|
? item.vehicle_numbers.length > 0
|
||||||
: '',
|
? item.vehicle_numbers.join(', ')
|
||||||
|
: '-'
|
||||||
|
: '-',
|
||||||
qty: formatNumber(item.qty || 0),
|
qty: formatNumber(item.qty || 0),
|
||||||
weight: formatNumber(item.weight || 0),
|
weight: formatNumber(item.weight || 0),
|
||||||
avgWeight: formatNumber(item.average_weight || 0),
|
avgWeight: formatNumber(item.average_weight || 0),
|
||||||
@@ -66,11 +96,13 @@ export const generateCustomerPaymentExcel = async (
|
|||||||
totalPrice: formatCurrency(item.total_price || 0),
|
totalPrice: formatCurrency(item.total_price || 0),
|
||||||
paymentAmount: formatCurrency(item.payment_amount || 0),
|
paymentAmount: formatCurrency(item.payment_amount || 0),
|
||||||
accountsReceivable: formatCurrency(item.accounts_receivable || 0),
|
accountsReceivable: formatCurrency(item.accounts_receivable || 0),
|
||||||
status: item.status || '',
|
status: item.status || '-',
|
||||||
pickupInfo: Array.isArray(item.pickup_info)
|
pickupInfo: Array.isArray(item.pickup_info)
|
||||||
? item.pickup_info.join(', ')
|
? item.pickup_info.length > 0
|
||||||
: '',
|
? item.pickup_info.join(', ')
|
||||||
salesPerson: item.sales_person || '',
|
: '-'
|
||||||
|
: '-',
|
||||||
|
salesPerson: item.sales_person || '-',
|
||||||
});
|
});
|
||||||
|
|
||||||
const accountsReceivableCell = row.getCell('accountsReceivable');
|
const accountsReceivableCell = row.getCell('accountsReceivable');
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import SelectInput, {
|
import SelectInput, { useSelect } from '@/components/input/SelectInput';
|
||||||
useSelect,
|
|
||||||
OptionType,
|
|
||||||
} from '@/components/input/SelectInput';
|
|
||||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
import { CustomerApi } from '@/services/api/master-data';
|
import { CustomerApi } from '@/services/api/master-data';
|
||||||
@@ -81,14 +78,14 @@ const CustomerPaymentTab = () => {
|
|||||||
const normalizedValue = notes.toLowerCase();
|
const normalizedValue = notes.toLowerCase();
|
||||||
|
|
||||||
if (normalizedValue === 'lunas') {
|
if (normalizedValue === 'lunas') {
|
||||||
return 'bg-info/10 text-info border-info';
|
return 'bg-info/10 text-black border-info';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedValue.includes('belum')) {
|
if (normalizedValue.includes('belum')) {
|
||||||
return 'bg-warning/10 text-warning border-warning';
|
return 'bg-warning/10 text-black border-warning';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'bg-gray-100 text-gray-600 border-gray-300';
|
return 'bg-gray-100 text-black border-gray-300';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPaymentStatusIndicatorColor = (notes: string) => {
|
const getPaymentStatusIndicatorColor = (notes: string) => {
|
||||||
@@ -364,7 +361,11 @@ const CustomerPaymentTab = () => {
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.row.original.vehicle_numbers;
|
const value = props.row.original.vehicle_numbers;
|
||||||
return Array.isArray(value) ? value.join(', ') : value || '-';
|
return Array.isArray(value)
|
||||||
|
? value.length > 0
|
||||||
|
? value.join(', ')
|
||||||
|
: '-'
|
||||||
|
: '-';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -510,13 +511,14 @@ const CustomerPaymentTab = () => {
|
|||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
statusIndicator={true}
|
statusIndicator={true}
|
||||||
|
size='sm'
|
||||||
variant='soft'
|
variant='soft'
|
||||||
className={{
|
className={{
|
||||||
badge: `rounded-xl justify-start border border-gray-200 ${getPaymentStatusColor(value)}`,
|
badge: `py-2.5 px-2 font-thin text-xs border border-gray-200 rounded-xl justify-start ${getPaymentStatusColor(value)}`,
|
||||||
status: getPaymentStatusIndicatorColor(value),
|
status: getPaymentStatusIndicatorColor(value),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className='capitalize'>{getPaymentStatusText(value)}</span>
|
{getPaymentStatusText(value)}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -528,7 +530,11 @@ const CustomerPaymentTab = () => {
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.row.original.pickup_info;
|
const value = props.row.original.pickup_info;
|
||||||
return Array.isArray(value) ? value.join(', ') : value || '-';
|
return Array.isArray(value)
|
||||||
|
? value.length > 0
|
||||||
|
? value.join(', ')
|
||||||
|
: '-'
|
||||||
|
: '-';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -754,9 +760,9 @@ const CustomerPaymentTab = () => {
|
|||||||
wrapper: 'w-full rounded-2xl',
|
wrapper: 'w-full rounded-2xl',
|
||||||
body: 'p-0',
|
body: 'p-0',
|
||||||
title:
|
title:
|
||||||
'py-1.5 px-3 bg-[#0069E0] text-white text-lg font-normal',
|
'py-1.5 px-3 bg-primary text-white text-lg font-normal',
|
||||||
subtitle:
|
subtitle:
|
||||||
'px-3 pb-1 bg-[#0069E0] text-white text-sm font-normal',
|
'px-3 pb-1 bg-primary text-white text-sm font-normal',
|
||||||
}}
|
}}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
@@ -772,7 +778,7 @@ const CustomerPaymentTab = () => {
|
|||||||
pageSize={customerReport.rows.length + 1}
|
pageSize={customerReport.rows.length + 1}
|
||||||
renderFooter={customerReport.rows.length > 0}
|
renderFooter={customerReport.rows.length > 0}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: 'w-full',
|
containerClassName: 'w-full mb-0!',
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
tableClassName: 'w-full table-auto text-sm',
|
tableClassName: 'w-full table-auto text-sm',
|
||||||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export const formatNumber = (
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const safeRound = (num: number, decimals: number) => {
|
||||||
|
const factor = 10 ** decimals;
|
||||||
|
return Math.round((num + Number.EPSILON) * factor) / factor;
|
||||||
|
};
|
||||||
|
|
||||||
export const formatTitleCase = (value: string) => {
|
export const formatTitleCase = (value: string) => {
|
||||||
return value
|
return value
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { formatDate } from '@/lib/helper';
|
import { formatDate, safeRound } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
@@ -32,21 +32,21 @@ export class ProductionResultReportApiService extends BaseApiService<
|
|||||||
const mappedProductionResults: {
|
const mappedProductionResults: {
|
||||||
projectFlockKandang: BaseProjectFlockKandang;
|
projectFlockKandang: BaseProjectFlockKandang;
|
||||||
productionResult: ProductionResult[] | null;
|
productionResult: ProductionResult[] | null;
|
||||||
}[] = [];
|
}[] = await Promise.all(
|
||||||
|
(projectFlockKandangs || []).map(async (projectFlockKandang) => {
|
||||||
|
const getProductionResultPath = `${this.basePath}/${projectFlockKandang.id}?page=1&limit=99999999`;
|
||||||
|
const getProductionResultRes = await httpClient<
|
||||||
|
BaseApiResponse<ProductionResult[]>
|
||||||
|
>(getProductionResultPath);
|
||||||
|
|
||||||
projectFlockKandangs?.forEach(async (projectFlockKandang) => {
|
return {
|
||||||
const getProductionResultPath = `${this.basePath}/${projectFlockKandang.id}?page=1&limit=99999999`;
|
projectFlockKandang,
|
||||||
const getProductionResultRes = await httpClient<
|
productionResult: isResponseSuccess(getProductionResultRes)
|
||||||
BaseApiResponse<ProductionResult[]>
|
? getProductionResultRes.data
|
||||||
>(getProductionResultPath);
|
: null,
|
||||||
|
};
|
||||||
mappedProductionResults.push({
|
})
|
||||||
projectFlockKandang,
|
);
|
||||||
productionResult: isResponseSuccess(getProductionResultRes)
|
|
||||||
? getProductionResultRes.data
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const rows = mappedProductionResults;
|
const rows = mappedProductionResults;
|
||||||
if (!rows || rows.length === 0) {
|
if (!rows || rows.length === 0) {
|
||||||
@@ -68,44 +68,46 @@ export class ProductionResultReportApiService extends BaseApiService<
|
|||||||
|
|
||||||
row.productionResult?.forEach((productionResult) => {
|
row.productionResult?.forEach((productionResult) => {
|
||||||
groupedData[kandangName].push({
|
groupedData[kandangName].push({
|
||||||
woa: productionResult.woa,
|
woa: safeRound(productionResult.woa, 2),
|
||||||
bw: productionResult.bw,
|
bw: safeRound(productionResult.bw, 2),
|
||||||
std_bw: productionResult.std_bw,
|
std_bw: safeRound(productionResult.std_bw, 2),
|
||||||
uniformity: productionResult.uniformity,
|
uniformity: safeRound(productionResult.uniformity, 2),
|
||||||
std_uniformity: productionResult.std_uniformity,
|
std_uniformity: productionResult.std_uniformity,
|
||||||
dep_kum: productionResult.dep_kum,
|
dep_kum: safeRound(productionResult.dep_kum, 2),
|
||||||
dep_std: productionResult.dep_std,
|
dep_std: safeRound(productionResult.dep_std, 2),
|
||||||
butiran_utuh: productionResult.butiran_utuh,
|
butiran_utuh: safeRound(productionResult.butiran_utuh, 2),
|
||||||
butiran_putih: productionResult.butiran_putih,
|
butiran_putih: safeRound(productionResult.butiran_putih, 2),
|
||||||
butiran_retak: productionResult.butiran_retak,
|
butiran_retak: safeRound(productionResult.butiran_retak, 2),
|
||||||
butiran_pecah: productionResult.butiran_pecah,
|
butiran_pecah: safeRound(productionResult.butiran_pecah, 2),
|
||||||
butiran_jumlah: productionResult.butiran_jumlah,
|
butiran_jumlah: safeRound(productionResult.butiran_jumlah, 2),
|
||||||
total_butir: productionResult.total_butir,
|
total_butir: safeRound(productionResult.total_butir, 2),
|
||||||
kg_utuh: productionResult.kg_utuh,
|
kg_utuh: safeRound(productionResult.kg_utuh, 2),
|
||||||
kg_putih: productionResult.kg_putih,
|
kg_putih: safeRound(productionResult.kg_putih, 2),
|
||||||
kg_retak: productionResult.kg_retak,
|
kg_retak: safeRound(productionResult.kg_retak, 2),
|
||||||
kg_pecah: productionResult.kg_pecah,
|
kg_pecah: safeRound(productionResult.kg_pecah, 2),
|
||||||
kg_jumlah: productionResult.kg_jumlah,
|
kg_jumlah: safeRound(productionResult.kg_jumlah, 2),
|
||||||
total_kg: productionResult.total_kg,
|
total_kg: safeRound(productionResult.total_kg, 2),
|
||||||
persen_utuh: productionResult.persen_utuh,
|
persen_utuh: safeRound(productionResult.persen_utuh, 2),
|
||||||
persen_putih: productionResult.persen_putih,
|
persen_putih: safeRound(productionResult.persen_putih, 2),
|
||||||
persen_retak: productionResult.persen_retak,
|
persen_retak: safeRound(productionResult.persen_retak, 2),
|
||||||
persen_pecah: productionResult.persen_pecah,
|
persen_pecah: safeRound(productionResult.persen_pecah, 2),
|
||||||
hd: productionResult.hd,
|
hd: safeRound(productionResult.hd, 2),
|
||||||
hd_std: productionResult.hd_std,
|
hd_std: safeRound(productionResult.hd_std, 2),
|
||||||
fi: productionResult.fi,
|
fi: safeRound(productionResult.fi, 2),
|
||||||
fi_std: productionResult.fi_std,
|
fi_std: safeRound(productionResult.fi_std, 2),
|
||||||
em: productionResult.em,
|
em: safeRound(productionResult.em, 2),
|
||||||
em_std: productionResult.em_std,
|
em_std: safeRound(productionResult.em_std, 2),
|
||||||
ew: productionResult.ew,
|
ew: safeRound(productionResult.ew, 2),
|
||||||
ew_std: productionResult.ew_std,
|
ew_std: safeRound(productionResult.ew_std, 2),
|
||||||
fcr: productionResult.fcr,
|
fcr: safeRound(productionResult.fcr, 2),
|
||||||
fcr_std: productionResult.fcr_std,
|
fcr_std: safeRound(productionResult.fcr_std, 2),
|
||||||
hh: productionResult.hh,
|
hh: safeRound(productionResult.hh, 2),
|
||||||
hh_std: productionResult.hh_std,
|
hh_std: safeRound(productionResult.hh_std, 2),
|
||||||
project_flock_name: productionResult.project_flock.name,
|
project_flock_name:
|
||||||
project_flock_category: productionResult.project_flock.category,
|
row.projectFlockKandang.project_flock.flock_name,
|
||||||
kandang_name: productionResult.project_flock.kandang.name,
|
project_flock_category:
|
||||||
|
row.projectFlockKandang.project_flock.category,
|
||||||
|
kandang_name: row.projectFlockKandang.kandang.name,
|
||||||
created_at: formatDate(productionResult.created_at, 'YYYY-MM-DD'),
|
created_at: formatDate(productionResult.created_at, 'YYYY-MM-DD'),
|
||||||
updated_at: formatDate(productionResult.updated_at, 'YYYY-MM-DD'),
|
updated_at: formatDate(productionResult.updated_at, 'YYYY-MM-DD'),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user