diff --git a/src/modules/registratura/components/registry-entry-detail.tsx b/src/modules/registratura/components/registry-entry-detail.tsx index 3d5f9bd..ccc07bc 100644 --- a/src/modules/registratura/components/registry-entry-detail.tsx +++ b/src/modules/registratura/components/registry-entry-detail.tsx @@ -166,11 +166,21 @@ export function RegistryEntryDetail({ const [previewIndex, setPreviewIndex] = useState(null); const [copiedPath, setCopiedPath] = useState(null); const [monitorConfigOpen, setMonitorConfigOpen] = useState(false); + const [monitorEditMode, setMonitorEditMode] = useState(false); - // Auto-detect if recipient matches a known authority + // Authority for existing tracking or auto-detected from recipient + const trackingAuthority = useMemo(() => { + if (!entry) return undefined; + if (entry.externalStatusTracking) { + return getAuthority(entry.externalStatusTracking.authorityId) ?? undefined; + } + return undefined; + }, [entry]); + + // Auto-detect if recipient matches a known authority (only when no tracking) const matchedAuthority = useMemo(() => { if (!entry) return undefined; - if (entry.externalStatusTracking?.active) return undefined; + if (entry.externalStatusTracking) return undefined; if (!entry.recipientRegNumber) return undefined; return findAuthorityForContact(entry.recipient); }, [entry]); @@ -757,14 +767,47 @@ export function RegistryEntryDetail({ )} {/* ── External status monitoring ── */} - {entry.externalStatusTracking?.active && ( - + {entry.externalStatusTracking && ( + <> + { + setMonitorEditMode(true); + setMonitorConfigOpen(true); + }} + /> + {trackingAuthority && ( + { + setMonitorConfigOpen(open); + if (!open) setMonitorEditMode(false); + }} + entry={entry} + authority={trackingAuthority} + editMode + onActivate={async (tracking) => { + try { + await fetch("/api/registratura", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + id: entry.id, + updates: { externalStatusTracking: tracking }, + }), + }); + window.location.reload(); + } catch { + // Best effort + } + }} + /> + )} + )} {/* ── Auto-detect: suggest monitoring activation ── */} - {matchedAuthority && !entry.externalStatusTracking?.active && ( + {matchedAuthority && !entry.externalStatusTracking && (
@@ -780,7 +823,10 @@ export function RegistryEntryDetail({ variant="outline" size="sm" className="mt-2 h-6 text-xs" - onClick={() => setMonitorConfigOpen(true)} + onClick={() => { + setMonitorEditMode(false); + setMonitorConfigOpen(true); + }} > Configureaza monitorizarea @@ -788,12 +834,11 @@ export function RegistryEntryDetail({
{ - // Save tracking to entry via API try { await fetch("/api/registratura", { method: "PUT", @@ -892,26 +937,54 @@ const STATUS_COLORS: Record = { necunoscut: "bg-muted text-muted-foreground", }; -function ExternalStatusSection({ entry }: { entry: RegistryEntry }) { +function ExternalStatusSection({ + entry, + onEdit, +}: { + entry: RegistryEntry; + onEdit: () => void; +}) { const tracking = entry.externalStatusTracking; if (!tracking) return null; const [checking, setChecking] = useState(false); + const [checkResult, setCheckResult] = useState<{ + changed: boolean; + error: string | null; + newStatus?: string; + } | null>(null); const [showHistory, setShowHistory] = useState(false); + const [liveTracking, setLiveTracking] = useState(tracking); const authority = getAuthority(tracking.authorityId); const handleManualCheck = useCallback(async () => { setChecking(true); + setCheckResult(null); try { - await fetch("/api/registratura/status-check/single", { + const res = await fetch("/api/registratura/status-check/single", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ entryId: entry.id }), }); - // Reload page to show updated status - window.location.reload(); - } catch { - // Ignore — user will see if it worked on reload + const data = (await res.json()) as { + changed: boolean; + error: string | null; + newStatus?: string; + tracking?: typeof tracking; + }; + setCheckResult({ + changed: data.changed, + error: data.error, + newStatus: data.newStatus, + }); + if (data.tracking) { + setLiveTracking(data.tracking); + } + } catch (err) { + setCheckResult({ + changed: false, + error: err instanceof Error ? err.message : "Eroare conexiune", + }); } finally { setChecking(false); } @@ -928,80 +1001,121 @@ function ExternalStatusSection({ entry }: { entry: RegistryEntry }) { return `acum ${days}z`; }; + const t = liveTracking; + return (

Monitorizare status extern + {!t.active && ( + (oprita) + )}

- +
+ + {t.active && ( + + )} +
+ {/* Inline check result */} + {checkResult && ( +
+ {checkResult.error + ? `Eroare: ${checkResult.error}` + : checkResult.changed + ? `Status actualizat: ${EXTERNAL_STATUS_LABELS[checkResult.newStatus as ExternalDocStatus] ?? checkResult.newStatus}` + : "Nicio schimbare detectata"} +
+ )} +
{/* Authority + status badge */}
- {authority?.name ?? tracking.authorityId} + {authority?.name ?? t.authorityId} - + - {EXTERNAL_STATUS_LABELS[tracking.semanticStatus]} + {EXTERNAL_STATUS_LABELS[t.semanticStatus]}
{/* Last check time */} - {tracking.lastCheckAt && ( + {t.lastCheckAt && (

- Ultima verificare: {relativeTime(tracking.lastCheckAt)} + Ultima verificare: {relativeTime(t.lastCheckAt)}

)} {/* Error state */} - {tracking.lastError && ( -

{tracking.lastError}

+ {t.lastError && ( +

{t.lastError}

)} {/* Latest status row */} - {tracking.lastStatusRow && ( + {t.lastStatusRow && (
Sursa:{" "} - {tracking.lastStatusRow.sursa} + {t.lastStatusRow.sursa} {" "} - {tracking.lastStatusRow.destinatie} + {t.lastStatusRow.destinatie}
- {tracking.lastStatusRow.modRezolvare && ( + {t.lastStatusRow.modRezolvare && (
Rezolvare:{" "} - {tracking.lastStatusRow.modRezolvare} + {t.lastStatusRow.modRezolvare}
)} - {tracking.lastStatusRow.comentarii && ( + {t.lastStatusRow.comentarii && (
- {tracking.lastStatusRow.comentarii} + {t.lastStatusRow.comentarii}
)}
- {tracking.lastStatusRow.dataVenire} {tracking.lastStatusRow.oraVenire} + {t.lastStatusRow.dataVenire} {t.lastStatusRow.oraVenire}
)} + {/* Tracking config info */} +
+ Nr: {t.regNumber} | Data: {t.regDate} | Deponent: {t.petitionerName} +
+ {/* History toggle */} - {tracking.history.length > 0 && ( + {t.history.length > 0 && (
{showHistory && (
- {[...tracking.history].reverse().map((change, i) => ( + {[...t.history].reverse().map((change, i) => (
void; + /** When true, pre-fills from existing tracking data for editing */ + editMode?: boolean; } export function StatusMonitorConfig({ @@ -37,30 +39,35 @@ export function StatusMonitorConfig({ entry, authority, onActivate, + editMode, }: StatusMonitorConfigProps) { + const existing = entry.externalStatusTracking; const [petitionerName, setPetitionerName] = useState(""); const [regNumber, setRegNumber] = useState( entry.recipientRegNumber ?? "", ); const [regDate, setRegDate] = useState(""); - // Convert YYYY-MM-DD to dd.mm.yyyy + // Pre-fill: edit mode uses existing tracking, otherwise entry fields useEffect(() => { - if (entry.recipientRegDate) { - const parts = entry.recipientRegDate.split("-"); - if (parts.length === 3) { - setRegDate(`${parts[2]}.${parts[1]}.${parts[0]}`); + if (editMode && existing) { + setPetitionerName(existing.petitionerName); + setRegNumber(existing.regNumber); + setRegDate(existing.regDate); + } else { + setRegNumber(entry.recipientRegNumber ?? ""); + if (entry.recipientRegDate) { + const parts = entry.recipientRegDate.split("-"); + if (parts.length === 3) { + setRegDate(`${parts[2]}.${parts[1]}.${parts[0]}`); + } } + const saved = localStorage.getItem( + `status-monitor-petitioner:${authority.id}`, + ); + if (saved) setPetitionerName(saved); } - }, [entry.recipientRegDate]); - - // Load saved petitioner name from localStorage - useEffect(() => { - const saved = localStorage.getItem( - `status-monitor-petitioner:${authority.id}`, - ); - if (saved) setPetitionerName(saved); - }, [authority.id]); + }, [editMode, existing, entry.recipientRegNumber, entry.recipientRegDate, authority.id]); const canActivate = petitionerName.trim().length >= 3 && @@ -74,19 +81,28 @@ export function StatusMonitorConfig({ petitionerName.trim(), ); - const tracking: ExternalStatusTracking = { - authorityId: authority.id, - petitionerName: petitionerName.trim(), - regNumber: regNumber.trim(), - regDate: regDate.trim(), - lastCheckAt: null, - lastStatusRow: null, - statusHash: "", - semanticStatus: "necunoscut", - history: [], - active: true, - lastError: null, - }; + const tracking: ExternalStatusTracking = editMode && existing + ? { + ...existing, + petitionerName: petitionerName.trim(), + regNumber: regNumber.trim(), + regDate: regDate.trim(), + active: true, + lastError: null, + } + : { + authorityId: authority.id, + petitionerName: petitionerName.trim(), + regNumber: regNumber.trim(), + regDate: regDate.trim(), + lastCheckAt: null, + lastStatusRow: null, + statusHash: "", + semanticStatus: "necunoscut", + history: [], + active: true, + lastError: null, + }; onActivate(tracking); onOpenChange(false); @@ -98,11 +114,12 @@ export function StatusMonitorConfig({ - Monitorizare status extern + {editMode ? "Modifica monitorizarea" : "Monitorizare status extern"} - {authority.name} suporta verificarea automata a statusului. - Configureaza datele de mai jos pentru a activa monitorizarea. + {editMode + ? "Modifica datele de monitorizare. Istoricul se pastreaza." + : `${authority.name} suporta verificarea automata a statusului. Configureaza datele de mai jos pentru a activa monitorizarea.`} @@ -152,7 +169,7 @@ export function StatusMonitorConfig({ Anuleaza diff --git a/src/modules/registratura/services/status-check-service.ts b/src/modules/registratura/services/status-check-service.ts index 9cb16c8..56ab5cc 100644 --- a/src/modules/registratura/services/status-check-service.ts +++ b/src/modules/registratura/services/status-check-service.ts @@ -229,6 +229,14 @@ export async function runStatusCheck( tracking.statusHash = newHash; tracking.semanticStatus = checkResult.newStatus; + // Auto-deactivate monitoring when resolved or rejected + if ( + checkResult.newStatus === "solutionat" || + checkResult.newStatus === "respins" + ) { + tracking.active = false; + } + // Cap history at 50 tracking.history.push(change); if (tracking.history.length > 50) { @@ -436,6 +444,15 @@ export async function checkSingleEntry( tracking.lastStatusRow = result.newRow; tracking.statusHash = newHash; tracking.semanticStatus = result.newStatus; + + // Auto-deactivate monitoring when resolved or rejected + if ( + result.newStatus === "solutionat" || + result.newStatus === "respins" + ) { + tracking.active = false; + } + tracking.history.push(change); if (tracking.history.length > 50) { tracking.history = tracking.history.slice(-50);