Improve the frontend modals

This commit is contained in:
Urtzi Alfaro
2025-10-27 16:33:26 +01:00
parent 61376b7a9f
commit 858d985c92
143 changed files with 9289 additions and 2306 deletions

View File

@@ -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>
)}