← Files VeraARCHIVED FILE
modules/registro-imprese-sari/assets/registro-imprese-sari-review-widget.html
45.3 KB · Oct 5, 2026 · 18:29 UTC
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Revisione Registro Imprese e SARI</title>
<style>
:root {
color-scheme: light;
--ink: #1d2927;
--muted: #64716e;
--line: #d9e1de;
--paper: #f7f9f8;
--white: #fff;
--teal: #145f5a;
--teal-soft: #e7f2f0;
--amber: #9b641e;
--amber-soft: #fbf3e5;
--red: #91463d;
--red-soft: #f9ece9;
--green: #31694e;
--green-soft: #eaf4ed;
--serif: "Iowan Old Style", Baskerville, "Times New Roman", serif;
--sans: "Avenir Next", "Helvetica Neue", Arial, sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; background: var(--white); color: var(--ink); }
body { font: 15px/1.45 var(--sans); }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
button:focus-visible, input:focus-visible, textarea:focus-visible {
outline: 2px solid var(--teal); outline-offset: 2px;
}
.shell { min-height: 100vh; display: grid; grid-template-rows: auto auto auto minmax(0, 1fr) auto; }
.case-summary { margin: 0; padding: 16px clamp(18px, 3vw, 38px); border-bottom: 1px solid var(--line); max-width: 100%; }
.case-summary[hidden] { display: none; }
.topbar {
display: flex; align-items: center; justify-content: space-between; gap: 18px;
padding: 17px clamp(18px, 3vw, 38px); border-bottom: 1px solid var(--line);
}
.identity { display: flex; align-items: center; gap: 12px; min-width: 0; }
.mark {
width: 40px; height: 40px; display: grid; place-items: center; flex: none;
border-radius: 9px; background: var(--teal); color: white; font: 700 11px/1 var(--serif);
letter-spacing: .08em;
}
.eyebrow { margin: 0 0 2px; color: var(--muted); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
h1 { margin: 0; font: 600 21px/1.18 var(--serif); }
.actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; }
.button {
min-height: 36px; border: 1px solid #bcc9c5; border-radius: 7px; padding: 7px 12px;
background: white; font-weight: 650; font-size: 13px;
}
.button:hover { background: var(--paper); }
.button.primary { border-color: var(--teal); background: var(--teal); color: white; }
.button:disabled { cursor: not-allowed; opacity: .45; }
.notice {
display: grid; grid-template-columns: repeat(4, minmax(100px, 1fr)) minmax(240px, 1.5fr);
border-bottom: 1px solid var(--line); background: var(--paper); padding: 0 clamp(18px, 3vw, 38px);
}
.metric { padding: 11px 18px 12px 0; }
.metric + .metric { padding-left: 18px; border-left: 1px solid var(--line); }
.metric span { display: block; color: var(--muted); font-size: 10px; letter-spacing: .09em; text-transform: uppercase; }
.metric strong { display: block; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.message { align-self: center; padding: 10px 0 10px 20px; color: var(--muted); font-size: 12px; text-align: right; }
.message.ok { color: var(--green); }
.message.error { color: var(--red); }
.workspace { display: grid; grid-template-columns: minmax(310px, 38%) minmax(0, 1fr); min-height: 560px; }
.queue { min-width: 0; border-right: 1px solid var(--line); background: var(--paper); }
.queue-head { padding: 18px 20px 13px; border-bottom: 1px solid var(--line); }
.queue-title { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
h2 { margin: 0; font-size: 14px; }
.count { color: var(--muted); font-size: 12px; }
.search { width: 100%; min-height: 38px; border: 1px solid #bcc9c5; border-radius: 6px; padding: 8px 10px; background: white; }
.filters { display: flex; gap: 5px; overflow-x: auto; padding-top: 9px; }
.filter { flex: none; border: 1px solid transparent; border-radius: 5px; padding: 4px 7px; background: transparent; color: var(--muted); font-size: 11px; }
.filter.active { border-color: #bcc9c5; background: white; color: var(--ink); }
.rows { max-height: calc(100vh - 224px); overflow: auto; }
.row {
width: 100%; display: grid; grid-template-columns: 9px minmax(0, 1fr) auto; gap: 10px;
border: 0; border-bottom: 1px solid var(--line); padding: 13px 20px; background: transparent; text-align: left;
}
.row:hover { background: #eef3f1; }
.row.selected { background: white; box-shadow: inset 3px 0 var(--teal); }
.dot { width: 8px; height: 8px; margin-top: 5px; border: 1px solid #96a49f; border-radius: 50%; }
.dot.accepted { border-color: var(--green); background: var(--green); }
.dot.rejected { border-color: var(--red); background: var(--red); }
.dot.edited_revision_required, .dot.needs_clarification, .dot.needs_documents { border-color: var(--amber); background: var(--amber); }
.row-title { display: block; overflow: hidden; text-overflow: ellipsis; font-weight: 650; font-size: 13px; }
.row-meta { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; }
.recommended { color: var(--muted); font-size: 10px; text-transform: uppercase; }
.empty { padding: 34px 20px; color: var(--muted); text-align: center; }
.detail { min-width: 0; overflow: auto; padding: 25px clamp(22px, 4vw, 48px) 42px; }
.detail-head { display: flex; justify-content: space-between; gap: 22px; padding-bottom: 17px; border-bottom: 1px solid var(--line); }
.type { margin: 0 0 4px; color: var(--teal); font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
h3 { max-width: 35ch; margin: 0; font: 600 23px/1.22 var(--serif); }
.pill { flex: none; align-self: flex-start; border: 1px solid #bcc9c5; border-radius: 999px; padding: 4px 8px; background: var(--paper); color: var(--muted); font-size: 10px; }
.section { padding: 19px 0; border-bottom: 1px solid var(--line); }
.section:last-child { border-bottom: 0; }
.section h4 { margin: 0 0 10px; color: var(--muted); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; }
dl { display: grid; grid-template-columns: minmax(120px, 34%) 1fr; margin: 0; }
dt, dd { margin: 0; padding: 7px 0; border-top: 1px solid var(--line); overflow-wrap: anywhere; }
dt { color: var(--muted); font-size: 12px; }
dd { font-size: 13px; }
.decision-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.decision-action { border: 1px solid #bcc9c5; border-radius: 6px; padding: 7px 9px; background: white; font-size: 12px; }
.decision-action.selected { border-color: var(--teal); background: var(--teal-soft); color: var(--teal); font-weight: 700; }
.field { display: grid; gap: 5px; margin-top: 12px; }
.field label { color: var(--muted); font-size: 11px; font-weight: 650; }
textarea { width: 100%; min-height: 72px; resize: vertical; border: 1px solid #bcc9c5; border-radius: 6px; padding: 8px 9px; background: white; }
.impact { margin-top: 11px; padding: 9px 10px; border: 1px solid var(--line); background: var(--paper); color: var(--muted); font-size: 11px; }
.footer { padding: 14px clamp(18px, 3vw, 38px); border-top: 1px solid var(--line); background: var(--paper); color: var(--muted); font-size: 11px; }
@media (max-width: 820px) {
.workspace { grid-template-columns: 1fr; }
.queue { border-right: 0; border-bottom: 1px solid var(--line); }
.rows { max-height: 340px; }
.notice { grid-template-columns: repeat(2, 1fr); }
.message { grid-column: 1 / -1; text-align: left; padding-left: 0; }
}
@media (max-width: 580px) {
.topbar { align-items: flex-start; padding: 14px 15px; }
.actions { gap: 4px; }
.button { padding: 6px 8px; }
.notice { padding: 0 15px; }
.detail { padding: 20px 18px 34px; }
dl { grid-template-columns: 1fr; }
dt { padding-bottom: 0; }
dd { border-top: 0; padding-top: 2px; }
}
</style>
</head>
<body>
<main class="shell">
<header class="topbar">
<div class="identity">
<div class="mark" aria-hidden="true">RI</div>
<div>
<p class="eyebrow" id="eyebrow">Vera · carta di lavoro</p>
<h1 id="page-title">Registro Imprese e SARI</h1>
</div>
</div>
<div class="actions">
<button class="button" id="copy" type="button">Copia decisioni</button>
<button class="button" id="save" type="button">Salva</button>
<button class="button primary" id="apply" type="button">Applica alla carta</button>
</div>
</header>
<section class="notice" id="summary" aria-label="Riepilogo run">
<div class="metric"><span id="run-label">Run</span><strong id="run">—</strong></div>
<div class="metric"><span id="status-label">Stato</span><strong id="status">—</strong></div>
<div class="metric"><span id="item-count-label">Elementi</span><strong id="item-count">0</strong></div>
<div class="metric"><span id="decision-count-label">Decisioni</span><strong id="decision-count">0</strong></div>
<div class="message" id="message">Nessun accesso, firma o invio è disponibile in questa revisione.</div>
</section>
<p class="case-summary" id="case-summary" hidden></p>
<section class="workspace">
<aside class="queue">
<div class="queue-head">
<div class="queue-title"><h2 id="queue-title">Coda di revisione</h2><span class="count" id="visible-count">0</span></div>
<input class="search" id="search" type="search" placeholder="Filtra per titolo o tipo" aria-label="Filtra la coda">
<div class="filters" id="filters"></div>
</div>
<div class="rows" id="rows"></div>
</aside>
<article class="detail" id="detail"></article>
</section>
<footer class="footer" id="footer-note">Nessun accesso, firma o invio è disponibile in questa revisione.</footer>
</main>
<script>
const FALLBACK = {
widget_type: "registro_imprese_sari_review",
run_intake: null,
review_payload: {
schema_version: "1.0",
plugin: "registro-imprese-sari",
workflow: "registro-imprese-sari",
run_id: "demo-local",
status: "draft_for_professional_review",
item_count: 3,
filing_status: "not_filed",
filing_authorized: false,
items: [
{
id: "source-demo",
item_type: "official_source",
title: "Fonte ufficiale SARI-DEMO",
allowed_actions: ["accept", "reject", "mark_unclear", "skip"],
recommended_action: "mark_unclear",
data: { source_id: "SARI-DEMO", applicability_status: "requires_professional_confirmation" },
},
{
id: "plan-step-1",
item_type: "practice_step",
title: "Dire step 1",
allowed_actions: ["accept", "reject", "edit", "mark_unclear", "request_more_documents", "skip"],
recommended_action: "mark_unclear",
data: { system: "DIRE", review_status: "proposed", source_ids: ["SARI-DEMO"] },
},
{
id: "audit-demo",
item_type: "audit_check",
title: "Controlli meccanici della pratica",
allowed_actions: ["accept", "mark_unclear", "skip"],
recommended_action: "accept",
data: { validation_status: "passed_with_blockers", blocker_count: 1 },
},
],
},
decision_policy: {
save_tool: "save_registro_imprese_sari_decisions",
apply_tool: "apply_registro_imprese_sari_decisions",
},
};
const UI_COPY = {
"it": {
"pageTitle": "Revisione Registro Imprese e SARI",
"eyebrow": "Vera · carta di lavoro",
"copyButton": "Copia decisioni",
"saveButton": "Salva",
"applyButton": "Applica alla carta",
"summaryAria": "Riepilogo run",
"runLabel": "Run",
"statusLabel": "Stato",
"itemsLabel": "Elementi",
"decisionsLabel": "Decisioni",
"boundary": "Nessun accesso, firma o invio è disponibile in questa revisione.",
"queueTitle": "Coda di revisione",
"searchPlaceholder": "Filtra per titolo o tipo",
"searchAria": "Filtra la coda",
"all": "Tutti",
"reviewDefault": "da rivedere",
"noFilterMatch": "Nessun elemento corrisponde al filtro.",
"selectItem": "Seleziona un elemento.",
"reviewDetails": "Dettagli di revisione",
"decision": "Decisione",
"reviewerNote": "Nota del revisore",
"proposedEdit": "Testo corretto proposto",
"requestedDocuments": "Documenti richiesti, uno per riga",
"impact": "Le decisioni aggiornano soltanto la carta di lavoro. “Correggi” crea una richiesta di revisione e non modifica automaticamente il piano professionale.",
"toolMissing": "Strumento di revisione non configurato",
"mcpUnavailable": "Salvataggio MCP non disponibile; usa Copia decisioni",
"copied": "Decisioni copiate negli appunti.",
"saved": "Decisioni salvate nella cartella privata del run.",
"applied": "Decisioni applicate alla carta di lavoro; nessuna azione su portali.",
"localSource": "Fonte locale",
"output": "Output",
"types": {
"case_fact": "Qualificazione",
"practice_step": "Passaggio pratica",
"official_source": "Fonte ufficiale",
"missing_information": "Informazione mancante",
"audit_check": "Controllo",
"artifact": "Artefatto"
},
"actions": {
"accept": "Accetta",
"reject": "Rifiuta",
"edit": "Correggi",
"mark_unclear": "Da chiarire",
"request_more_documents": "Chiedi documenti",
"skip": "Salta"
},
"statuses": {
"ready_for_professional_review": "Pronto per revisione professionale",
"partial_review": "Revisione parziale",
"draft_for_professional_review": "Bozza per revisione professionale",
"passed": "Superato",
"needs_review": "Da rivedere",
"confirmed": "Confermato",
"requires_professional_confirmation": "Da confermare dal professionista",
"proposed": "Proposto",
"blocked": "Bloccato",
"passed_with_blockers": "Controlli superati; punti aperti",
"needs_professional_review": "Da rivedere dal professionista",
"not_selected": "Non selezionato",
"selected_requires_professional_applicability_review": "Fonte selezionata; applicabilità da confermare"
},
"fields": {
"type": "Tipo",
"source_id": "ID fonte",
"publisher": "Editore",
"territorial_applicability": "Applicabilità territoriale",
"source_date": "Data fonte",
"official_url": "URL ufficiale",
"applicability_status": "Stato applicabilità",
"record_id": "ID record",
"system": "Sistema",
"sequence": "Sequenza",
"detail": "Dettaglio",
"proposed_value": "Valore proposto",
"document_quotes": "Citazioni documentali",
"review_status": "Stato revisione",
"source_ids": "ID fonti",
"case_fact_ids": "ID fatti del caso",
"confirmation": "Conferma",
"validation_status": "Stato validazione",
"error_count": "Errori",
"blocker_count": "Blocchi",
"text": "Testo"
},
"technicalDetails": "Dettagli tecnici"
},
"es": {
"pageTitle": "Revisión de Registro Imprese y SARI",
"eyebrow": "Vera · documento de trabajo",
"copyButton": "Copiar decisiones",
"saveButton": "Guardar",
"applyButton": "Aplicar al documento",
"summaryAria": "Resumen de la ejecución",
"runLabel": "Ejecución",
"statusLabel": "Estado",
"itemsLabel": "Elementos",
"decisionsLabel": "Decisiones",
"boundary": "En esta revisión no está disponible ningún acceso, firma ni presentación.",
"queueTitle": "Cola de revisión",
"searchPlaceholder": "Filtrar por título o tipo",
"searchAria": "Filtrar la cola",
"all": "Todos",
"reviewDefault": "por revisar",
"noFilterMatch": "Ningún elemento coincide con el filtro.",
"selectItem": "Seleccione un elemento.",
"reviewDetails": "Detalles de la revisión",
"decision": "Decisión",
"reviewerNote": "Nota del revisor",
"proposedEdit": "Texto corregido propuesto",
"requestedDocuments": "Documentos solicitados, uno por línea",
"impact": "Las decisiones solo actualizan el documento de trabajo. «Corregir» crea una solicitud de revisión y no modifica automáticamente el plan profesional.",
"toolMissing": "La herramienta de revisión no está configurada",
"mcpUnavailable": "El guardado MCP no está disponible; use Copiar decisiones",
"copied": "Decisiones copiadas al portapapeles.",
"saved": "Decisiones guardadas en la carpeta privada de la ejecución.",
"applied": "Decisiones aplicadas al documento de trabajo; no se ha realizado ninguna acción en portales.",
"localSource": "Fuente local",
"output": "Salida",
"types": {
"case_fact": "Calificación",
"practice_step": "Paso de la práctica",
"official_source": "Fuente oficial",
"missing_information": "Información pendiente",
"audit_check": "Control",
"artifact": "Artefacto"
},
"actions": {
"accept": "Aceptar",
"reject": "Rechazar",
"edit": "Corregir",
"mark_unclear": "Requiere aclaración",
"request_more_documents": "Solicitar documentos",
"skip": "Omitir"
},
"statuses": {
"ready_for_professional_review": "Listo para revisión profesional",
"partial_review": "Revisión parcial",
"draft_for_professional_review": "Borrador para revisión profesional",
"passed": "Superado",
"needs_review": "Por revisar",
"confirmed": "Confirmado",
"requires_professional_confirmation": "Requiere confirmación profesional",
"proposed": "Propuesto",
"blocked": "Bloqueado",
"passed_with_blockers": "Controles superados; quedan bloqueos",
"needs_professional_review": "Requiere revisión profesional",
"not_selected": "No seleccionado",
"selected_requires_professional_applicability_review": "Fuente seleccionada; aplicabilidad por confirmar"
},
"fields": {
"type": "Tipo",
"source_id": "ID de la fuente",
"publisher": "Editor",
"territorial_applicability": "Aplicabilidad territorial",
"source_date": "Fecha de la fuente",
"official_url": "URL oficial",
"applicability_status": "Estado de aplicabilidad",
"record_id": "ID del registro",
"system": "Sistema",
"sequence": "Secuencia",
"detail": "Detalle",
"proposed_value": "Valor propuesto",
"document_quotes": "Citas documentales",
"review_status": "Estado de la revisión",
"source_ids": "ID de las fuentes",
"case_fact_ids": "ID de los hechos del caso",
"confirmation": "Confirmación",
"validation_status": "Estado de la validación",
"error_count": "Errores",
"blocker_count": "Bloqueos",
"text": "Texto"
},
"technicalDetails": "Detalles técnicos"
},
"en": {
"pageTitle": "Registro Imprese and SARI review",
"eyebrow": "Vera · workpaper",
"copyButton": "Copy decisions",
"saveButton": "Save",
"applyButton": "Apply to workpaper",
"summaryAria": "Run summary",
"runLabel": "Run",
"statusLabel": "Status",
"itemsLabel": "Items",
"decisionsLabel": "Decisions",
"boundary": "This review provides no portal access, signature or submission.",
"queueTitle": "Review queue",
"searchPlaceholder": "Filter by title or type",
"searchAria": "Filter the queue",
"all": "All",
"reviewDefault": "To review",
"noFilterMatch": "No item matches the filter.",
"selectItem": "Select an item.",
"reviewDetails": "Review details",
"decision": "Decision",
"reviewerNote": "Reviewer note",
"proposedEdit": "Proposed corrected text",
"requestedDocuments": "Requested documents, one per line",
"impact": "Decisions update the workpaper only. “Edit” records a revision request and does not automatically change the professional plan.",
"toolMissing": "Review tool is not configured",
"mcpUnavailable": "MCP saving is unavailable; use Copy decisions",
"copied": "Decisions copied to the clipboard.",
"saved": "Decisions saved in the private run folder.",
"applied": "Decisions applied to the workpaper; no portal action.",
"localSource": "Local source",
"output": "Output",
"types": {
"case_fact": "Classification",
"practice_step": "Practice step",
"official_source": "Official source",
"missing_information": "Missing information",
"audit_check": "Check",
"artifact": "Artifact"
},
"actions": {
"accept": "Accept",
"reject": "Reject",
"edit": "Edit",
"mark_unclear": "Needs clarification",
"request_more_documents": "Request documents",
"skip": "Skip"
},
"statuses": {
"ready_for_professional_review": "Ready for professional review",
"partial_review": "Partial review",
"draft_for_professional_review": "Draft for professional review",
"passed": "Passed",
"needs_review": "To review",
"confirmed": "Confirmed",
"requires_professional_confirmation": "Needs professional confirmation",
"proposed": "Proposed",
"blocked": "Blocked",
"passed_with_blockers": "Checks passed; blockers remain",
"needs_professional_review": "Professional review needed",
"not_selected": "Not selected",
"selected_requires_professional_applicability_review": "Source selected; applicability needs confirmation"
},
"fields": {
"type": "Type",
"source_id": "Source ID",
"publisher": "Publisher",
"territorial_applicability": "Territorial applicability",
"source_date": "Source date",
"official_url": "Official URL",
"applicability_status": "Applicability status",
"record_id": "Record ID",
"system": "System",
"sequence": "Sequence",
"detail": "Detail",
"proposed_value": "Proposed value",
"document_quotes": "Document excerpts",
"review_status": "Review status",
"source_ids": "Source IDs",
"case_fact_ids": "Case-fact IDs",
"confirmation": "Confirmation",
"validation_status": "Validation status",
"error_count": "Errors",
"blocker_count": "Blockers",
"text": "Text"
},
"technicalDetails": "Technical details"
},
"fr": {
"pageTitle": "Révision Registro Imprese et SARI",
"eyebrow": "Vera · document de travail",
"copyButton": "Copier les décisions",
"saveButton": "Enregistrer",
"applyButton": "Appliquer au document",
"summaryAria": "Résumé de l’exécution",
"runLabel": "Exécution",
"statusLabel": "État",
"itemsLabel": "Éléments",
"decisionsLabel": "Décisions",
"boundary": "Cette révision ne permet aucun accès au portail, signature ou dépôt.",
"queueTitle": "Liste de révision",
"searchPlaceholder": "Filtrer par titre ou type",
"searchAria": "Filtrer la liste",
"all": "Tous",
"reviewDefault": "À revoir",
"noFilterMatch": "Aucun élément ne correspond au filtre.",
"selectItem": "Sélectionnez un élément.",
"reviewDetails": "Détails de la révision",
"decision": "Décision",
"reviewerNote": "Note du réviseur",
"proposedEdit": "Texte corrigé proposé",
"requestedDocuments": "Pièces demandées, une par ligne",
"impact": "Les décisions mettent à jour le document de travail uniquement. « Corriger » crée une demande de révision et ne modifie pas automatiquement le plan professionnel.",
"toolMissing": "L’outil de révision n’est pas configuré",
"mcpUnavailable": "Enregistrement MCP indisponible ; utilisez Copier les décisions",
"copied": "Décisions copiées dans le presse-papiers.",
"saved": "Décisions enregistrées dans le dossier privé de l’exécution.",
"applied": "Décisions appliquées au document de travail ; aucune action sur un portail.",
"localSource": "Source locale",
"output": "Résultat",
"types": {
"case_fact": "Qualification",
"practice_step": "Étape du dossier",
"official_source": "Source officielle",
"missing_information": "Information manquante",
"audit_check": "Contrôle",
"artifact": "Document"
},
"actions": {
"accept": "Accepter",
"reject": "Refuser",
"edit": "Corriger",
"mark_unclear": "À clarifier",
"request_more_documents": "Demander des pièces",
"skip": "Passer"
},
"statuses": {
"ready_for_professional_review": "Prêt pour révision professionnelle",
"partial_review": "Révision partielle",
"draft_for_professional_review": "Projet pour révision professionnelle",
"passed": "Réussi",
"needs_review": "À revoir",
"confirmed": "Confirmé",
"requires_professional_confirmation": "À confirmer par le professionnel",
"proposed": "Proposé",
"blocked": "Bloqué",
"passed_with_blockers": "Contrôles réussis ; points bloquants",
"needs_professional_review": "À revoir par le professionnel",
"not_selected": "Non sélectionné",
"selected_requires_professional_applicability_review": "Source sélectionnée ; applicabilité à confirmer"
},
"fields": {
"type": "Type",
"source_id": "ID de la source",
"publisher": "Éditeur",
"territorial_applicability": "Applicabilité territoriale",
"source_date": "Date de la source",
"official_url": "URL officielle",
"applicability_status": "État de l’applicabilité",
"record_id": "ID de l’enregistrement",
"system": "Système",
"sequence": "Séquence",
"detail": "Détail",
"proposed_value": "Valeur proposée",
"document_quotes": "Extraits des pièces",
"review_status": "État de la révision",
"source_ids": "ID des sources",
"case_fact_ids": "ID des faits du dossier",
"confirmation": "Confirmation",
"validation_status": "État de la validation",
"error_count": "Erreurs",
"blocker_count": "Points bloquants",
"text": "Texte"
},
"technicalDetails": "Détails techniques"
},
"de": {
"pageTitle": "Prüfung Registro Imprese und SARI",
"eyebrow": "Vera · Arbeitspapier",
"copyButton": "Entscheidungen kopieren",
"saveButton": "Speichern",
"applyButton": "Auf Arbeitspapier anwenden",
"summaryAria": "Zusammenfassung des Durchlaufs",
"runLabel": "Durchlauf",
"statusLabel": "Status",
"itemsLabel": "Einträge",
"decisionsLabel": "Entscheidungen",
"boundary": "Diese Prüfung ermöglicht keinen Portalzugang, keine Unterschrift und keine Einreichung.",
"queueTitle": "Prüfliste",
"searchPlaceholder": "Nach Titel oder Typ filtern",
"searchAria": "Prüfliste filtern",
"all": "Alle",
"reviewDefault": "Zu prüfen",
"noFilterMatch": "Kein Eintrag entspricht dem Filter.",
"selectItem": "Wählen Sie einen Eintrag.",
"reviewDetails": "Prüfdetails",
"decision": "Entscheidung",
"reviewerNote": "Prüfvermerk",
"proposedEdit": "Vorgeschlagener korrigierter Text",
"requestedDocuments": "Angeforderte Unterlagen, eine pro Zeile",
"impact": "Entscheidungen aktualisieren nur das Arbeitspapier. „Korrigieren“ erfasst einen Änderungsauftrag und ändert den fachlichen Plan nicht automatisch.",
"toolMissing": "Prüfwerkzeug ist nicht eingerichtet",
"mcpUnavailable": "MCP-Speicherung nicht verfügbar; nutzen Sie Entscheidungen kopieren",
"copied": "Entscheidungen in die Zwischenablage kopiert.",
"saved": "Entscheidungen im privaten Durchlaufordner gespeichert.",
"applied": "Entscheidungen auf das Arbeitspapier angewendet; keine Portalaktion.",
"localSource": "Lokale Quelle",
"output": "Ausgabe",
"types": {
"case_fact": "Einordnung",
"practice_step": "Verfahrensschritt",
"official_source": "Amtliche Quelle",
"missing_information": "Fehlende Information",
"audit_check": "Prüfung",
"artifact": "Dokument"
},
"actions": {
"accept": "Annehmen",
"reject": "Ablehnen",
"edit": "Korrigieren",
"mark_unclear": "Zu klären",
"request_more_documents": "Unterlagen anfordern",
"skip": "Überspringen"
},
"statuses": {
"ready_for_professional_review": "Bereit zur fachlichen Prüfung",
"partial_review": "Teilweise Prüfung",
"draft_for_professional_review": "Entwurf zur fachlichen Prüfung",
"passed": "Bestanden",
"needs_review": "Zu prüfen",
"confirmed": "Bestätigt",
"requires_professional_confirmation": "Fachlich zu bestätigen",
"proposed": "Vorgeschlagen",
"blocked": "Blockiert",
"passed_with_blockers": "Prüfungen bestanden; offene Punkte",
"needs_professional_review": "Fachlich zu prüfen",
"not_selected": "Nicht ausgewählt",
"selected_requires_professional_applicability_review": "Quelle ausgewählt; Anwendbarkeit zu bestätigen"
},
"fields": {
"type": "Typ",
"source_id": "Quellen-ID",
"publisher": "Herausgeber",
"territorial_applicability": "Räumliche Anwendbarkeit",
"source_date": "Quelldatum",
"official_url": "Amtliche URL",
"applicability_status": "Status der Anwendbarkeit",
"record_id": "Datensatz-ID",
"system": "System",
"sequence": "Reihenfolge",
"detail": "Einzelheit",
"proposed_value": "Vorgeschlagener Wert",
"document_quotes": "Dokumentauszüge",
"review_status": "Prüfstatus",
"source_ids": "Quellen-IDs",
"case_fact_ids": "Sachverhalts-IDs",
"confirmation": "Bestätigung",
"validation_status": "Validierungsstatus",
"error_count": "Fehler",
"blocker_count": "Offene Prüfpunkte",
"text": "Text"
},
"technicalDetails": "Technische Details"
}
};
function normalizeLanguage(value) {
const language = String(value || "").trim().toLowerCase().replaceAll("_", "-");
const primary = language.split("-")[0];
const code = ({ ita: "it", eng: "en", fra: "fr", deu: "de", spa: "es" })[primary] || primary;
return Object.hasOwn(UI_COPY, code) ? code : "it";
}
function languageFor(payload) {
return normalizeLanguage(
payload?.review_payload?.language
|| payload?.run_intake?.language
|| payload?.run_intake?.working_language
|| "it"
);
}
function copyFor(payload) { return UI_COPY[languageFor(payload)] || UI_COPY.it; }
const state = {
payload: null,
selectedId: null,
selectedType: "all",
query: "",
decisions: new Map(),
};
function copy() { return copyFor(state.payload); }
function localizedValue(value) {
return copy().statuses[String(value || "")] || value;
}
function applyStaticCopy() {
const text = copy();
document.documentElement.lang = languageFor(state.payload);
document.title = text.pageTitle;
document.getElementById("page-title").textContent = text.pageTitle;
document.getElementById("eyebrow").textContent = text.eyebrow;
document.getElementById("copy").textContent = text.copyButton;
document.getElementById("save").textContent = text.saveButton;
document.getElementById("apply").textContent = text.applyButton;
document.getElementById("summary").setAttribute("aria-label", text.summaryAria);
document.getElementById("run-label").textContent = text.runLabel;
document.getElementById("status-label").textContent = text.statusLabel;
document.getElementById("item-count-label").textContent = text.itemsLabel;
document.getElementById("decision-count-label").textContent = text.decisionsLabel;
document.getElementById("message").textContent = text.boundary;
document.getElementById("queue-title").textContent = text.queueTitle;
document.getElementById("search").placeholder = text.searchPlaceholder;
document.getElementById("search").setAttribute("aria-label", text.searchAria);
document.getElementById("footer-note").textContent = text.boundary;
}
function toolOutput() {
return window.openai?.toolOutput || window.openai?.structuredContent || FALLBACK;
}
function esc(value) {
return String(value ?? "").replace(/[&<>"']/g, (char) => ({
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
})[char]);
}
function review() { return state.payload?.review_payload || FALLBACK.review_payload; }
function items() { return Array.isArray(review().items) ? review().items : []; }
function decisionFor(id) { return state.decisions.get(id) || null; }
function filteredItems() {
const query = state.query.trim().toLowerCase();
return items().filter((item) => {
if (state.selectedType !== "all" && item.item_type !== state.selectedType) return false;
if (!query) return true;
return `${item.title} ${item.item_type}`.toLowerCase().includes(query);
});
}
function statusClass(decision) {
if (!decision) return "";
return ({
accept: "accepted", reject: "rejected", edit: "edited_revision_required",
mark_unclear: "needs_clarification", request_more_documents: "needs_documents", skip: "skipped",
})[decision.action] || "";
}
function setMessage(text, kind = "") {
const node = document.getElementById("message");
node.textContent = text;
node.className = `message ${kind}`.trim();
}
function renderMetrics() {
const summary = review().case_context?.case_summary;
document.getElementById("case-summary").textContent = typeof summary === "string" ? summary : "";
document.getElementById("case-summary").hidden = !summary;
document.getElementById("run").textContent = review().run_id || "—";
document.getElementById("status").textContent = localizedValue(review().status) || "—";
document.getElementById("item-count").textContent = String(items().length);
document.getElementById("decision-count").textContent = String(state.decisions.size);
}
function renderFilters() {
const counts = new Map([["all", items().length]]);
for (const item of items()) counts.set(item.item_type, (counts.get(item.item_type) || 0) + 1);
document.getElementById("filters").innerHTML = Array.from(counts.entries()).map(([type, count]) => {
const label = type === "all" ? copy().all : (copy().types[type] || type);
return `<button type="button" class="filter ${state.selectedType === type ? "active" : ""}" data-type="${esc(type)}">${esc(label)} · ${count}</button>`;
}).join("");
}
function renderRows() {
const visible = filteredItems();
document.getElementById("visible-count").textContent = `${visible.length} / ${items().length}`;
if (!state.selectedId && visible[0]) state.selectedId = visible[0].id;
if (state.selectedId && !items().some((item) => item.id === state.selectedId)) {
state.selectedId = visible[0]?.id || null;
}
document.getElementById("rows").innerHTML = visible.length ? visible.map((item) => {
const decision = decisionFor(item.id);
return `<button type="button" class="row ${state.selectedId === item.id ? "selected" : ""}" data-id="${esc(item.id)}">
<span class="dot ${statusClass(decision)}"></span>
<span><span class="row-title">${esc(item.title)}</span><span class="row-meta">${esc(copy().types[item.item_type] || item.item_type)}</span></span>
<span class="recommended">${esc(decision ? copy().actions[decision.action] : copy().actions[item.recommended_action] || copy().reviewDefault)}</span>
</button>`;
}).join("") : `<div class="empty">${esc(copy().noFilterMatch)}</div>`;
renderDetail();
}
function detailValue(value) {
if (Array.isArray(value)) return `<ul>${value.map(entry => `<li>${detailValue(entry)}</li>`).join("")}</ul>`;
if (value && typeof value === "object") return `<dl>${Object.entries(value).map(([key, entry]) => `<dt>${esc(copy().fields[key] || key.replaceAll("_", " "))}</dt><dd>${detailValue(entry)}</dd>`).join("")}</dl>`;
return esc(localizedValue(value));
}
function detailFields(item) {
const fields = Object.entries(item.data || {});
const meaningful = fields.filter(([key]) => !key.startsWith("target_") && !["record_id", "case_fact_ids"].includes(key));
const technical = [["ID", item.id], ...fields.filter(([key]) => key.startsWith("target_") || ["record_id", "case_fact_ids"].includes(key))];
const entries = [
[copy().fields.type, copy().types[item.item_type] || item.item_type],
[copy().localSource, item.source_path],
[copy().output, item.output_path],
...meaningful.map(([key, value]) => [copy().fields[key] || key.replaceAll("_", " "), value]),
].filter(([, value]) => value !== null && value !== undefined && value !== "" && (!Array.isArray(value) || value.length));
const render = values => `<dl>${values.map(([key, value]) => `<dt>${esc(key)}</dt><dd>${detailValue(value)}</dd>`).join("")}</dl>`;
return render(entries) + `<details><summary>${esc(copy().technicalDetails)}</summary>${render(technical)}</details>`;
}
function renderDetail() {
const item = items().find((candidate) => candidate.id === state.selectedId);
if (!item) {
document.getElementById("detail").innerHTML = `<div class="empty">${esc(copy().selectItem)}</div>`;
return;
}
const decision = decisionFor(item.id) || { item_id: item.id, action: "", reviewer_note: "", edit_value: "", requested_documents: [] };
const actions = item.allowed_actions || [];
document.getElementById("detail").innerHTML = `
<div class="detail-head">
<div><p class="type">${esc(copy().types[item.item_type] || item.item_type)}</p><h3>${esc(item.title)}</h3></div>
<span class="pill">${esc(decision.action ? copy().actions[decision.action] : copy().reviewDefault)}</span>
</div>
<section class="section"><h4>${esc(copy().reviewDetails)}</h4>${detailFields(item)}</section>
<section class="section">
<h4>${esc(copy().decision)}</h4>
<div class="decision-actions">${actions.map((action) => `<button type="button" class="decision-action ${decision.action === action ? "selected" : ""}" data-action="${esc(action)}">${esc(copy().actions[action] || action)}</button>`).join("")}</div>
<div class="field"><label for="note">${esc(copy().reviewerNote)}</label><textarea id="note">${esc(decision.reviewer_note || "")}</textarea></div>
${decision.action === "edit" ? `<div class="field"><label for="edit-value">${esc(copy().proposedEdit)}</label><textarea id="edit-value">${esc(decision.edit_value || "")}</textarea></div>` : ""}
${decision.action === "request_more_documents" ? `<div class="field"><label for="documents">${esc(copy().requestedDocuments)}</label><textarea id="documents">${esc((decision.requested_documents || []).join("\n"))}</textarea></div>` : ""}
<p class="impact">${esc(copy().impact)}</p>
</section>`;
if (state.payload?.local_read_only) {
document.getElementById("detail").querySelectorAll("button, textarea").forEach(control => { control.disabled = true; });
}
}
function updateDecision(itemId, changes) {
if (state.payload?.local_read_only) return;
const current = state.decisions.get(itemId) || { item_id: itemId, action: "", reviewer_note: "", edit_value: "", requested_documents: [] };
const next = { ...current, ...changes };
if (!next.action) state.decisions.delete(itemId); else state.decisions.set(itemId, next);
renderMetrics();
renderRows();
}
function serializedDecisions() {
return Array.from(state.decisions.values()).map((decision) => {
const result = { item_id: decision.item_id, action: decision.action };
if (decision.reviewer_note?.trim()) result.reviewer_note = decision.reviewer_note.trim();
if (decision.action === "edit") result.edit_value = decision.edit_value?.trim() || "";
if (decision.action === "request_more_documents") result.requested_documents = decision.requested_documents || [];
return result;
});
}
function toolArguments() {
const token = state.payload?.persistence_token || undefined;
return {
persistence_token: token,
...(token ? {} : {
run_intake: state.payload?.run_intake || undefined,
review_payload: review(),
}),
decisions: serializedDecisions(),
decision_source: "mcp_widget",
};
}
async function callReviewTool(toolName) {
if (!toolName) throw new Error(copy().toolMissing);
if (!window.openai?.callTool) throw new Error(copy().mcpUnavailable);
const args = toolArguments();
const missingDocuments = args.decisions.some(d => d.action === "request_more_documents" && !d.requested_documents?.length);
const missingEdit = args.decisions.some(d => d.action === "edit" && !d.edit_value?.trim());
if (missingDocuments || missingEdit) {
const messages = {
it: ["Indica almeno un documento da richiedere prima di salvare.", "Scrivi la modifica richiesta prima di salvare."],
en: ["Enter at least one requested document before saving.", "Enter the requested change before saving."],
fr: ["Indiquez au moins un document à demander avant d’enregistrer.", "Indiquez la modification demandée avant d’enregistrer."],
de: ["Vor dem Speichern mindestens ein angefordertes Dokument angeben.", "Vor dem Speichern die gewünschte Änderung angeben."],
es: ["Indica al menos un documento que solicitar antes de guardar.", "Escribe el cambio solicitado antes de guardar."],
};
throw new Error((messages[review().language] || messages.en)[missingDocuments ? 0 : 1]);
}
return window.openai.callTool(toolName, args);
}
async function copyDecisions() {
const text = JSON.stringify(toolArguments(), null, 2);
await navigator.clipboard.writeText(text);
setMessage(copy().copied, "ok");
}
function render() { renderMetrics(); renderFilters(); renderRows(); }
document.getElementById("search").addEventListener("input", (event) => {
state.query = event.target.value; renderRows();
});
document.getElementById("filters").addEventListener("click", (event) => {
const button = event.target.closest("button[data-type]");
if (!button) return;
state.selectedType = button.dataset.type; state.selectedId = null; render();
});
document.getElementById("rows").addEventListener("click", (event) => {
const button = event.target.closest("button[data-id]");
if (!button) return;
state.selectedId = button.dataset.id; renderRows();
});
document.getElementById("detail").addEventListener("click", (event) => {
const button = event.target.closest("button[data-action]");
if (!button || !state.selectedId) return;
updateDecision(state.selectedId, { action: button.dataset.action });
});
document.getElementById("detail").addEventListener("input", (event) => {
if (!state.selectedId) return;
const current = state.decisions.get(state.selectedId) || {
item_id: state.selectedId, action: "", reviewer_note: "", edit_value: "", requested_documents: [],
};
if (event.target.id === "note") current.reviewer_note = event.target.value;
if (event.target.id === "edit-value") current.edit_value = event.target.value;
if (event.target.id === "documents") {
current.requested_documents = event.target.value.split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
}
if (current.action) state.decisions.set(state.selectedId, current);
renderMetrics();
});
document.getElementById("copy").addEventListener("click", () => copyDecisions().catch((error) => setMessage(error.message, "error")));
document.getElementById("save").addEventListener("click", async () => {
try {
await callReviewTool(state.payload?.decision_policy?.save_tool);
setMessage(copy().saved, "ok");
} catch (error) { setMessage(error.message, "error"); }
});
document.getElementById("apply").addEventListener("click", async () => {
try {
await callReviewTool(state.payload?.decision_policy?.apply_tool);
setMessage(copy().applied, "ok");
} catch (error) { setMessage(error.message, "error"); }
});
state.payload = toolOutput();
applyStaticCopy();
const previous = state.payload?.ui_decisions?.decisions;
if (Array.isArray(previous)) previous.forEach((decision) => state.decisions.set(decision.item_id, decision));
render();
if (state.payload?.local_read_only) {
document.getElementById("save").disabled = true;
document.getElementById("apply").disabled = true;
const notices = {
it: "Esecuzione archiviata: puoi leggere le decisioni salvate. Per modificarle, avvia una nuova versione.",
en: "Archived run: saved decisions are read-only. Start a new version to make changes.",
fr: "Exécution archivée : les décisions enregistrées sont en lecture seule. Créez une nouvelle version pour les modifier.",
de: "Archivierter Lauf: Gespeicherte Entscheidungen sind schreibgeschützt. Für Änderungen eine neue Version starten.",
es: "Ejecución archivada: las decisiones guardadas son de solo lectura. Inicia una nueva versión para cambiarlas.",
};
setMessage(notices[review().language] || notices.en, "ok");
}
</script>
</body>
</html>
SHA-256: df4a4f000a127956a98784ed9babd30397c853e58a3263033838e0e80fa167cb