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 ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import Table from '@/components/Table';
import Dropdown from '@/components/Dropdown';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn, formatDate } from '@/lib/helper';
import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data';
@@ -44,6 +43,7 @@ import {
import Modal from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio';
import ButtonFilter from '@/components/helper/ButtonFilter';
import NumberInput from '@/components/input/NumberInput';
const RowOptionsMenu = ({
props,
@@ -211,8 +211,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
const {
isChickinApproveModalOpen,
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 =====
const areaValue = useMemo(() => {
if (!formik.values.area_id) return null;
@@ -393,13 +384,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
);
}, [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 =====
const handleFilterAreaChange = (area: OptionType | null) => {
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 = () => {
setApprovalAction('APPROVED');
confirmModal.openModal();
@@ -1020,51 +996,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
onClick={handleFilterModalOpen}
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>
@@ -1393,18 +1324,14 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
isClearable={true}
/>
<SelectInputRadio
<NumberInput
label='Periode'
placeholder='Pilih Periode'
options={periodOptions}
value={periodValue}
onChange={(val) => {
if (!Array.isArray(val)) {
formik.setFieldValue('period', val?.value || null);
}
}}
name='period'
placeholder='Masukkan Periode'
value={formik.values.period ?? ''}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={{ wrapper: 'w-full' }}
isClearable
/>
</div>
@@ -5,6 +5,7 @@ import {
CreateLayingRecordingPayload,
CreateEggPayload,
} from '@/types/api/production/recording';
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
type RecordingGrowingFormSchemaType = {
record_date: string;
@@ -29,11 +30,19 @@ type RecordingGrowingFormSchemaType = {
} | null;
project_flock_kandang_id: number;
stocks: {
product_warehouse_id: number;
product_warehouse_id:
| {
value: number;
label: string;
}
| undefined;
qty: number | string;
}[];
depletions: {
product_warehouse_id?: number;
product_warehouse_id?: {
value: number;
label: string;
};
source_product_warehouse_id?: number;
qty?: number | string;
}[];
@@ -41,34 +50,48 @@ type RecordingGrowingFormSchemaType = {
type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
eggs: {
product_warehouse_id?: number;
product_warehouse_id?: {
value: number;
label: string;
};
qty?: number | string;
weight?: number | string;
}[];
};
export type StockSchema = {
product_warehouse_id: number;
product_warehouse_id: {
value: number;
label: string;
};
qty: number | string;
};
export type DepletionSchema = {
product_warehouse_id?: number;
product_warehouse_id?: {
value: number;
label: string;
};
source_product_warehouse_id?: number;
qty?: number | string;
};
export type EggSchema = {
product_warehouse_id?: number;
product_warehouse_id?: {
value: number;
label: string;
};
qty?: number | string;
weight?: number | string;
};
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!')
.min(1, 'Produk wajib diisi!')
.typeError('Produk harus berupa angka!'),
.typeError('Produk wajib diisi!'),
qty: Yup.number()
.required('Jumlah penggunaan wajib diisi!')
.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({
product_warehouse_id: Yup.number()
product_warehouse_id: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
})
.optional()
.typeError('Depletions harus berupa angka!'),
source_product_warehouse_id: Yup.number()
@@ -88,7 +114,10 @@ const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = 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()
.typeError('Kondisi 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 ??
0,
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:
(stock as { qty?: number; usage_amount?: number }).qty ||
(stock as { qty?: number; usage_amount?: number }).usage_amount ||
'',
})) ?? [
{
product_warehouse_id: 0,
product_warehouse_id: undefined,
qty: '',
},
],
@@ -263,13 +295,16 @@ export const getRecordingGrowingFormInitialValues = (
(
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,
qty: depletion.qty,
})
) ?? [
{
product_warehouse_id: 0,
product_warehouse_id: undefined,
qty: '',
},
],
@@ -281,12 +316,15 @@ export const getRecordingLayingFormInitialValues = (
...getRecordingGrowingFormInitialValues(initialValues),
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,
weight: egg.weight,
})) ?? [
{
product_warehouse_id: 0,
product_warehouse_id: undefined,
qty: '',
weight: '',
},
@@ -522,7 +522,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
? values.depletions
?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!,
product_warehouse_id: depletion.product_warehouse_id?.value ?? 0,
...(depletion.source_product_warehouse_id && {
source_product_warehouse_id:
depletion.source_product_warehouse_id,
@@ -533,13 +533,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const stocks = recordingRestriction.canEditStock
? (values.stocks ?? [])
.filter((s) => s.product_warehouse_id && s.qty)
.filter((s) => s.product_warehouse_id?.value && s.qty)
.map((stock) => ({
// In migration mode, product_warehouse_id field holds product.id;
// send it as product_id so the backend auto-creates the warehouse entry.
...(isMigrationMode
? { product_id: stock.product_warehouse_id }
: { product_warehouse_id: stock.product_warehouse_id }),
? { product_id: stock.product_warehouse_id?.value }
: { product_warehouse_id: stock.product_warehouse_id?.value }),
qty: Number(stock.qty) || 0,
}))
: [];
@@ -561,9 +561,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const createLayingPayload = useCallback(
(values: RecordingLayingFormValues) => {
const depletions = values.depletions
?.filter((d) => d.product_warehouse_id && d.qty)
?.filter((d) => d.product_warehouse_id?.value && d.qty)
.map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!,
product_warehouse_id: depletion.product_warehouse_id?.value ?? 0,
...(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
?.filter((e) => e.product_warehouse_id && e.qty && e.weight)
.map((egg) => ({
product_warehouse_id: egg.product_warehouse_id!,
product_warehouse_id: egg.product_warehouse_id?.value ?? 0,
qty: Number(egg.qty) || 0,
weight:
typeof egg.weight === 'number'
@@ -583,11 +583,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const stocks = recordingRestriction.canEditStock
? values.stocks
.filter((s) => s.product_warehouse_id && s.qty)
.filter((s) => s.product_warehouse_id?.value && s.qty)
.map((stock) => ({
...(isMigrationMode
? { product_id: stock.product_warehouse_id }
: { product_warehouse_id: stock.product_warehouse_id }),
? { product_id: stock.product_warehouse_id?.value }
: { product_warehouse_id: stock.product_warehouse_id?.value }),
qty: Number(stock.qty) || 0,
}))
: [];
@@ -636,21 +636,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
rawData: stockProductsPW,
isLoadingOptions: isLoadingStockProductsPW,
loadMore: loadMoreStockProductsPW,
} = useSelect(
isMigrationMode ? null : ProductWarehouseApi.basePath,
'id',
'product.name',
'search',
{
flags: 'PAKAN,OVK',
limit: '100',
available_only: 'false',
location_id: stockProductsLocationId,
...(selectedKandangId
? { kandang_id: selectedKandangId.toString() }
: {}),
}
);
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
flags: 'PAKAN,OVK',
limit: '100',
available_only: 'false',
location_id: stockProductsLocationId,
...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}),
});
const {
setInputValue: setStockMasterInputValue,
@@ -1283,8 +1275,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// product_warehouse object returned by the API.
if (isMigrationMode && type === 'edit' && initialValues?.stocks?.length) {
baseValues.stocks = initialValues.stocks.map((stock) => ({
product_warehouse_id:
stock.product_warehouse?.product_id ?? stock.product_warehouse_id,
product_warehouse_id: {
value: Number(
stock.product_warehouse?.product_id ?? stock.product_warehouse_id
),
label: getProductWarehouseOptionLabel(stock.product_warehouse),
},
qty: stock.usage_amount ?? '',
}));
}
@@ -1438,8 +1434,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
formik.setFieldValue(
'stocks',
initialValues.stocks.map((stock) => ({
product_warehouse_id:
stock.product_warehouse?.product_id ?? stock.product_warehouse_id,
product_warehouse_id: {
value: Number(
stock.product_warehouse?.product_id ?? stock.product_warehouse_id
),
label: getProductWarehouseOptionLabel(stock.product_warehouse),
},
qty: stock.usage_amount ?? '',
}))
);
@@ -1462,7 +1462,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
(stockIdx: number) => {
if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
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;
},
[formik.values.stocks, type]
@@ -1492,13 +1492,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const getStockUsageAdornment = useCallback(
(stockIdx: number) => {
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 availableStock = getAvailableStock(stock.product_warehouse_id);
const availableStock = getAvailableStock(
stock.product_warehouse_id.value
);
const requestedUsage = Number(stock.qty) || 0;
const remainingStock = availableStock - requestedUsage;
const { pendingQty } = getStockPendingInfo(stock.product_warehouse_id);
const { pendingQty } = getStockPendingInfo(
stock.product_warehouse_id.value
);
if (isDetail) {
if (pendingQty > 0) {
@@ -1605,10 +1609,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return (
idx !== currentIdx &&
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(
(opt) => !selectedProductIds.includes(Number(opt.value))
@@ -1625,10 +1629,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return (
idx !== currentIdx &&
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(
(opt) => !selectedProductIds.includes(Number(opt.value))
@@ -1645,10 +1649,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
return (
idx !== currentIdx &&
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(
(opt) => !selectedProductIds.includes(Number(opt.value))
@@ -1694,7 +1698,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
isError: touchedField && Boolean(errorField?.[column]),
errorMessage:
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>
<SelectInput
required
key={`stock-product-${idx}-${stock.product_warehouse_id}`}
value={
unifiedStockProducts.find(
(product) =>
product.value === stock.product_warehouse_id
) || null
}
key={`stock-product-${idx}-${stock.product_warehouse_id?.value}`}
value={stock.product_warehouse_id}
onInputChange={setStockInputValue}
onChange={(selectedOption) => {
const option =
selectedOption as OptionType | null;
formik.setFieldValue(
`stocks.${idx}.product_warehouse_id`,
option?.value || 0
option
);
}}
options={getAvailableStockProductOptions(idx)}
@@ -2950,9 +2951,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
}
isClearable={type !== 'detail'}
inputPrefix={
stock.product_warehouse_id
stock.product_warehouse_id?.value
? getProductFlagBadgeAdornment(
stock.product_warehouse_id
stock.product_warehouse_id.value
)
: undefined
}
@@ -2988,7 +2989,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
inputSuffix={
stock.product_warehouse_id
? getProductUomSuffix(
stock.product_warehouse_id,
stock.product_warehouse_id.value,
'stock'
)
: null
@@ -3181,19 +3182,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)}
<td>
<SelectInput
value={
depletionProducts.find(
(product) =>
product.value ===
depletion.product_warehouse_id
) || null
}
value={depletion.product_warehouse_id}
onChange={(selectedOption) => {
const option =
selectedOption as OptionType | null;
formik.setFieldValue(
`depletions.${idx}.product_warehouse_id`,
option?.value || 0
option
);
}}
options={getAvailableDepletionProductOptions(idx)}
@@ -3256,7 +3251,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
inputSuffix={
depletion.product_warehouse_id
? getProductUomSuffix(
depletion.product_warehouse_id,
depletion.product_warehouse_id.value,
'depletion'
)
: null
@@ -3434,18 +3429,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)}
<td>
<SelectInput
value={
eggProducts.find(
(product) =>
product.value === egg.product_warehouse_id
) || null
}
value={egg.product_warehouse_id}
onChange={(selectedOption) => {
const option =
selectedOption as OptionType | null;
formik.setFieldValue(
`eggs.${idx}.product_warehouse_id`,
option?.value || 0
option
);
}}
options={getAvailableEggProductOptions(idx)}
@@ -223,6 +223,8 @@ const TransferToLayingFormModal = () => {
},
});
const { flockSource: formikFlockSource } = formik.values;
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
const [selectedFlockSourceRawData, setSelectedFlockSourceRawData] = useState<
@@ -455,13 +457,13 @@ const TransferToLayingFormModal = () => {
useEffect(() => {
if (isResponseSuccess(flockSourceRawData)) {
const selectedFlockSourceRawData = flockSourceRawData.data.find(
const currentSelectedFlockSourceRawData = flockSourceRawData.data.find(
(item) => item.id === formik.values.flockSource?.value
);
setSelectedFlockSourceRawData(selectedFlockSourceRawData);
setSelectedFlockSourceRawData(currentSelectedFlockSourceRawData);
}
}, [flockSourceRawData]);
}, [flockSourceRawData, formikFlockSource]);
useEffect(() => {
formik.setFieldValue('totalQuantity', totalTransferedChicken);
@@ -625,6 +627,7 @@ const TransferToLayingFormModal = () => {
>
<div className='flex flex-row items-center gap-3'>
<input
id={`flock-source-kandang-${item.project_flock_kandang_id}`}
type='radio'
name='flockSourceKandang'
value={item.project_flock_kandang_id}
@@ -637,13 +640,14 @@ const TransferToLayingFormModal = () => {
/>
<label
htmlFor={`flock-source-kandang-${item.project_flock_kandang_id}`}
className={cn('text-sm text-base-content/50', {
'cursor-pointer': isAvailable,
'cursor-not-allowed opacity-50': !isAvailable,
})}
>
{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>
</div>
@@ -409,10 +409,17 @@ const PurchaseTable = () => {
setIsDeleteLoading(true);
try {
await PurchaseApi.delete(selectedPurchase?.id as number);
refreshPurchaseRequests();
deleteModal.closeModal();
toast.success('Berhasil menghapus data permintaan pembelian!');
const deleteResponse = await PurchaseApi.delete(
selectedPurchase?.id as number
);
if (isResponseSuccess(deleteResponse)) {
refreshPurchaseRequests();
deleteModal.closeModal();
toast.success('Berhasil menghapus data permintaan pembelian!');
} else {
toast.error(deleteResponse?.message ?? 'Gagal menghapus data!');
}
} catch {
toast.error('Gagal menghapus data permintaan pembelian!');
}
@@ -89,7 +89,10 @@ export function Dashboard() {
options: kandangOptions,
loadMore: loadMoreKandang,
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 target = e.target as HTMLDivElement;
@@ -110,7 +110,10 @@ export function ListDailyChecklistContent() {
options: kandangOptions,
isLoadingMore: isLoadingMoreKandang,
loadMore: loadMoreKandang,
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name');
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
order_by: 'asc',
sort_by: 'name',
});
const checklistList = isResponseSuccess(checklistListRes)
? checklistListRes.data || []
@@ -96,7 +96,10 @@ export function MasterEmployeeContent() {
options: kandangOptions,
loadMore: loadMoreKandang,
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 target = e.target as HTMLDivElement;
@@ -368,7 +368,9 @@ export function MasterKandangContent() {
name='search'
placeholder='Cari kandang...'
value={tableFilterState.search}
onChange={(e) => updateFilter('search', e.target.value)}
onChange={(e) =>
updateFilter('search', e.target.value, true)
}
className={{
wrapper: 'w-full sm:w-[280px] border-gray-200',
inputWrapper: 'px-3 py-2 h-fit rounded-md',
@@ -383,7 +385,11 @@ export function MasterKandangContent() {
<Select
value={tableFilterState.location_id}
onValueChange={(value) =>
updateFilter('location_id', value === 'all' ? '' : value)
updateFilter(
'location_id',
value === 'all' ? '' : value,
true
)
}
>
<SelectTrigger className='w-[180px] border-gray-200'>
@@ -137,6 +137,8 @@ export function DailyChecklistReportsContent() {
} = useSelect(DailyChecklistKandangApi.basePath, 'id', 'name', 'search', {
area_id: tableFilterState.area_id,
location_id: tableFilterState.location_id,
order_by: 'asc',
sort_by: 'name',
});
const handleKandangScroll = (e: React.UIEvent<HTMLDivElement>) => {
@@ -159,17 +161,24 @@ export function DailyChecklistReportsContent() {
}
);
const { options: employeeOptions } = useSelect(
EmployeeApi.basePath,
'id',
'name',
'search',
{
page: '1',
limit: '500',
kandang_id: tableFilterState.kandang_id,
const {
options: employeeOptions,
loadMore: loadMoreEmployee,
isLoadingMore: isLoadingMoreEmployee,
} = useSelect(EmployeeApi.basePath, 'id', 'name', 'search', {
order_by: 'asc',
sort_by: 'name',
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(
Number(tableFilterState.tahun),
@@ -493,7 +502,7 @@ export function DailyChecklistReportsContent() {
>
<SelectValue placeholder='Semua ABK' />
</SelectTrigger>
<SelectContent>
<SelectContent onScroll={handleEmployeeScroll}>
<SelectItem value='ALL'>Semua ABK</SelectItem>
{employeeOptions.map((employee) => (
<SelectItem
@@ -503,6 +512,11 @@ export function DailyChecklistReportsContent() {
{employee.label}
</SelectItem>
))}
{isLoadingMoreEmployee && (
<div className='flex justify-center p-2'>
<Loader2 className='h-4 w-4 animate-spin text-gray-500' />
</div>
)}
</SelectContent>
</Select>
</div>
+2 -2
View File
@@ -5,7 +5,7 @@ import { RequestOptions } from '@/services/http/base';
import { redirectToSSO } from '@/lib/auth-helper';
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(
(response) => response,
@@ -38,7 +38,7 @@ export async function httpClient<T, B = unknown>(
method: opts.method ?? 'GET',
params: opts.query,
data: opts.body,
timeout: opts.timeoutMs ?? 30_000,
timeout: opts.timeoutMs ?? 60_000,
withCredentials: isCookieAuth && !isBearerAuth,
responseType: opts.responseType,
headers: {
+2
View File
@@ -117,6 +117,7 @@ export type CreateGrowingRecordingPayload = {
product_warehouse_id?: number;
source_product_warehouse_id?: number;
qty?: number;
product_warehouse?: ProductWarehouse;
}[];
};
@@ -124,6 +125,7 @@ export type CreateEggPayload = {
product_warehouse_id?: number;
qty?: number;
weight?: number;
product_warehouse?: ProductWarehouse;
};
export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {