mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
refactor(FE): Restrict stock and depletion edits based on permissions
This commit is contained in:
@@ -1043,6 +1043,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!recordingRestriction.canEditStock) {
|
||||||
|
baseValues.stocks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recordingRestriction.canEditDepletion) {
|
||||||
|
baseValues.depletions = [];
|
||||||
|
}
|
||||||
|
|
||||||
return baseValues;
|
return baseValues;
|
||||||
}, [
|
}, [
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -1053,6 +1061,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
selectedLocation,
|
selectedLocation,
|
||||||
selectedProjectFlock,
|
selectedProjectFlock,
|
||||||
selectedKandang,
|
selectedKandang,
|
||||||
|
recordingRestriction.canEditStock,
|
||||||
|
recordingRestriction.canEditDepletion,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const formik = useFormik<
|
const formik = useFormik<
|
||||||
@@ -2443,86 +2453,39 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Stocks Table */}
|
{/* Stocks Table - Only show if can edit stock or has data */}
|
||||||
<Card
|
{(recordingRestriction.canEditStock ||
|
||||||
title='Stok Persediaan'
|
(type === 'detail' && formik.values.stocks?.length > 0)) && (
|
||||||
className={{
|
<Card
|
||||||
wrapper: 'w-full mb-4 shadow',
|
title='Stok Persediaan'
|
||||||
title: 'mb-4',
|
className={{
|
||||||
}}
|
wrapper: 'w-full mb-4 shadow',
|
||||||
>
|
title: 'mb-4',
|
||||||
<div className='overflow-x-auto'>
|
}}
|
||||||
<table className='table'>
|
>
|
||||||
<thead>
|
<div className='overflow-x-auto'>
|
||||||
<tr>
|
<table className='table'>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
<thead>
|
||||||
<th>
|
<tr>
|
||||||
<CheckboxInput
|
|
||||||
name='select-all-stocks'
|
|
||||||
checked={
|
|
||||||
formik.values.stocks?.length ===
|
|
||||||
selectedStocks.length &&
|
|
||||||
formik.values.stocks?.length > 0
|
|
||||||
}
|
|
||||||
onChange={(
|
|
||||||
e: React.ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
setSelectedStocks(
|
|
||||||
formik.values.stocks?.map((_, idx) => idx) ?? []
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setSelectedStocks([]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!recordingRestriction.canEditStock}
|
|
||||||
classNames={{
|
|
||||||
wrapper: 'flex justify-center',
|
|
||||||
checkbox: 'checkbox checkbox-sm',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
)}
|
|
||||||
<th>
|
|
||||||
Persediaan
|
|
||||||
<span
|
|
||||||
className='tooltip tooltip-error tooltip-bottom '
|
|
||||||
data-tip='required'
|
|
||||||
>
|
|
||||||
<span className='text-error'>*</span>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Jumlah Pakai
|
|
||||||
<span
|
|
||||||
className='tooltip tooltip-error tooltip-bottom '
|
|
||||||
data-tip='required'
|
|
||||||
>
|
|
||||||
<span className='text-error'>*</span>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
|
||||||
<th>Action</th>
|
|
||||||
)}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{formik.values.stocks?.map((stock, idx) => (
|
|
||||||
<tr key={`stock-${idx}`}>
|
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td className='align-middle!'>
|
<th>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`stock-${idx}`}
|
name='select-all-stocks'
|
||||||
checked={selectedStocks.includes(idx)}
|
checked={
|
||||||
|
formik.values.stocks?.length ===
|
||||||
|
selectedStocks.length &&
|
||||||
|
formik.values.stocks?.length > 0
|
||||||
|
}
|
||||||
onChange={(
|
onChange={(
|
||||||
e: React.ChangeEvent<HTMLInputElement>
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
) => {
|
) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedStocks([...selectedStocks, idx]);
|
|
||||||
} else {
|
|
||||||
setSelectedStocks(
|
setSelectedStocks(
|
||||||
selectedStocks.filter((i) => i !== idx)
|
formik.values.stocks?.map((_, idx) => idx) ??
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
setSelectedStocks([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!recordingRestriction.canEditStock}
|
disabled={!recordingRestriction.canEditStock}
|
||||||
@@ -2531,169 +2494,221 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
checkbox: 'checkbox checkbox-sm',
|
checkbox: 'checkbox checkbox-sm',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</th>
|
||||||
)}
|
)}
|
||||||
<td>
|
<th>
|
||||||
<SelectInput
|
Persediaan
|
||||||
required
|
<span
|
||||||
key={`stock-product-${idx}-${stock.product_warehouse_id}`}
|
className='tooltip tooltip-error tooltip-bottom '
|
||||||
value={
|
data-tip='required'
|
||||||
unifiedStockProducts.find(
|
>
|
||||||
(product) =>
|
<span className='text-error'>*</span>
|
||||||
product.value === stock.product_warehouse_id
|
</span>
|
||||||
) || null
|
</th>
|
||||||
}
|
<th>
|
||||||
onChange={(selectedOption) => {
|
Jumlah Pakai
|
||||||
const option = selectedOption as OptionType | null;
|
<span
|
||||||
formik.setFieldValue(
|
className='tooltip tooltip-error tooltip-bottom '
|
||||||
`stocks.${idx}.product_warehouse_id`,
|
data-tip='required'
|
||||||
option?.value || 0
|
>
|
||||||
);
|
<span className='text-error'>*</span>
|
||||||
}}
|
</span>
|
||||||
options={getAvailableStockProductOptions(idx)}
|
</th>
|
||||||
placeholder={
|
|
||||||
!formik.values.project_flock_kandang_id
|
|
||||||
? 'Pilih kandang terlebih dahulu'
|
|
||||||
: 'Pilih Produk'
|
|
||||||
}
|
|
||||||
isLoading={isLoadingStockProducts}
|
|
||||||
onMenuScrollToBottom={loadMoreStockProducts}
|
|
||||||
isError={
|
|
||||||
isRepeaterInputError(
|
|
||||||
'stocks',
|
|
||||||
'product_warehouse_id',
|
|
||||||
idx
|
|
||||||
).isError
|
|
||||||
}
|
|
||||||
errorMessage={
|
|
||||||
isRepeaterInputError(
|
|
||||||
'stocks',
|
|
||||||
'product_warehouse_id',
|
|
||||||
idx
|
|
||||||
).errorMessage
|
|
||||||
}
|
|
||||||
className={{
|
|
||||||
wrapper: 'w-full min-w-48',
|
|
||||||
}}
|
|
||||||
isSearchable
|
|
||||||
isDisabled={
|
|
||||||
type === 'detail' ||
|
|
||||||
!formik.values.project_flock_kandang_id ||
|
|
||||||
!recordingRestriction.canEditStock
|
|
||||||
}
|
|
||||||
isClearable={type !== 'detail'}
|
|
||||||
inputPrefix={
|
|
||||||
stock.product_warehouse_id
|
|
||||||
? getProductFlagBadgeAdornment(
|
|
||||||
stock.product_warehouse_id
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className='flex flex-col gap-1'>
|
|
||||||
<NumberInput
|
|
||||||
required
|
|
||||||
name={`stocks.${idx}.qty`}
|
|
||||||
value={stock.qty ?? ''}
|
|
||||||
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
|
||||||
onBlur={formik.handleBlur}
|
|
||||||
decimalScale={0}
|
|
||||||
allowNegative={false}
|
|
||||||
thousandSeparator=','
|
|
||||||
decimalSeparator='.'
|
|
||||||
isError={
|
|
||||||
isRepeaterInputError('stocks', 'qty', idx)
|
|
||||||
.isError || Boolean(getStockUsageError(idx))
|
|
||||||
}
|
|
||||||
errorMessage={
|
|
||||||
isRepeaterInputError('stocks', 'qty', idx)
|
|
||||||
.errorMessage ||
|
|
||||||
getStockUsageError(idx) ||
|
|
||||||
undefined
|
|
||||||
}
|
|
||||||
readOnly={type === 'detail'}
|
|
||||||
className={{
|
|
||||||
wrapper: 'w-full min-w-24',
|
|
||||||
}}
|
|
||||||
placeholder='Masukkan jumlah pakai'
|
|
||||||
inputSuffix={
|
|
||||||
stock.product_warehouse_id
|
|
||||||
? getProductUomSuffix(
|
|
||||||
stock.product_warehouse_id,
|
|
||||||
'stock'
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
disabled={
|
|
||||||
type === 'detail' ||
|
|
||||||
!recordingRestriction.canEditStock
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{getStockUsageAdornment(idx)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<td>
|
<th>Action</th>
|
||||||
<div className='flex items-center'>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={() => removeStock(idx)}
|
|
||||||
disabled={!recordingRestriction.canEditStock}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:trash-can'
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{formik.values.stocks?.map((stock, idx) => (
|
||||||
</div>
|
<tr key={`stock-${idx}`}>
|
||||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
<div className='flex justify-center items-center mt-4 gap-4'>
|
<td className='align-middle!'>
|
||||||
{selectedStocks.length > 0 &&
|
<CheckboxInput
|
||||||
recordingRestriction.canEditStock && (
|
name={`stock-${idx}`}
|
||||||
|
checked={selectedStocks.includes(idx)}
|
||||||
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedStocks([...selectedStocks, idx]);
|
||||||
|
} else {
|
||||||
|
setSelectedStocks(
|
||||||
|
selectedStocks.filter((i) => i !== idx)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!recordingRestriction.canEditStock}
|
||||||
|
classNames={{
|
||||||
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td>
|
||||||
|
<SelectInput
|
||||||
|
required
|
||||||
|
key={`stock-product-${idx}-${stock.product_warehouse_id}`}
|
||||||
|
value={
|
||||||
|
unifiedStockProducts.find(
|
||||||
|
(product) =>
|
||||||
|
product.value === stock.product_warehouse_id
|
||||||
|
) || null
|
||||||
|
}
|
||||||
|
onChange={(selectedOption) => {
|
||||||
|
const option =
|
||||||
|
selectedOption as OptionType | null;
|
||||||
|
formik.setFieldValue(
|
||||||
|
`stocks.${idx}.product_warehouse_id`,
|
||||||
|
option?.value || 0
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={getAvailableStockProductOptions(idx)}
|
||||||
|
placeholder={
|
||||||
|
!formik.values.project_flock_kandang_id
|
||||||
|
? 'Pilih kandang terlebih dahulu'
|
||||||
|
: 'Pilih Produk'
|
||||||
|
}
|
||||||
|
isLoading={isLoadingStockProducts}
|
||||||
|
onMenuScrollToBottom={loadMoreStockProducts}
|
||||||
|
isError={
|
||||||
|
isRepeaterInputError(
|
||||||
|
'stocks',
|
||||||
|
'product_warehouse_id',
|
||||||
|
idx
|
||||||
|
).isError
|
||||||
|
}
|
||||||
|
errorMessage={
|
||||||
|
isRepeaterInputError(
|
||||||
|
'stocks',
|
||||||
|
'product_warehouse_id',
|
||||||
|
idx
|
||||||
|
).errorMessage
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full min-w-48',
|
||||||
|
}}
|
||||||
|
isSearchable
|
||||||
|
isDisabled={
|
||||||
|
type === 'detail' ||
|
||||||
|
!formik.values.project_flock_kandang_id ||
|
||||||
|
!recordingRestriction.canEditStock
|
||||||
|
}
|
||||||
|
isClearable={type !== 'detail'}
|
||||||
|
inputPrefix={
|
||||||
|
stock.product_warehouse_id
|
||||||
|
? getProductFlagBadgeAdornment(
|
||||||
|
stock.product_warehouse_id
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
name={`stocks.${idx}.qty`}
|
||||||
|
value={stock.qty ?? ''}
|
||||||
|
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
decimalScale={0}
|
||||||
|
allowNegative={false}
|
||||||
|
thousandSeparator=','
|
||||||
|
decimalSeparator='.'
|
||||||
|
isError={
|
||||||
|
isRepeaterInputError('stocks', 'qty', idx)
|
||||||
|
.isError || Boolean(getStockUsageError(idx))
|
||||||
|
}
|
||||||
|
errorMessage={
|
||||||
|
isRepeaterInputError('stocks', 'qty', idx)
|
||||||
|
.errorMessage ||
|
||||||
|
getStockUsageError(idx) ||
|
||||||
|
undefined
|
||||||
|
}
|
||||||
|
readOnly={type === 'detail'}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full min-w-24',
|
||||||
|
}}
|
||||||
|
placeholder='Masukkan jumlah pakai'
|
||||||
|
inputSuffix={
|
||||||
|
stock.product_warehouse_id
|
||||||
|
? getProductUomSuffix(
|
||||||
|
stock.product_warehouse_id,
|
||||||
|
'stock'
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
type === 'detail' ||
|
||||||
|
!recordingRestriction.canEditStock
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{getStockUsageAdornment(idx)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
|
<td>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='error'
|
||||||
|
onClick={() => removeStock(idx)}
|
||||||
|
disabled={!recordingRestriction.canEditStock}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='mdi:trash-can'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||||
|
<div className='flex justify-center items-center mt-4 gap-4'>
|
||||||
|
{selectedStocks.length > 0 &&
|
||||||
|
recordingRestriction.canEditStock && (
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
color='error'
|
||||||
|
onClick={removeSelectedStocks}
|
||||||
|
disabled={selectedStocks.length === 0}
|
||||||
|
className='w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:trash-can' width={24} height={24} />
|
||||||
|
Hapus Terpilih ({selectedStocks.length})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
!recordingRestriction.canEditStock
|
||||||
|
? 'Stock tidak dapat ditambahkan pada masa transisi Laying'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
position='top'
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='error'
|
color='success'
|
||||||
onClick={removeSelectedStocks}
|
onClick={addStock}
|
||||||
disabled={selectedStocks.length === 0}
|
|
||||||
className='w-fit'
|
className='w-fit'
|
||||||
|
disabled={!recordingRestriction.canEditStock}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:trash-can' width={24} height={24} />
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
Hapus Terpilih ({selectedStocks.length})
|
Tambah Stok
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</Tooltip>
|
||||||
<Tooltip
|
</div>
|
||||||
content={
|
)}
|
||||||
!recordingRestriction.canEditStock
|
</Card>
|
||||||
? 'Stock tidak dapat ditambahkan pada masa transisi Laying'
|
)}
|
||||||
: ''
|
|
||||||
}
|
|
||||||
position='top'
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
color='success'
|
|
||||||
onClick={addStock}
|
|
||||||
className='w-fit'
|
|
||||||
disabled={!recordingRestriction.canEditStock}
|
|
||||||
>
|
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
|
||||||
Tambah Stok
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Transition Warning Banner -- MOVED UP -- */}
|
{/* Transition Warning Banner -- MOVED UP -- */}
|
||||||
{isTransitionPeriod && (
|
{isTransitionPeriod && (
|
||||||
|
|||||||
Reference in New Issue
Block a user