← Files VeraARCHIVED FILE

modules/registro-imprese-sari/assets/registro-imprese-sari-review-widget.html

45.3 KB · Oct 5, 2026 · 18:29 UTC

↓ Download file

<!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) => ({
        "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
      })[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