Improve the frontend modals
This commit is contained in:
@@ -76,6 +76,12 @@ export interface EditViewModalProps {
|
||||
totalSteps?: number; // Total steps in workflow
|
||||
validationErrors?: Record<string, string>; // Field validation errors
|
||||
onValidationError?: (errors: Record<string, string>) => void; // Validation error handler
|
||||
|
||||
// Wait-for-refetch support (Option A approach)
|
||||
waitForRefetch?: boolean; // Enable wait-for-refetch behavior after save
|
||||
isRefetching?: boolean; // External refetch state (from React Query)
|
||||
onSaveComplete?: () => Promise<void>; // Async callback for triggering refetch
|
||||
refetchTimeout?: number; // Timeout in ms for refetch (default: 3000)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,9 +345,16 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
totalSteps,
|
||||
validationErrors = {},
|
||||
onValidationError,
|
||||
// Wait-for-refetch support
|
||||
waitForRefetch = false,
|
||||
isRefetching = false,
|
||||
onSaveComplete,
|
||||
refetchTimeout = 3000,
|
||||
}) => {
|
||||
const { t } = useTranslation(['common']);
|
||||
const StatusIcon = statusIndicator?.icon;
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const [isWaitingForRefetch, setIsWaitingForRefetch] = React.useState(false);
|
||||
|
||||
const handleEdit = () => {
|
||||
if (onModeChange) {
|
||||
@@ -352,11 +365,59 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (onSave) {
|
||||
if (!onSave) return;
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
|
||||
// Execute the save mutation
|
||||
await onSave();
|
||||
}
|
||||
if (onModeChange) {
|
||||
onModeChange('view');
|
||||
|
||||
// If waitForRefetch is enabled, wait for data to refresh
|
||||
if (waitForRefetch && onSaveComplete) {
|
||||
setIsWaitingForRefetch(true);
|
||||
|
||||
// Trigger the refetch
|
||||
await onSaveComplete();
|
||||
|
||||
// Wait for isRefetching to become true then false, or timeout
|
||||
const startTime = Date.now();
|
||||
const checkRefetch = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
// Timeout reached
|
||||
if (elapsed >= refetchTimeout) {
|
||||
clearInterval(interval);
|
||||
console.warn('Refetch timeout reached, proceeding anyway');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Refetch completed (was true, now false)
|
||||
if (!isRefetching) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
||||
await checkRefetch();
|
||||
setIsWaitingForRefetch(false);
|
||||
}
|
||||
|
||||
// Switch to view mode after save (and optional refetch) completes
|
||||
if (onModeChange) {
|
||||
onModeChange('view');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving:', error);
|
||||
// Don't switch mode on error
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
setIsWaitingForRefetch(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -371,30 +432,38 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
|
||||
// Default actions based on mode
|
||||
const defaultActions: EditViewModalAction[] = [];
|
||||
const isProcessing = loading || isSaving || isWaitingForRefetch;
|
||||
|
||||
if (showDefaultActions) {
|
||||
if (mode === 'view') {
|
||||
defaultActions.push({
|
||||
label: t('common:modals.actions.edit', 'Editar'),
|
||||
icon: Edit,
|
||||
variant: 'primary',
|
||||
onClick: handleEdit,
|
||||
disabled: loading,
|
||||
});
|
||||
defaultActions.push(
|
||||
{
|
||||
label: t('common:modals.actions.cancel', 'Cancelar'),
|
||||
variant: 'outline',
|
||||
onClick: onClose,
|
||||
disabled: isProcessing,
|
||||
},
|
||||
{
|
||||
label: t('common:modals.actions.edit', 'Editar'),
|
||||
variant: 'primary',
|
||||
onClick: handleEdit,
|
||||
disabled: isProcessing,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
defaultActions.push(
|
||||
{
|
||||
label: t('common:modals.actions.cancel', 'Cancelar'),
|
||||
variant: 'outline',
|
||||
onClick: handleCancel,
|
||||
disabled: loading,
|
||||
disabled: isProcessing,
|
||||
},
|
||||
{
|
||||
label: t('common:modals.actions.save', 'Guardar'),
|
||||
variant: 'primary',
|
||||
onClick: handleSave,
|
||||
disabled: loading,
|
||||
loading: loading,
|
||||
disabled: isProcessing,
|
||||
loading: isProcessing,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -469,8 +538,8 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size={size}
|
||||
closeOnOverlayClick={!loading}
|
||||
closeOnEscape={!loading}
|
||||
closeOnOverlayClick={!isProcessing}
|
||||
closeOnEscape={!isProcessing}
|
||||
showCloseButton={false}
|
||||
>
|
||||
<ModalHeader
|
||||
@@ -479,8 +548,13 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
{/* Status indicator */}
|
||||
{statusIndicator && (
|
||||
<div
|
||||
className="flex-shrink-0 p-2 rounded-lg"
|
||||
style={{ backgroundColor: `${statusIndicator.color}15` }}
|
||||
className={`flex-shrink-0 p-2 rounded-lg transition-all ${
|
||||
statusIndicator.isCritical ? 'ring-2 ring-offset-2' : ''
|
||||
} ${statusIndicator.isHighlight ? 'shadow-lg' : ''}`}
|
||||
style={{
|
||||
backgroundColor: `${statusIndicator.color}15`,
|
||||
...(statusIndicator.isCritical && { ringColor: statusIndicator.color })
|
||||
}}
|
||||
>
|
||||
{StatusIcon && (
|
||||
<StatusIcon
|
||||
@@ -491,27 +565,13 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title and status */}
|
||||
<div>
|
||||
{/* Title and subtitle */}
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
|
||||
{title}
|
||||
</h2>
|
||||
{statusIndicator && (
|
||||
<div
|
||||
className="text-sm font-medium mt-1"
|
||||
style={{ color: statusIndicator.color }}
|
||||
>
|
||||
{statusIndicator.text}
|
||||
{statusIndicator.isCritical && (
|
||||
<span className="ml-2 text-xs">⚠️</span>
|
||||
)}
|
||||
{statusIndicator.isHighlight && (
|
||||
<span className="ml-2 text-xs">⭐</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{subtitle && (
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-1">
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-0.5">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
@@ -529,11 +589,17 @@ export const EditViewModal: React.FC<EditViewModalProps> = ({
|
||||
{renderTopActions()}
|
||||
|
||||
<ModalBody>
|
||||
{loading && (
|
||||
{(loading || isSaving || isWaitingForRefetch) && (
|
||||
<div className="absolute inset-0 bg-[var(--bg-primary)]/80 backdrop-blur-sm flex items-center justify-center z-10">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[var(--color-primary)]"></div>
|
||||
<span className="text-[var(--text-secondary)]">{t('common:modals.loading', 'Cargando...')}</span>
|
||||
<span className="text-[var(--text-secondary)]">
|
||||
{isWaitingForRefetch
|
||||
? t('common:modals.refreshing', 'Actualizando datos...')
|
||||
: isSaving
|
||||
? t('common:modals.saving', 'Guardando...')
|
||||
: t('common:modals.loading', 'Cargando...')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user