diff --git a/scripts/f6-1-apply-cancel-ux.py b/scripts/f6-1-apply-cancel-ux.py new file mode 100644 index 0000000..c0e6a46 --- /dev/null +++ b/scripts/f6-1-apply-cancel-ux.py @@ -0,0 +1,77 @@ +from pathlib import Path +import re + +path = Path('web-v2/src/pages/InspectionVisitEditorF4Page.tsx') +text = path.read_text() + +state_before = " const [success, setSuccess] = useState('');\n" +state_after = """ const [success, setSuccess] = useState(''); + const [showCancelForm, setShowCancelForm] = useState(false); + const [cancelReason, setCancelReason] = useState(''); +""" +if text.count(state_before) != 1: + raise SystemExit('Could not locate success state exactly once') +text = text.replace(state_before, state_after) + +lifecycle_pattern = re.compile( + r" const lifecycleAction = async \(action: 'PLAN' \| 'UNPLAN' \| 'CANCEL'\) => \{.*?\n \};\n\n if \(loading\)", + re.S, +) +lifecycle_replacement = """ const lifecycleAction = async (action: 'PLAN' | 'UNPLAN') => { + if (!id) return; + setBusy(true); setError(''); setSuccess(''); + try { + let updated: InspectionVisit; + if (action === 'PLAN') { + updated = await planInspectionVisit(id); + setSuccess('Inspección planificada. Ya puede iniciarse desde la APK.'); + } else { + updated = await unplanInspectionVisit(id); + setSuccess('La Inspección volvió a borrador para corregir la planificación.'); + } + applyVisit(updated); + } catch (requestError) { setError(errorMessage(requestError)); } + finally { setBusy(false); } + }; + + const cancelCurrentVisit = async (event: FormEvent) => { + event.preventDefault(); + if (!id) return; + const reason = cancelReason.trim(); + if (reason.length < 10) { + setError('El motivo de cancelación debe tener al menos 10 caracteres.'); + return; + } + if (reason.length > 500) { + setError('El motivo de cancelación no puede superar los 500 caracteres.'); + return; + } + setBusy(true); setError(''); setSuccess(''); + try { + const updated = await cancelInspectionVisit(id, reason); + applyVisit(updated); + setCancelReason(''); + setShowCancelForm(false); + setSuccess('Inspección cancelada. El motivo quedó registrado y auditado.'); + } catch (requestError) { setError(errorMessage(requestError)); } + finally { setBusy(false); } + }; + + if (loading)""" +text, count = lifecycle_pattern.subn(lifecycle_replacement, text, count=1) +if count != 1: + raise SystemExit(f'Lifecycle block replacements: {count}') + +actions_pattern = re.compile( + r" \{visit &&
.*?
\}\n", + re.S, +) +actions_replacement = """ {visit &&
Flujo de la Inspección

La oficina planifica. El inicio y cierre operativo se realizan desde la APK. Una Inspección no puede cerrarse hasta que todas sus Actas estén selladas.

{canManage && visit.status === 'DRAFT' && }{canManage && visit.status === 'PLANNED' && }{canManage && ['DRAFT', 'PLANNED', 'IN_PROGRESS'].includes(visit.status) && }
} + + {visit && showCancelForm && ['DRAFT', 'PLANNED', 'IN_PROGRESS'].includes(visit.status) &&
CANCELACIÓN

Cancelar inspección

La cancelación es definitiva y queda auditada. No se permite si la Inspección ya tiene un Acta sellada.