mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-170,174): add loading state and improve validation messages in GradingForm
This commit is contained in:
@@ -37,7 +37,6 @@ const RowOptionsMenu = ({
|
|||||||
approveClickHandler,
|
approveClickHandler,
|
||||||
rejectClickHandler,
|
rejectClickHandler,
|
||||||
isGradingCompleted,
|
isGradingCompleted,
|
||||||
hasConsumableEggs,
|
|
||||||
}: {
|
}: {
|
||||||
type: 'dropdown' | 'collapse';
|
type: 'dropdown' | 'collapse';
|
||||||
props: CellContext<Recording, unknown>;
|
props: CellContext<Recording, unknown>;
|
||||||
@@ -45,7 +44,6 @@ const RowOptionsMenu = ({
|
|||||||
approveClickHandler: () => void;
|
approveClickHandler: () => void;
|
||||||
rejectClickHandler: () => void;
|
rejectClickHandler: () => void;
|
||||||
isGradingCompleted: (recording: Recording) => boolean;
|
isGradingCompleted: (recording: Recording) => boolean;
|
||||||
hasConsumableEggs: (recording: Recording) => boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const isLayingCategory =
|
const isLayingCategory =
|
||||||
props.row.original.project_flock_category === 'LAYING';
|
props.row.original.project_flock_category === 'LAYING';
|
||||||
@@ -60,7 +58,6 @@ const RowOptionsMenu = ({
|
|||||||
|
|
||||||
const isApproved = isRecordingApproved(props.row.original);
|
const isApproved = isRecordingApproved(props.row.original);
|
||||||
const isGradingDone = isGradingCompleted(props.row.original);
|
const isGradingDone = isGradingCompleted(props.row.original);
|
||||||
const hasConsumable = hasConsumableEggs(props.row.original);
|
|
||||||
|
|
||||||
const getApprovalTooltip = () => {
|
const getApprovalTooltip = () => {
|
||||||
if (isLayingCategory && !isGradingDone) {
|
if (isLayingCategory && !isGradingDone) {
|
||||||
@@ -95,24 +92,9 @@ const RowOptionsMenu = ({
|
|||||||
<Button
|
<Button
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='info'
|
color='info'
|
||||||
className={`justify-start text-sm ${!hasConsumable ? 'cursor-not-allowed opacity-50' : ''}`}
|
className={`justify-start text-sm`}
|
||||||
onClick={(e) => {
|
href={`/production/recording/grading/add?recording_id=${props.row.original.id}`}
|
||||||
if (!hasConsumable) {
|
title={'Lanjut ke proses grading untuk telur konsumsi baik'}
|
||||||
e.preventDefault();
|
|
||||||
toast.error(
|
|
||||||
'Tidak bisa melakukan grading karena tidak ada Telur Konsumsi Baik'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
router.push(
|
|
||||||
`/production/recording/grading/add?recording_id=${props.row.original.id}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
title={
|
|
||||||
hasConsumable
|
|
||||||
? 'Lanjut ke proses grading untuk telur konsumsi baik'
|
|
||||||
: 'Tidak bisa melakukan grading karena tidak ada Telur Konsumsi Baik'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:egg' width={16} height={16} />
|
<Icon icon='material-symbols:egg' width={16} height={16} />
|
||||||
Grading
|
Grading
|
||||||
@@ -461,23 +443,6 @@ const RecordingTable = () => {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const hasConsumableEggs = useCallback((recording: Recording): boolean => {
|
|
||||||
if (!recording.eggs || recording.eggs.length === 0) return false;
|
|
||||||
|
|
||||||
return recording.eggs.some((egg) => {
|
|
||||||
if (!egg.product_warehouse || !egg.product_warehouse.product)
|
|
||||||
return false;
|
|
||||||
if (Number(egg.qty) <= 0) return false;
|
|
||||||
|
|
||||||
const productName = egg.product_warehouse.product.name.toLowerCase();
|
|
||||||
return (
|
|
||||||
productName.includes('konsumsi') &&
|
|
||||||
productName.includes('baik') &&
|
|
||||||
Number(egg.qty) > 0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const searchChangeHandler = useCallback(
|
const searchChangeHandler = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateFilter('search', e.target.value);
|
updateFilter('search', e.target.value);
|
||||||
@@ -910,7 +875,6 @@ const RecordingTable = () => {
|
|||||||
approveClickHandler={approveClickHandler}
|
approveClickHandler={approveClickHandler}
|
||||||
rejectClickHandler={rejectClickHandler}
|
rejectClickHandler={rejectClickHandler}
|
||||||
isGradingCompleted={isGradingCompleted}
|
isGradingCompleted={isGradingCompleted}
|
||||||
hasConsumableEggs={hasConsumableEggs}
|
|
||||||
/>
|
/>
|
||||||
</RowDropdownOptions>
|
</RowDropdownOptions>
|
||||||
)}
|
)}
|
||||||
@@ -924,7 +888,6 @@ const RecordingTable = () => {
|
|||||||
approveClickHandler={approveClickHandler}
|
approveClickHandler={approveClickHandler}
|
||||||
rejectClickHandler={rejectClickHandler}
|
rejectClickHandler={rejectClickHandler}
|
||||||
isGradingCompleted={isGradingCompleted}
|
isGradingCompleted={isGradingCompleted}
|
||||||
hasConsumableEggs={hasConsumableEggs}
|
|
||||||
/>
|
/>
|
||||||
</RowCollapseOptions>
|
</RowCollapseOptions>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -113,6 +113,11 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
const totalKonsumsiBaikEggs = konsumsiBaikEggData?.qty || 0;
|
const totalKonsumsiBaikEggs = konsumsiBaikEggData?.qty || 0;
|
||||||
const konsumsiBaikEggId = konsumsiBaikEggData?.id;
|
const konsumsiBaikEggId = konsumsiBaikEggData?.id;
|
||||||
|
|
||||||
|
const isDataLoading =
|
||||||
|
!recording ||
|
||||||
|
(totalKonsumsiBaikEggs === 0 &&
|
||||||
|
recording?.project_flock_category === 'LAYING');
|
||||||
|
|
||||||
// FORM HANDLERS
|
// FORM HANDLERS
|
||||||
const createGradingHandler = useCallback(
|
const createGradingHandler = useCallback(
|
||||||
async (payload: CreateGradingPayload) => {
|
async (payload: CreateGradingPayload) => {
|
||||||
@@ -256,7 +261,8 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
}, [formik.values.eggs_grading]);
|
}, [formik.values.eggs_grading]);
|
||||||
|
|
||||||
const isGradingExceedsAvailable = currentGradingTotal > totalKonsumsiBaikEggs;
|
const isGradingExceedsAvailable = currentGradingTotal > totalKonsumsiBaikEggs;
|
||||||
const isGradingIncomplete = currentGradingTotal < totalKonsumsiBaikEggs && totalKonsumsiBaikEggs > 0;
|
const isGradingIncomplete =
|
||||||
|
currentGradingTotal < totalKonsumsiBaikEggs && totalKonsumsiBaikEggs > 0;
|
||||||
const hasUserStartedGrading = currentGradingTotal > 0;
|
const hasUserStartedGrading = currentGradingTotal > 0;
|
||||||
|
|
||||||
// GRADING HANDLERS
|
// GRADING HANDLERS
|
||||||
@@ -349,6 +355,12 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
|
|
||||||
// EFFECTS
|
// EFFECTS
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isDataLoading) {
|
||||||
|
toast.dismiss('grading-exceeds');
|
||||||
|
toast.dismiss('grading-incomplete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isGradingExceedsAvailable && currentGradingTotal > 0) {
|
if (isGradingExceedsAvailable && currentGradingTotal > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`Total grading (${currentGradingTotal}) melebihi telur yang tersedia (${totalKonsumsiBaikEggs})!`,
|
`Total grading (${currentGradingTotal}) melebihi telur yang tersedia (${totalKonsumsiBaikEggs})!`,
|
||||||
@@ -371,7 +383,14 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
toast.dismiss('grading-exceeds');
|
toast.dismiss('grading-exceeds');
|
||||||
toast.dismiss('grading-incomplete');
|
toast.dismiss('grading-incomplete');
|
||||||
}
|
}
|
||||||
}, [isGradingExceedsAvailable, isGradingIncomplete, hasUserStartedGrading, currentGradingTotal, totalKonsumsiBaikEggs]);
|
}, [
|
||||||
|
isDataLoading,
|
||||||
|
isGradingExceedsAvailable,
|
||||||
|
isGradingIncomplete,
|
||||||
|
hasUserStartedGrading,
|
||||||
|
currentGradingTotal,
|
||||||
|
totalKonsumsiBaikEggs,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -570,11 +589,13 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
{/* Total Telur Konsumsi Baik Info */}
|
{/* Total Telur Konsumsi Baik Info */}
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg p-4 border-2 ${
|
className={`rounded-lg p-4 border-2 ${
|
||||||
isGradingExceedsAvailable
|
isDataLoading
|
||||||
? 'bg-red-50 border-red-200'
|
? 'bg-gray-50 border-gray-200'
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: isGradingExceedsAvailable
|
||||||
? 'bg-yellow-50 border-yellow-200'
|
? 'bg-red-50 border-red-200'
|
||||||
: 'bg-green-50 border-green-200'
|
: isGradingIncomplete && hasUserStartedGrading
|
||||||
|
? 'bg-yellow-50 border-yellow-200'
|
||||||
|
: 'bg-green-50 border-green-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-between mb-3'>
|
<div className='flex items-center justify-between mb-3'>
|
||||||
@@ -584,7 +605,11 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
</p>
|
</p>
|
||||||
<div className='flex items-baseline gap-2'>
|
<div className='flex items-baseline gap-2'>
|
||||||
<p className='text-2xl font-bold text-gray-900'>
|
<p className='text-2xl font-bold text-gray-900'>
|
||||||
{totalKonsumsiBaikEggs}{' '}
|
{isDataLoading ? (
|
||||||
|
<span className='loading loading-spinner loading-sm'></span>
|
||||||
|
) : (
|
||||||
|
totalKonsumsiBaikEggs
|
||||||
|
)}{' '}
|
||||||
<span className='text-sm text-gray-500 font-medium'>
|
<span className='text-sm text-gray-500 font-medium'>
|
||||||
telur
|
telur
|
||||||
</span>
|
</span>
|
||||||
@@ -593,29 +618,35 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`rounded-full p-2 ${
|
className={`rounded-full p-2 ${
|
||||||
isGradingExceedsAvailable
|
isDataLoading
|
||||||
? 'bg-red-100'
|
? 'bg-gray-100'
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: isGradingExceedsAvailable
|
||||||
? 'bg-yellow-100'
|
? 'bg-red-100'
|
||||||
: 'bg-green-100'
|
: isGradingIncomplete && hasUserStartedGrading
|
||||||
|
? 'bg-yellow-100'
|
||||||
|
: 'bg-green-100'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon={
|
icon={
|
||||||
isGradingExceedsAvailable
|
isDataLoading
|
||||||
? 'material-symbols:error'
|
? 'material-symbols:hourglass-empty'
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: isGradingExceedsAvailable
|
||||||
? 'material-symbols:warning'
|
? 'material-symbols:error'
|
||||||
: 'material-symbols:check-circle'
|
: isGradingIncomplete && hasUserStartedGrading
|
||||||
|
? 'material-symbols:warning'
|
||||||
|
: 'material-symbols:check-circle'
|
||||||
}
|
}
|
||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
className={
|
className={
|
||||||
isGradingExceedsAvailable
|
isDataLoading
|
||||||
? 'text-red-600'
|
? 'text-gray-500'
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: isGradingExceedsAvailable
|
||||||
? 'text-yellow-600'
|
? 'text-red-600'
|
||||||
: 'text-green-600'
|
: isGradingIncomplete && hasUserStartedGrading
|
||||||
|
? 'text-yellow-600'
|
||||||
|
: 'text-green-600'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -627,31 +658,41 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
<span className='text-gray-600'>Total yang digrading:</span>
|
<span className='text-gray-600'>Total yang digrading:</span>
|
||||||
<span
|
<span
|
||||||
className={`font-semibold ${
|
className={`font-semibold ${
|
||||||
isGradingExceedsAvailable
|
isDataLoading
|
||||||
? 'text-red-600'
|
? 'text-gray-500'
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: isGradingExceedsAvailable
|
||||||
? 'text-yellow-600'
|
? 'text-red-600'
|
||||||
: 'text-green-600'
|
: isGradingIncomplete && hasUserStartedGrading
|
||||||
|
? 'text-yellow-600'
|
||||||
|
: 'text-green-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{currentGradingTotal} / {totalKonsumsiBaikEggs}
|
{isDataLoading ? (
|
||||||
|
<span className='loading loading-spinner loading-xs'></span>
|
||||||
|
) : (
|
||||||
|
`${currentGradingTotal} / ${totalKonsumsiBaikEggs}`
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full bg-gray-200 rounded-full h-2 overflow-hidden'>
|
<div className='w-full bg-gray-200 rounded-full h-2 overflow-hidden'>
|
||||||
<div
|
<div
|
||||||
className={`h-full transition-all duration-300 ${
|
className={`h-full transition-all duration-300 ${
|
||||||
isGradingExceedsAvailable
|
isDataLoading
|
||||||
? 'bg-red-500'
|
? 'bg-gray-400'
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: isGradingExceedsAvailable
|
||||||
? 'bg-yellow-500'
|
? 'bg-red-500'
|
||||||
: 'bg-green-500'
|
: isGradingIncomplete && hasUserStartedGrading
|
||||||
|
? 'bg-yellow-500'
|
||||||
|
: 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.min((currentGradingTotal / totalKonsumsiBaikEggs) * 100, 100)}%`,
|
width: isDataLoading
|
||||||
|
? '0%'
|
||||||
|
: `${Math.min((currentGradingTotal / totalKonsumsiBaikEggs) * 100, 100)}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isGradingExceedsAvailable && (
|
{!isDataLoading && isGradingExceedsAvailable && (
|
||||||
<div className='flex items-center gap-1 text-xs text-red-600 mt-1'>
|
<div className='flex items-center gap-1 text-xs text-red-600 mt-1'>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:warning'
|
icon='material-symbols:warning'
|
||||||
@@ -661,14 +702,28 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
<span>Melebihi batas tersedia</span>
|
<span>Melebihi batas tersedia</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isGradingIncomplete && hasUserStartedGrading && (
|
{!isDataLoading &&
|
||||||
<div className='flex items-center gap-1 text-xs text-yellow-600 mt-1'>
|
isGradingIncomplete &&
|
||||||
|
hasUserStartedGrading && (
|
||||||
|
<div className='flex items-center gap-1 text-xs text-yellow-600 mt-1'>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:info'
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
Grading belum lengkap, semua telur harus digrading
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isDataLoading && (
|
||||||
|
<div className='flex items-center gap-1 text-xs text-gray-500 mt-1'>
|
||||||
<Icon
|
<Icon
|
||||||
icon='material-symbols:info'
|
icon='material-symbols:hourglass-empty'
|
||||||
width={12}
|
width={12}
|
||||||
height={12}
|
height={12}
|
||||||
/>
|
/>
|
||||||
<span>Grading belum lengkap, semua telur harus digrading</span>
|
<span>Memuat data telur konsumsi baik...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -813,14 +868,18 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
isError={
|
isError={
|
||||||
isRepeaterInputError('eggs_grading', 'qty', idx)
|
isRepeaterInputError('eggs_grading', 'qty', idx)
|
||||||
.isError ||
|
.isError ||
|
||||||
(isGradingExceedsAvailable || (isGradingIncomplete && hasUserStartedGrading))
|
(!isDataLoading &&
|
||||||
|
(isGradingExceedsAvailable ||
|
||||||
|
(isGradingIncomplete && hasUserStartedGrading)))
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError('eggs_grading', 'qty', idx)
|
isRepeaterInputError('eggs_grading', 'qty', idx)
|
||||||
.errorMessage ||
|
.errorMessage ||
|
||||||
(isGradingExceedsAvailable
|
(!isDataLoading && isGradingExceedsAvailable
|
||||||
? `Total grading melebihi telur yang tersedia (${totalKonsumsiBaikEggs})`
|
? `Total grading melebihi telur yang tersedia (${totalKonsumsiBaikEggs})`
|
||||||
: isGradingIncomplete && hasUserStartedGrading
|
: !isDataLoading &&
|
||||||
|
isGradingIncomplete &&
|
||||||
|
hasUserStartedGrading
|
||||||
? `Total grading (${currentGradingTotal}) harus sama dengan total telur konsumsi baik (${totalKonsumsiBaikEggs})`
|
? `Total grading (${currentGradingTotal}) harus sama dengan total telur konsumsi baik (${totalKonsumsiBaikEggs})`
|
||||||
: undefined)
|
: undefined)
|
||||||
}
|
}
|
||||||
@@ -943,6 +1002,7 @@ const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => {
|
|||||||
disabled={
|
disabled={
|
||||||
!formik.isValid ||
|
!formik.isValid ||
|
||||||
formik.isSubmitting ||
|
formik.isSubmitting ||
|
||||||
|
isDataLoading ||
|
||||||
isGradingExceedsAvailable ||
|
isGradingExceedsAvailable ||
|
||||||
(isGradingIncomplete && hasUserStartedGrading)
|
(isGradingIncomplete && hasUserStartedGrading)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user