mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +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;
|
||||
}, [
|
||||
initialValues,
|
||||
@@ -1053,6 +1061,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
selectedLocation,
|
||||
selectedProjectFlock,
|
||||
selectedKandang,
|
||||
recordingRestriction.canEditStock,
|
||||
recordingRestriction.canEditDepletion,
|
||||
]);
|
||||
|
||||
const formik = useFormik<
|
||||
@@ -2443,86 +2453,39 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stocks Table */}
|
||||
<Card
|
||||
title='Stok Persediaan'
|
||||
className={{
|
||||
wrapper: 'w-full mb-4 shadow',
|
||||
title: 'mb-4',
|
||||
}}
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<th>
|
||||
<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}`}>
|
||||
{/* Stocks Table - Only show if can edit stock or has data */}
|
||||
{(recordingRestriction.canEditStock ||
|
||||
(type === 'detail' && formik.values.stocks?.length > 0)) && (
|
||||
<Card
|
||||
title='Stok Persediaan'
|
||||
className={{
|
||||
wrapper: 'w-full mb-4 shadow',
|
||||
title: 'mb-4',
|
||||
}}
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td className='align-middle!'>
|
||||
<th>
|
||||
<CheckboxInput
|
||||
name={`stock-${idx}`}
|
||||
checked={selectedStocks.includes(idx)}
|
||||
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([...selectedStocks, idx]);
|
||||
} else {
|
||||
setSelectedStocks(
|
||||
selectedStocks.filter((i) => i !== idx)
|
||||
formik.values.stocks?.map((_, idx) => idx) ??
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
setSelectedStocks([]);
|
||||
}
|
||||
}}
|
||||
disabled={!recordingRestriction.canEditStock}
|
||||
@@ -2531,169 +2494,221 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
checkbox: 'checkbox checkbox-sm',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</th>
|
||||
)}
|
||||
<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>
|
||||
<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' && (
|
||||
<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>
|
||||
<th>Action</th>
|
||||
)}
|
||||
</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 && (
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.stocks?.map((stock, idx) => (
|
||||
<tr key={`stock-${idx}`}>
|
||||
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
|
||||
<td className='align-middle!'>
|
||||
<CheckboxInput
|
||||
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
|
||||
type='button'
|
||||
color='error'
|
||||
onClick={removeSelectedStocks}
|
||||
disabled={selectedStocks.length === 0}
|
||||
color='success'
|
||||
onClick={addStock}
|
||||
className='w-fit'
|
||||
disabled={!recordingRestriction.canEditStock}
|
||||
>
|
||||
<Icon icon='mdi:trash-can' width={24} height={24} />
|
||||
Hapus Terpilih ({selectedStocks.length})
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Stok
|
||||
</Button>
|
||||
)}
|
||||
<Tooltip
|
||||
content={
|
||||
!recordingRestriction.canEditStock
|
||||
? '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>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Transition Warning Banner -- MOVED UP -- */}
|
||||
{isTransitionPeriod && (
|
||||
|
||||
Reference in New Issue
Block a user