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