Merge branch 'development' into 'production'

Development

See merge request mbugroup/lti-web-client!456
This commit is contained in:
Adnan Zahir
2026-05-05 14:12:28 +07:00
12 changed files with 188 additions and 191 deletions
@@ -11,7 +11,6 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Dropdown from '@/components/Dropdown';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data'; import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data';
@@ -44,6 +43,7 @@ import {
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio'; import SelectInputRadio from '@/components/input/SelectInputRadio';
import ButtonFilter from '@/components/helper/ButtonFilter'; import ButtonFilter from '@/components/helper/ButtonFilter';
import NumberInput from '@/components/input/NumberInput';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
props, props,
@@ -211,8 +211,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
const { const {
isChickinApproveModalOpen, isChickinApproveModalOpen,
isChickinApproveLoading, isChickinApproveLoading,
@@ -327,14 +326,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
[] []
); );
const periodOptions = useMemo(
() => [
{ value: '1', label: 'Periode 1' },
{ value: '2', label: 'Periode 2' },
],
[]
);
// ===== FILTER HELPERS ===== // ===== FILTER HELPERS =====
const areaValue = useMemo(() => { const areaValue = useMemo(() => {
if (!formik.values.area_id) return null; if (!formik.values.area_id) return null;
@@ -393,13 +384,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
}, [formik.values.category, categoryOptions]); }, [formik.values.category, categoryOptions]);
const periodValue = useMemo(() => {
if (!formik.values.period) return null;
return (
periodOptions.find((opt) => opt.value === formik.values.period) || null
);
}, [formik.values.period, periodOptions]);
// ===== FILTER DEPENDENCY HANDLERS ===== // ===== FILTER DEPENDENCY HANDLERS =====
const handleFilterAreaChange = (area: OptionType | null) => { const handleFilterAreaChange = (area: OptionType | null) => {
const areaId = area?.value ? String(area.value) : undefined; const areaId = area?.value ? String(area.value) : undefined;
@@ -813,14 +797,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
[] []
); );
const exportToExcelHandler = async () => {
setIsLoadingExportingToExcel(true);
toast.error('Not implemented yet!');
setIsLoadingExportingToExcel(false);
};
const bulkApproveClickHandler = () => { const bulkApproveClickHandler = () => {
setApprovalAction('APPROVED'); setApprovalAction('APPROVED');
confirmModal.openModal(); confirmModal.openModal();
@@ -1020,51 +996,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className='px-3 py-2.5' className='px-3 py-2.5'
/> />
<Dropdown
align='end'
direction='bottom'
className={{
content:
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
}}
trigger={
<Button
variant='outline'
color='none'
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
>
<div className='flex flex-row items-center gap-1.5'>
<Icon
icon='heroicons:cloud-arrow-down'
width={20}
height={20}
/>
<span>Export</span>
<div className='w-px self-stretch bg-base-content/10' />
<Icon
icon='heroicons:chevron-down'
width={14}
height={14}
/>
</div>
</Button>
}
>
<Button
variant='ghost'
color='none'
onClick={exportToExcelHandler}
isLoading={isLoadingExportingToExcel}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Export to Excel
</Button>
</Dropdown>
</div> </div>
</div> </div>
@@ -1393,18 +1324,14 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
isClearable={true} isClearable={true}
/> />
<SelectInputRadio <NumberInput
label='Periode' label='Periode'
placeholder='Pilih Periode' name='period'
options={periodOptions} placeholder='Masukkan Periode'
value={periodValue} value={formik.values.period ?? ''}
onChange={(val) => { onChange={formik.handleChange}
if (!Array.isArray(val)) { onBlur={formik.handleBlur}
formik.setFieldValue('period', val?.value || null);
}
}}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
isClearable
/> />
</div> </div>
@@ -5,6 +5,7 @@ import {
CreateLayingRecordingPayload, CreateLayingRecordingPayload,
CreateEggPayload, CreateEggPayload,
} from '@/types/api/production/recording'; } from '@/types/api/production/recording';
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
type RecordingGrowingFormSchemaType = { type RecordingGrowingFormSchemaType = {
record_date: string; record_date: string;
@@ -29,11 +30,19 @@ type RecordingGrowingFormSchemaType = {
} | null; } | null;
project_flock_kandang_id: number; project_flock_kandang_id: number;
stocks: { stocks: {
product_warehouse_id: number; product_warehouse_id:
| {
value: number;
label: string;
}
| undefined;
qty: number | string; qty: number | string;
}[]; }[];
depletions: { depletions: {
product_warehouse_id?: number; product_warehouse_id?: {
value: number;
label: string;
};
source_product_warehouse_id?: number; source_product_warehouse_id?: number;
qty?: number | string; qty?: number | string;
}[]; }[];
@@ -41,34 +50,48 @@ type RecordingGrowingFormSchemaType = {
type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & { type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
eggs: { eggs: {
product_warehouse_id?: number; product_warehouse_id?: {
value: number;
label: string;
};
qty?: number | string; qty?: number | string;
weight?: number | string; weight?: number | string;
}[]; }[];
}; };
export type StockSchema = { export type StockSchema = {
product_warehouse_id: number; product_warehouse_id: {
value: number;
label: string;
};
qty: number | string; qty: number | string;
}; };
export type DepletionSchema = { export type DepletionSchema = {
product_warehouse_id?: number; product_warehouse_id?: {
value: number;
label: string;
};
source_product_warehouse_id?: number; source_product_warehouse_id?: number;
qty?: number | string; qty?: number | string;
}; };
export type EggSchema = { export type EggSchema = {
product_warehouse_id?: number; product_warehouse_id?: {
value: number;
label: string;
};
qty?: number | string; qty?: number | string;
weight?: number | string; weight?: number | string;
}; };
const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
})
.required('Produk wajib diisi!') .required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!') .typeError('Produk wajib diisi!'),
.typeError('Produk harus berupa angka!'),
qty: Yup.number() qty: Yup.number()
.required('Jumlah penggunaan wajib diisi!') .required('Jumlah penggunaan wajib diisi!')
.moreThan(0, 'Jumlah penggunaan harus lebih dari 0!') .moreThan(0, 'Jumlah penggunaan harus lebih dari 0!')
@@ -76,7 +99,10 @@ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
}); });
const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({ const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
})
.optional() .optional()
.typeError('Depletions harus berupa angka!'), .typeError('Depletions harus berupa angka!'),
source_product_warehouse_id: Yup.number() source_product_warehouse_id: Yup.number()
@@ -88,7 +114,10 @@ const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
}); });
const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({ const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
})
.optional() .optional()
.typeError('Kondisi telur harus berupa angka!'), .typeError('Kondisi telur harus berupa angka!'),
qty: Yup.number().optional().typeError('Jumlah telur harus berupa angka!'), qty: Yup.number().optional().typeError('Jumlah telur harus berupa angka!'),
@@ -248,14 +277,17 @@ export const getRecordingGrowingFormInitialValues = (
initialValues?.project_flock?.project_flock_kandang_id ?? initialValues?.project_flock?.project_flock_kandang_id ??
0, 0,
stocks: initialValues?.stocks?.map((stock) => ({ stocks: initialValues?.stocks?.map((stock) => ({
product_warehouse_id: stock.product_warehouse_id, product_warehouse_id: {
value: stock.product_warehouse_id,
label: getProductWarehouseOptionLabel(stock.product_warehouse),
},
qty: qty:
(stock as { qty?: number; usage_amount?: number }).qty || (stock as { qty?: number; usage_amount?: number }).qty ||
(stock as { qty?: number; usage_amount?: number }).usage_amount || (stock as { qty?: number; usage_amount?: number }).usage_amount ||
'', '',
})) ?? [ })) ?? [
{ {
product_warehouse_id: 0, product_warehouse_id: undefined,
qty: '', qty: '',
}, },
], ],
@@ -263,13 +295,16 @@ export const getRecordingGrowingFormInitialValues = (
( (
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0] depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
) => ({ ) => ({
product_warehouse_id: depletion.product_warehouse_id, product_warehouse_id: {
value: Number(depletion.product_warehouse_id ?? 0),
label: getProductWarehouseOptionLabel(depletion.product_warehouse),
},
source_product_warehouse_id: depletion.source_product_warehouse_id, source_product_warehouse_id: depletion.source_product_warehouse_id,
qty: depletion.qty, qty: depletion.qty,
}) })
) ?? [ ) ?? [
{ {
product_warehouse_id: 0, product_warehouse_id: undefined,
qty: '', qty: '',
}, },
], ],
@@ -281,12 +316,15 @@ export const getRecordingLayingFormInitialValues = (
...getRecordingGrowingFormInitialValues(initialValues), ...getRecordingGrowingFormInitialValues(initialValues),
eggs: initialValues?.eggs?.map((egg: CreateEggPayload) => ({ eggs: initialValues?.eggs?.map((egg: CreateEggPayload) => ({
product_warehouse_id: egg.product_warehouse_id, product_warehouse_id: {
value: Number(egg.product_warehouse_id ?? 0),
label: getProductWarehouseOptionLabel(egg.product_warehouse),
},
qty: egg.qty, qty: egg.qty,
weight: egg.weight, weight: egg.weight,
})) ?? [ })) ?? [
{ {
product_warehouse_id: 0, product_warehouse_id: undefined,
qty: '', qty: '',
weight: '', weight: '',
}, },
@@ -522,7 +522,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
? values.depletions ? values.depletions
?.filter((d) => d.product_warehouse_id && d.qty) ?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({ .map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!, product_warehouse_id: depletion.product_warehouse_id?.value ?? 0,
...(depletion.source_product_warehouse_id && { ...(depletion.source_product_warehouse_id && {
source_product_warehouse_id: source_product_warehouse_id:
depletion.source_product_warehouse_id, depletion.source_product_warehouse_id,
@@ -533,13 +533,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const stocks = recordingRestriction.canEditStock const stocks = recordingRestriction.canEditStock
? (values.stocks ?? []) ? (values.stocks ?? [])
.filter((s) => s.product_warehouse_id && s.qty) .filter((s) => s.product_warehouse_id?.value && s.qty)
.map((stock) => ({ .map((stock) => ({
// In migration mode, product_warehouse_id field holds product.id; // In migration mode, product_warehouse_id field holds product.id;
// send it as product_id so the backend auto-creates the warehouse entry. // send it as product_id so the backend auto-creates the warehouse entry.
...(isMigrationMode ...(isMigrationMode
? { product_id: stock.product_warehouse_id } ? { product_id: stock.product_warehouse_id?.value }
: { product_warehouse_id: stock.product_warehouse_id }), : { product_warehouse_id: stock.product_warehouse_id?.value }),
qty: Number(stock.qty) || 0, qty: Number(stock.qty) || 0,
})) }))
: []; : [];
@@ -561,9 +561,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const createLayingPayload = useCallback( const createLayingPayload = useCallback(
(values: RecordingLayingFormValues) => { (values: RecordingLayingFormValues) => {
const depletions = values.depletions const depletions = values.depletions
?.filter((d) => d.product_warehouse_id && d.qty) ?.filter((d) => d.product_warehouse_id?.value && d.qty)
.map((depletion) => ({ .map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!, product_warehouse_id: depletion.product_warehouse_id?.value ?? 0,
...(depletion.source_product_warehouse_id && { ...(depletion.source_product_warehouse_id && {
source_product_warehouse_id: depletion.source_product_warehouse_id, source_product_warehouse_id: depletion.source_product_warehouse_id,
}), }),
@@ -573,7 +573,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const eggs = values.eggs const eggs = values.eggs
?.filter((e) => e.product_warehouse_id && e.qty && e.weight) ?.filter((e) => e.product_warehouse_id && e.qty && e.weight)
.map((egg) => ({ .map((egg) => ({
product_warehouse_id: egg.product_warehouse_id!, product_warehouse_id: egg.product_warehouse_id?.value ?? 0,
qty: Number(egg.qty) || 0, qty: Number(egg.qty) || 0,
weight: weight:
typeof egg.weight === 'number' typeof egg.weight === 'number'
@@ -583,11 +583,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const stocks = recordingRestriction.canEditStock const stocks = recordingRestriction.canEditStock
? values.stocks ? values.stocks
.filter((s) => s.product_warehouse_id && s.qty) .filter((s) => s.product_warehouse_id?.value && s.qty)
.map((stock) => ({ .map((stock) => ({
...(isMigrationMode ...(isMigrationMode
? { product_id: stock.product_warehouse_id } ? { product_id: stock.product_warehouse_id?.value }
: { product_warehouse_id: stock.product_warehouse_id }), : { product_warehouse_id: stock.product_warehouse_id?.value }),
qty: Number(stock.qty) || 0, qty: Number(stock.qty) || 0,
})) }))
: []; : [];
@@ -636,21 +636,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
rawData: stockProductsPW, rawData: stockProductsPW,
isLoadingOptions: isLoadingStockProductsPW, isLoadingOptions: isLoadingStockProductsPW,
loadMore: loadMoreStockProductsPW, loadMore: loadMoreStockProductsPW,
} = useSelect( } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
isMigrationMode ? null : ProductWarehouseApi.basePath, flags: 'PAKAN,OVK',
'id', limit: '100',
'product.name', available_only: 'false',
'search', location_id: stockProductsLocationId,
{ ...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}),
flags: 'PAKAN,OVK', });
limit: '100',
available_only: 'false',
location_id: stockProductsLocationId,
...(selectedKandangId
? { kandang_id: selectedKandangId.toString() }
: {}),
}
);
const { const {
setInputValue: setStockMasterInputValue, setInputValue: setStockMasterInputValue,
@@ -1283,8 +1275,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// product_warehouse object returned by the API. // product_warehouse object returned by the API.
if (isMigrationMode && type === 'edit' && initialValues?.stocks?.length) { if (isMigrationMode && type === 'edit' && initialValues?.stocks?.length) {
baseValues.stocks = initialValues.stocks.map((stock) => ({ baseValues.stocks = initialValues.stocks.map((stock) => ({
product_warehouse_id: product_warehouse_id: {
stock.product_warehouse?.product_id ?? stock.product_warehouse_id, value: Number(
stock.product_warehouse?.product_id ?? stock.product_warehouse_id
),
label: getProductWarehouseOptionLabel(stock.product_warehouse),
},
qty: stock.usage_amount ?? '', qty: stock.usage_amount ?? '',
})); }));
} }
@@ -1438,8 +1434,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
formik.setFieldValue( formik.setFieldValue(
'stocks', 'stocks',
initialValues.stocks.map((stock) => ({ initialValues.stocks.map((stock) => ({
product_warehouse_id: product_warehouse_id: {
stock.product_warehouse?.product_id ?? stock.product_warehouse_id, value: Number(
stock.product_warehouse?.product_id ?? stock.product_warehouse_id
),
label: getProductWarehouseOptionLabel(stock.product_warehouse),
},
qty: stock.usage_amount ?? '', qty: stock.usage_amount ?? '',
})) }))
); );
@@ -1462,7 +1462,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
(stockIdx: number) => { (stockIdx: number) => {
if ((type as 'add' | 'edit' | 'detail') === 'detail') return null; if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
const stock = formik.values.stocks?.[stockIdx]; const stock = formik.values.stocks?.[stockIdx];
if (!stock || !stock.product_warehouse_id) return null; if (!stock || !stock.product_warehouse_id?.value) return null;
return null; return null;
}, },
[formik.values.stocks, type] [formik.values.stocks, type]
@@ -1492,13 +1492,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const getStockUsageAdornment = useCallback( const getStockUsageAdornment = useCallback(
(stockIdx: number) => { (stockIdx: number) => {
const stock = formik.values.stocks?.[stockIdx]; const stock = formik.values.stocks?.[stockIdx];
if (!stock || !stock.product_warehouse_id) return null; if (!stock || !stock.product_warehouse_id?.value) return null;
const isDetail = (type as 'add' | 'edit' | 'detail') === 'detail'; const isDetail = (type as 'add' | 'edit' | 'detail') === 'detail';
const availableStock = getAvailableStock(stock.product_warehouse_id); const availableStock = getAvailableStock(
stock.product_warehouse_id.value
);
const requestedUsage = Number(stock.qty) || 0; const requestedUsage = Number(stock.qty) || 0;
const remainingStock = availableStock - requestedUsage; const remainingStock = availableStock - requestedUsage;
const { pendingQty } = getStockPendingInfo(stock.product_warehouse_id); const { pendingQty } = getStockPendingInfo(
stock.product_warehouse_id.value
);
if (isDetail) { if (isDetail) {
if (pendingQty > 0) { if (pendingQty > 0) {
@@ -1605,10 +1609,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return ( return (
idx !== currentIdx && idx !== currentIdx &&
s.product_warehouse_id && s.product_warehouse_id &&
s.product_warehouse_id !== 0 s.product_warehouse_id.value !== 0
); );
}) })
.map((s) => s.product_warehouse_id) || []; .map((s) => s.product_warehouse_id?.value) || [];
return unifiedStockProducts.filter( return unifiedStockProducts.filter(
(opt) => !selectedProductIds.includes(Number(opt.value)) (opt) => !selectedProductIds.includes(Number(opt.value))
@@ -1625,10 +1629,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return ( return (
idx !== currentIdx && idx !== currentIdx &&
d.product_warehouse_id && d.product_warehouse_id &&
d.product_warehouse_id !== 0 d.product_warehouse_id.value !== 0
); );
}) })
.map((d) => d.product_warehouse_id) || []; .map((d) => d.product_warehouse_id?.value) || [];
return depletionProducts.filter( return depletionProducts.filter(
(opt) => !selectedProductIds.includes(Number(opt.value)) (opt) => !selectedProductIds.includes(Number(opt.value))
@@ -1645,10 +1649,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return ( return (
idx !== currentIdx && idx !== currentIdx &&
e.product_warehouse_id && e.product_warehouse_id &&
e.product_warehouse_id !== 0 e.product_warehouse_id.value !== 0
); );
}) })
.map((e) => e.product_warehouse_id) || []; .map((e) => e.product_warehouse_id?.value) || [];
return eggProducts.filter( return eggProducts.filter(
(opt) => !selectedProductIds.includes(Number(opt.value)) (opt) => !selectedProductIds.includes(Number(opt.value))
@@ -1694,7 +1698,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
isError: touchedField && Boolean(errorField?.[column]), isError: touchedField && Boolean(errorField?.[column]),
errorMessage: errorMessage:
touchedField && errorField?.[column] touchedField && errorField?.[column]
? (errorField[column] as string) ? errorField[column] instanceof Object
? (errorField[column] as OptionType)?.label
: (errorField[column] as string)
: '', : '',
}; };
}; };
@@ -2901,20 +2907,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<td> <td>
<SelectInput <SelectInput
required required
key={`stock-product-${idx}-${stock.product_warehouse_id}`} key={`stock-product-${idx}-${stock.product_warehouse_id?.value}`}
value={ value={stock.product_warehouse_id}
unifiedStockProducts.find(
(product) =>
product.value === stock.product_warehouse_id
) || null
}
onInputChange={setStockInputValue} onInputChange={setStockInputValue}
onChange={(selectedOption) => { onChange={(selectedOption) => {
const option = const option =
selectedOption as OptionType | null; selectedOption as OptionType | null;
formik.setFieldValue( formik.setFieldValue(
`stocks.${idx}.product_warehouse_id`, `stocks.${idx}.product_warehouse_id`,
option?.value || 0 option
); );
}} }}
options={getAvailableStockProductOptions(idx)} options={getAvailableStockProductOptions(idx)}
@@ -2950,9 +2951,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
isClearable={type !== 'detail'} isClearable={type !== 'detail'}
inputPrefix={ inputPrefix={
stock.product_warehouse_id stock.product_warehouse_id?.value
? getProductFlagBadgeAdornment( ? getProductFlagBadgeAdornment(
stock.product_warehouse_id stock.product_warehouse_id.value
) )
: undefined : undefined
} }
@@ -2988,7 +2989,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
inputSuffix={ inputSuffix={
stock.product_warehouse_id stock.product_warehouse_id
? getProductUomSuffix( ? getProductUomSuffix(
stock.product_warehouse_id, stock.product_warehouse_id.value,
'stock' 'stock'
) )
: null : null
@@ -3181,19 +3182,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} )}
<td> <td>
<SelectInput <SelectInput
value={ value={depletion.product_warehouse_id}
depletionProducts.find(
(product) =>
product.value ===
depletion.product_warehouse_id
) || null
}
onChange={(selectedOption) => { onChange={(selectedOption) => {
const option = const option =
selectedOption as OptionType | null; selectedOption as OptionType | null;
formik.setFieldValue( formik.setFieldValue(
`depletions.${idx}.product_warehouse_id`, `depletions.${idx}.product_warehouse_id`,
option?.value || 0 option
); );
}} }}
options={getAvailableDepletionProductOptions(idx)} options={getAvailableDepletionProductOptions(idx)}
@@ -3256,7 +3251,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
inputSuffix={ inputSuffix={
depletion.product_warehouse_id depletion.product_warehouse_id
? getProductUomSuffix( ? getProductUomSuffix(
depletion.product_warehouse_id, depletion.product_warehouse_id.value,
'depletion' 'depletion'
) )
: null : null
@@ -3434,18 +3429,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} )}
<td> <td>
<SelectInput <SelectInput
value={ value={egg.product_warehouse_id}
eggProducts.find(
(product) =>
product.value === egg.product_warehouse_id
) || null
}
onChange={(selectedOption) => { onChange={(selectedOption) => {
const option = const option =
selectedOption as OptionType | null; selectedOption as OptionType | null;
formik.setFieldValue( formik.setFieldValue(
`eggs.${idx}.product_warehouse_id`, `eggs.${idx}.product_warehouse_id`,
option?.value || 0 option
); );
}} }}
options={getAvailableEggProductOptions(idx)} options={getAvailableEggProductOptions(idx)}
@@ -223,6 +223,8 @@ const TransferToLayingFormModal = () => {
}, },
}); });
const { flockSource: formikFlockSource } = formik.values;
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
const [selectedFlockSourceRawData, setSelectedFlockSourceRawData] = useState< const [selectedFlockSourceRawData, setSelectedFlockSourceRawData] = useState<
@@ -455,13 +457,13 @@ const TransferToLayingFormModal = () => {
useEffect(() => { useEffect(() => {
if (isResponseSuccess(flockSourceRawData)) { if (isResponseSuccess(flockSourceRawData)) {
const selectedFlockSourceRawData = flockSourceRawData.data.find( const currentSelectedFlockSourceRawData = flockSourceRawData.data.find(
(item) => item.id === formik.values.flockSource?.value (item) => item.id === formik.values.flockSource?.value
); );
setSelectedFlockSourceRawData(selectedFlockSourceRawData); setSelectedFlockSourceRawData(currentSelectedFlockSourceRawData);
} }
}, [flockSourceRawData]); }, [flockSourceRawData, formikFlockSource]);
useEffect(() => { useEffect(() => {
formik.setFieldValue('totalQuantity', totalTransferedChicken); formik.setFieldValue('totalQuantity', totalTransferedChicken);
@@ -625,6 +627,7 @@ const TransferToLayingFormModal = () => {
> >
<div className='flex flex-row items-center gap-3'> <div className='flex flex-row items-center gap-3'>
<input <input
id={`flock-source-kandang-${item.project_flock_kandang_id}`}
type='radio' type='radio'
name='flockSourceKandang' name='flockSourceKandang'
value={item.project_flock_kandang_id} value={item.project_flock_kandang_id}
@@ -637,13 +640,14 @@ const TransferToLayingFormModal = () => {
/> />
<label <label
htmlFor={`flock-source-kandang-${item.project_flock_kandang_id}`}
className={cn('text-sm text-base-content/50', { className={cn('text-sm text-base-content/50', {
'cursor-pointer': isAvailable, 'cursor-pointer': isAvailable,
'cursor-not-allowed opacity-50': !isAvailable, 'cursor-not-allowed opacity-50': !isAvailable,
})} })}
> >
{item.kandang_name}{' '} {item.kandang_name}{' '}
<span className='text-base-content/20'>{`(Max: ${item.available_qty})`}</span> <span className='text-base-content/20'>{`(Max: ${item.available_qty ?? '-'})`}</span>
</label> </label>
</div> </div>
@@ -409,10 +409,17 @@ const PurchaseTable = () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
try { try {
await PurchaseApi.delete(selectedPurchase?.id as number); const deleteResponse = await PurchaseApi.delete(
refreshPurchaseRequests(); selectedPurchase?.id as number
deleteModal.closeModal(); );
toast.success('Berhasil menghapus data permintaan pembelian!');
if (isResponseSuccess(deleteResponse)) {
refreshPurchaseRequests();
deleteModal.closeModal();
toast.success('Berhasil menghapus data permintaan pembelian!');
} else {
toast.error(deleteResponse?.message ?? 'Gagal menghapus data!');
}
} catch { } catch {
toast.error('Gagal menghapus data permintaan pembelian!'); toast.error('Gagal menghapus data permintaan pembelian!');
} }
@@ -89,7 +89,10 @@ export function Dashboard() {
options: kandangOptions, options: kandangOptions,
loadMore: loadMoreKandang, loadMore: loadMoreKandang,
isLoadingMore: isLoadingMoreKandang, isLoadingMore: isLoadingMoreKandang,
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name'); } = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
order_by: 'asc',
sort_by: 'name',
});
const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => { const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement; const target = e.target as HTMLDivElement;
@@ -110,7 +110,10 @@ export function ListDailyChecklistContent() {
options: kandangOptions, options: kandangOptions,
isLoadingMore: isLoadingMoreKandang, isLoadingMore: isLoadingMoreKandang,
loadMore: loadMoreKandang, loadMore: loadMoreKandang,
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name'); } = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
order_by: 'asc',
sort_by: 'name',
});
const checklistList = isResponseSuccess(checklistListRes) const checklistList = isResponseSuccess(checklistListRes)
? checklistListRes.data || [] ? checklistListRes.data || []
@@ -96,7 +96,10 @@ export function MasterEmployeeContent() {
options: kandangOptions, options: kandangOptions,
loadMore: loadMoreKandang, loadMore: loadMoreKandang,
isLoadingMore: isLoadingMoreKandang, isLoadingMore: isLoadingMoreKandang,
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name'); } = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
order_by: 'asc',
sort_by: 'name',
});
const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => { const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement; const target = e.target as HTMLDivElement;
@@ -368,7 +368,9 @@ export function MasterKandangContent() {
name='search' name='search'
placeholder='Cari kandang...' placeholder='Cari kandang...'
value={tableFilterState.search} value={tableFilterState.search}
onChange={(e) => updateFilter('search', e.target.value)} onChange={(e) =>
updateFilter('search', e.target.value, true)
}
className={{ className={{
wrapper: 'w-full sm:w-[280px] border-gray-200', wrapper: 'w-full sm:w-[280px] border-gray-200',
inputWrapper: 'px-3 py-2 h-fit rounded-md', inputWrapper: 'px-3 py-2 h-fit rounded-md',
@@ -383,7 +385,11 @@ export function MasterKandangContent() {
<Select <Select
value={tableFilterState.location_id} value={tableFilterState.location_id}
onValueChange={(value) => onValueChange={(value) =>
updateFilter('location_id', value === 'all' ? '' : value) updateFilter(
'location_id',
value === 'all' ? '' : value,
true
)
} }
> >
<SelectTrigger className='w-[180px] border-gray-200'> <SelectTrigger className='w-[180px] border-gray-200'>
@@ -137,6 +137,8 @@ export function DailyChecklistReportsContent() {
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', { } = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
area_id: tableFilterState.area_id, area_id: tableFilterState.area_id,
location_id: tableFilterState.location_id, location_id: tableFilterState.location_id,
order_by: 'asc',
sort_by: 'name',
}); });
const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => { const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => {
@@ -159,17 +161,24 @@ export function DailyChecklistReportsContent() {
} }
); );
const { options: employeeOptions } = useSelect( const {
EmployeeApi.basePath, options: employeeOptions,
'id', loadMore: loadMoreEmployee,
'name', isLoadingMore: isLoadingMoreEmployee,
'search', } = useSelect(EmployeeApi.basePath, 'id', 'name', 'search', {
{ order_by: 'asc',
page: '1', sort_by: 'name',
limit: '500', kandang_id: tableFilterState.kandang_id,
kandang_id: tableFilterState.kandang_id, });
const handleEmployeeScroll = (e: React.UIEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement;
if (target.scrollHeight - target.scrollTop <= target.clientHeight + 10) {
if (!isLoadingMoreEmployee) {
loadMoreEmployee();
}
} }
); };
const currentMonthMaxDay = new Date( const currentMonthMaxDay = new Date(
Number(tableFilterState.tahun), Number(tableFilterState.tahun),
@@ -493,7 +502,7 @@ export function DailyChecklistReportsContent() {
> >
<SelectValue placeholder='Semua ABK' /> <SelectValue placeholder='Semua ABK' />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent onScroll={handleEmployeeScroll}>
<SelectItem value='ALL'>Semua ABK</SelectItem> <SelectItem value='ALL'>Semua ABK</SelectItem>
{employeeOptions.map((employee) => ( {employeeOptions.map((employee) => (
<SelectItem <SelectItem
@@ -503,6 +512,11 @@ export function DailyChecklistReportsContent() {
{employee.label} {employee.label}
</SelectItem> </SelectItem>
))} ))}
{isLoadingMoreEmployee && (
<div className='flex justify-center p-2'>
<Loader2 className='h-4 w-4 animate-spin text-gray-500' />
</div>
)}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
+2 -2
View File
@@ -5,7 +5,7 @@ import { RequestOptions } from '@/services/http/base';
import { redirectToSSO } from '@/lib/auth-helper'; import { redirectToSSO } from '@/lib/auth-helper';
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? ''; const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 30_000 }); const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 60_000 });
axiosClient.interceptors.response.use( axiosClient.interceptors.response.use(
(response) => response, (response) => response,
@@ -38,7 +38,7 @@ export async function httpClient<T, B = unknown>(
method: opts.method ?? 'GET', method: opts.method ?? 'GET',
params: opts.query, params: opts.query,
data: opts.body, data: opts.body,
timeout: opts.timeoutMs ?? 30_000, timeout: opts.timeoutMs ?? 60_000,
withCredentials: isCookieAuth && !isBearerAuth, withCredentials: isCookieAuth && !isBearerAuth,
responseType: opts.responseType, responseType: opts.responseType,
headers: { headers: {
+2
View File
@@ -117,6 +117,7 @@ export type CreateGrowingRecordingPayload = {
product_warehouse_id?: number; product_warehouse_id?: number;
source_product_warehouse_id?: number; source_product_warehouse_id?: number;
qty?: number; qty?: number;
product_warehouse?: ProductWarehouse;
}[]; }[];
}; };
@@ -124,6 +125,7 @@ export type CreateEggPayload = {
product_warehouse_id?: number; product_warehouse_id?: number;
qty?: number; qty?: number;
weight?: number; weight?: number;
product_warehouse?: ProductWarehouse;
}; };
export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & { export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {