1/* prisma-shell:20260525-1 */(function(){"use strict";let e=null,o=null,i=[],f=!1,a=null;const l={conforme:{label:"Conforme",icon:"\u2705",color:"#22c55e",bg:"#f0fdf4"},deviazione:{label:"Deviazione",icon:"\u26A0\uFE0F",color:"#f59e0b",bg:"#fffbeb"},inaccettabile:{label:"Inaccettabile",icon:"\u274C",color:"#ef4444",bg:"#fef2f2"},non_in_playbook:{label:"Non in playbook",icon:"\u2753",color:"#6b7280",bg:"#f3f4f6"}},u={low:"#22c55e",medium:"#f59e0b",high:"#ef4444",critical:"#dc2626"};function r(t){return window.escapeHtml?window.escapeHtml(t):String(t||"").replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}function x(t){return firebase.app().functions("europe-west1").httpsCallable(t)}async function m(){return e||(window.DbHelper&&typeof window.DbHelper.getStudioId=="function"&&(e=await window.DbHelper.getStudioId()),e)}async function b(){if(!f){if(console.log("[NegotiationCopilotModule] init"),await m(),!e){console.warn("[NegotiationCopilotModule] No studioId \u2014 skipping");return}f=!0,M()}}async function v(t){if(!window.Swal)return;await b();const{value:n}=await Swal.fire({title:"Analisi Contratto Controparte",html:` 2 <div style="text-align:left;font-size:13px"> 3 <label style="display:block;margin-bottom:8px;font-weight:600">Tipo contratto</label> 4 <select id="swal-nc-type" class="swal2-select" style="width:100%;margin:0 0 16px;padding:8px;border:1px solid #2a2d3e;border-radius:6px;background:#0f1117;color:#c7ccda"> 5 <option value="commerciale" ${t==="commerciale"?"selected":""}>Commerciale</option> 6 <option value="lavoro" ${t==="lavoro"?"selected":""}>Lavoro</option> 7 <option value="immobiliare" ${t==="immobiliare"?"selected":""}>Immobiliare</option> 8 <option value="mandato" ${t==="mandato"?"selected":""}>Mandato professionale</option> 9 <option value="altro">Altro</option> 10 </select> 11 <label style="display:block;margin-bottom:8px;font-weight:600">Carica contratto (PDF, Word, o testo)</label> 12 <div id="swal-nc-dropzone" style="border:2px dashed #2a2d3e;border-radius:10px;padding:32px;text-align:center;cursor:pointer;transition:all .2s;background:#0f111780"> 13 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="1.5" style="margin-bottom:8px"> 14 <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/> 15 </svg> 16 <p style="color:#667085;font-size:12px;margin:0">Trascina qui o clicca per caricare</p> 17 <p style="color:#4a5568;font-size:10px;margin:4px 0 0">PDF, DOCX, TXT</p> 18 </div> 19 <input type="file" id="swal-nc-file" accept=".pdf,.docx,.doc,.txt,.html" style="display:none"> 20 <div id="swal-nc-file-info" style="margin-top:8px;font-size:11px;color:#667085;display:none"></div> 21 <div style="margin-top:16px"> 22 <label style="display:block;margin-bottom:8px;font-weight:600">Oppure incolla il testo del contratto</label> 23 <textarea id="swal-nc-text" rows="6" placeholder="Incolla qui il testo del contratto..." style="width:100%;padding:8px;border:1px solid #2a2d3e;border-radius:6px;background:#0f1117;color:#c7ccda;font-size:12px;resize:vertical"></textarea> 24 </div> 25 </div>`,showCancelButton:!0,confirmButtonText:"Analizza",cancelButtonText:"Annulla",width:560,didOpen:()=>{const s=document.getElementById("swal-nc-dropzone"),c=document.getElementById("swal-nc-file"),p=document.getElementById("swal-nc-file-info");s.addEventListener("click",()=>c.click()),s.addEventListener("dragover",d=>{d.preventDefault(),s.style.borderColor="#5D1ADB"}),s.addEventListener("dragleave",()=>{s.style.borderColor="#2a2d3e"}),s.addEventListener("drop",d=>
25{d.preventDefault(),s.style.borderColor="#2a2d3e",d.dataTransfer.files.length&&(c.files=d.dataTransfer.files,p.textContent=d.dataTransfer.files[0].name,p.style.display="block")}),c.addEventListener("change",()=>{c.files.length&&(p.textContent=c.files[0].name,p.style.display="block")})},preConfirm:async()=>{const s=document.getElementById("swal-nc-type").value,c=document.getElementById("swal-nc-file"),p=document.getElementById("swal-nc-text");let d="";if(c.files.length>0){const g=c.files[0];try{g.name.endsWith(".txt")||g.name.endsWith(".html")?d=await g.text():g.name.endsWith(".pdf")?d=await w(g):g.name.endsWith(".docx")||g.name.endsWith(".doc")?d=await C(g):d=await g.text()}catch(h){return Swal.showValidationMessage("Errore lettura file: "+h.message),!1}}else if(p.value.trim())d=p.value.trim();else return Swal.showValidationMessage("Carica un file o incolla il testo del contratto"),!1;return d.length<50?(Swal.showValidationMessage("Testo del contratto troppo breve"),!1):{html:d,contractType:s}}});if(n){Swal.fire({title:"Analisi in corso...",html:"<p>Il Copilot sta analizzando le clausole contro il playbook...</p>",allowOutsideClick:!1,didOpen:()=>Swal.showLoading()});try{o=(await x("analyzeCounterpartyContract")({html:n.html,contractType:n.contractType})).data,Swal.close(),k(o)}catch(s){Swal.fire({icon:"error",title:"Errore analisi",text:s.message||"Errore durante analisi contratto"})}}}async function w(t){let n;try{n=await window.VendorLoader.load("pdfjs")}catch{return await t.text()}const s=await t.arrayBuffer(),c=await n.getDocument({data:s}).promise;let p="";for(let d=1;d<=c.numPages;d++){const h=await(await c.getPage(d)).getTextContent();p+=h.items.map(y=>y.str).join(" ")+` 26 27`}return p}async function C(t){let n;try{n=await window.VendorLoader.load("jszip")}catch{return await t.text()}const s=await t.arrayBuffer(),p=await(await n.loadAsync(s)).file("word/document.xml")?.async("text");if(!p)throw new Error("File DOCX non valido");return p.replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim()}function k(t){if(!t)return;a&&a.remove(),a=document.createElement("div"),a.id="nc-analysis-panel",a.className="nc-panel";const n=t.overallRisk||{},s=u[n.level]||u.medium,c=t.clauses||[],p=t.vessatorie||[],d=t.redlines||[],g={conforme:0,deviazione:0,inaccettabile:0,non_in_playbook:0};c.forEach(h=>{g[h.conformity]!==void 0&&g[h.conformity]++}),a.innerHTML=` 28 <div class="nc-panel-header"> 29 <h2 class="nc-panel-title">Negotiation Copilot</h2> 30 <button class="nc-close-btn" onclick="NegotiationCopilotModule.closePanel()">×</button> 31 </div> 32 33 <!-- Overall Risk Score --> 34 <div class="nc-risk-banner" style="border-color:${s}40;background:${s}08"> 35 <div class="nc-risk-score" style="border-color:${s};color:${s}">${n.score||"?"}<span>/10</span></div> 36 <div class="nc-risk-info"> 37 <div class="nc-risk-level" style="color:${s}">${(n.level||"N/D").toUpperCase()}</div> 38 <div class="nc-risk-summary">${r(n.summary||"")}</div> 39 </div> 40 </div> 41 42 <!-- Conformity Summary --> 43 <div class="nc-conformity-strip"> 44 ${Object.entries(l).map(([h,y])=>` 45 <div class="nc-conf-chip" style="background:${y.bg};color:${y.color};border:1px solid ${y.color}30"> 46 ${y.icon} ${g[h]} ${y.label} 47 </div> 48 `).join("")} 49 </div> 50 51 ${p.length>0?` 52 <!-- Vessatorie Warning --> 53 <div class="nc-vessatorie-banner"> 54 <div class="nc-vess-icon">⚠</div> 55 <div class="nc-vess-content"> 56 <strong>Clausole vessatorie rilevate (Art. 1341-1342 c.c.)</strong> 57 <p>${p.length} clausola/e richiedono approvazione specifica per iscritto</p> 58 </div> 59 </div>`:""} 60 61 <!-- Recommendations --> 62 ${n.recommendations?.length?` 63 <div class="nc-recommendations"> 64 <h4>Raccomandazioni</h4> 65 <ul>${n.recommendations.map(h=>`<li>${r(h)}</li>`).join("")}</ul> 66 </div>`:""} 67 68 <!-- Clauses Analysis --> 69 <div class="nc-clauses-list"> 70 <h3>
70Analisi clausole (${c.length})</h3> 71 ${c.map(h=>$(h)).join("")} 72 </div> 73 74 ${p.length>0?` 75 <!-- Vessatorie Detail --> 76 <div class="nc-vess-detail"> 77 <h3>Dettaglio clausole vessatorie</h3> 78 ${p.map(h=>S(h)).join("")} 79 </div>`:""} 80 81 <!-- Actions --> 82 <div class="nc-panel-footer"> 83 <button class="nc-btn nc-btn-primary" onclick="NegotiationCopilotModule.uploadContract()">Nuova analisi</button> 84 <button class="nc-btn nc-btn-secondary" onclick="NegotiationCopilotModule.managePlaybook()">Gestisci Playbook</button> 85 </div> 86 `,document.body.appendChild(a)}function $(t){const n=l[t.conformity]||l.non_in_playbook,s=(t.riskScore||0)/10*100,c=t.redlines&&(t.conformity==="deviazione"||t.conformity==="inaccettabile");return` 87 <div class="nc-clause-card" data-id="${t.id}"> 88 <div class="nc-clause-header"> 89 <span class="nc-clause-conformity" style="background:${n.bg};color:${n.color};border:1px solid ${n.color}30"> 90 ${n.icon} ${n.label} 91 </span> 92 <span class="nc-clause-type">${r(t.type||"")}</span> 93 <span class="nc-clause-title">${r(t.title||"")}</span> 94 <div class="nc-clause-risk"> 95 <div class="nc-risk-bar"><div class="nc-risk-fill" style="width:${s}%;background:${s>60?"#ef4444":s>30?"#f59e0b":"#22c55e"}"></div></div> 96 <span>${t.riskScore||0}/10</span> 97 </div> 98 </div> 99 <div class="nc-clause-body"> 100 <div class="nc-clause-original"><strong>Testo controparte:</strong> ${r((t.originalText||"").substring(0,200))}${(t.originalText||"").length>200?"...":""}</div> 101 ${t.deviationDescription?`<div class="nc-clause-deviation"><strong>Deviazione:</strong> ${r(t.deviationDescription)}</div>`:""} 102 ${t.playbookRule?`<div class="nc-clause-playbook"><strong>Regola playbook:</strong> ${r(t.playbookRule)}</div>`:""} 103 </div> 104 ${c?` 105 <div class="nc-clause-actions"> 106 <button class="nc-btn nc-btn-sm" onclick="NegotiationCopilotModule.showRedlines('${t.id}')">Vedi redline suggerite</button> 107 </div>`:""} 108 </div>`}function S(t){return` 109 <div class="nc-vess-card"> 110 <div class="nc-vess-card-header"> 111 <span class="nc-vess-severity ${t.severity||"high"}">${(t.severity||"high").toUpperCase()}</span> 112 <span class="nc-vess-article">${r(t.article||"")}</span> 113 </div> 114 <div class="nc-vess-card-body"> 115 <p class="nc-vess-type"><strong>${r(t.type||"")}</strong></p> 116 <p class="nc-vess-desc">${r(t.description||"")}</p> 117 ${t.requiresSpecificApproval?'<p class="nc-vess-approval">Richiede approvazione specifica per iscritto ex Art. 1341, c. 2, c.c.</p>':""} 118 </div> 119 </div>`}function I(t){if(!o)return;const n=(o.clauses||[]).find(d=>d.id===t);if(!n||!n.redlines)return;const{ideal:s,acceptable:c,minimum:p}=n.redlines;Swal.fire({title:`Redline: ${n.title||n.type||"Clausola"}`,html:` 120 <div style="text-align:left;font-size:13px"> 121 <div class="nc-redline-level" style="border-left:3px solid #22c55e;padding-left:12px;margin-bottom:16px"> 122 <h4 style="color:#22c55e;margin:0 0 4px">Ideale</h4> 123 <p style="color:#94a3b8;margin:0;font-size:12px">${r(s||"Non disponibile")}</p> 124 <button class="nc-copy-btn" onclick="NegotiationCopilotModule.applyRedline('${t}','ideal')" style="margin-top:6px;padding:4px 10px;border:1px solid #22c55e40;border-radius:4px;background:transparent;color:#22c55e;font-size:11px;cursor:pointer">Copia</button> 125 </div> 126 <div class="nc-redline-level" style="border-left:3px solid #f59e0b;padding-left:12px;margin-bottom:16px"> 127 <h4 style="color:#f59e0b;margin:0 0 4px">Accettabile</h4> 128 <p style="color:#94a3b8;margin:0;font-size:12px">${r(c||"Non disponibile")}</p> 129 <button class="nc-copy-btn" onclick="NegotiationCopilotModule.applyRedline('${t}','acceptable')" style="margin-top:6px;padding:4px 10px;border:1px solid #f59e0b40;border-radius:4px;background:transparent;color:#f59e0b;font-size:11px;cursor:pointer">Copia</button> 130 </div> 131 <div class="nc-redline-level" style="border-left:3px solid #ef4444;padding-left:12px;margin-bottom:16px"> 132 <h4 style="color:#ef4444;margin:0 0 4px">Minimo accettabile</h4> 133 <p style="color:#94a3b8;margin:0;font-size:12px">${r(p||"Non disponibile")}</p> 134 <button class="nc-copy-btn" onclick="NegotiationCopilotModule.applyRedline('${t}','minimum')" style="margin-top:6px;padding:4px 10px;border:1px solid #ef444440;border-radius:4px;background:transparent;color:#ef4444;font-size:11px;cursor:pointer">Copia</button> 135 </div> 136 </div>`,width:600,showConfirmButton:!1,showCloseButton:!0})}async function z(t,n){if(!o)return;const s=(o.clauses||[]).find(p=>p.id===t);if(!s||!s.redlines)return;const c=s.redlines[n]||"";if(c)try{await navigator.clipboard.writeText(c),window.Swal&&Swal.fire({icon:"success",title:"Copiato!",text:"Testo redline copiato negli appunti",timer:1500,showConfirmButton:!1})}catch{window.Swal&&Swal.fire({icon:"info",title:"Copia manuale",html:`<textarea style="width:100%;height:120px;font-size:12px" readonly>${r(c)}</textarea>`})}}async function B(){await b(),Swal.fire({title:"Caricamento playbook...",allowOutsideClick:!1,didOpen:()=>Swal.showLoading()});try{i=(await x("getPlaybookRules")({contractType:"commerciale"})).data||[],Swal.close(),E()}catch(t){Swal.fire({icon:"error",title:"Errore",text:t.message||"Errore caricamento playbook"})}}function E(){Swal.fire({title:"Playbook Regole",html:` 137 <div style="text-align:left;font-size:13px;max-height:400px;overflow-y:auto"> 138 ${i.length===0?'<p style="color:#667085">Nessuna regola personalizzata. Verranno usate le regole predefinite.</p>':""} 139 ${i.map((t,n)=>` 140 <div style="padding:10px;border:1px solid #2a2d3e;border-radius:8px;margin-bottom:8px;background:#0f111780"> 141 <div style="display:flex;justify-content:space-between;margin-bottom:4px">
142 <span style="font-weight:600;color:#a78bfa">${r(t.clauseType||"")}</span> 143 <span style="font-size:10px;color:${t.severity==="high"?"#ef4444":t.severity==="medium"?"#f59e0b":"#22c55e"}">${(t.severity||"medium").toUpperCase()}</span> 144 </div> 145 <p style="color:#94a3b8;margin:0;font-size:12px">${r(t.rule||"")}</p> 146 <span style="font-size:10px;color:#667085">${r(t.contractType||"all")}</span> 147 </div> 148 `).join("")} 149 </div>`,showCancelButton:!0,confirmButtonText:"Aggiungi regola",cancelButtonText:"Chiudi",width:560}).then(t=>{t.isConfirmed&&L()})}async function L(){const{value:t}=await Swal.fire({title:"Nuova regola playbook",html:` 150 <div style="text-align:left;font-size:13px"> 151 <label style="display:block;margin-bottom:4px;font-weight:600">Tipo contratto</label> 152 <select id="swal-pb-ctype" class="swal2-select" style="width:100%;margin:0 0 12px;padding:8px;border:1px solid #2a2d3e;border-radius:6px;background:#0f1117;color:#c7ccda"> 153 <option value="all">Tutti</option> 154 <option value="commerciale">Commerciale</option> 155 <option value="lavoro">Lavoro</option> 156 <option value="immobiliare">Immobiliare</option> 157 <option value="mandato">Mandato</option> 158 </select> 159 <label style="display:block;margin-bottom:4px;font-weight:600">Tipo clausola</label> 160 <input id="swal-pb-clause" class="swal2-input" placeholder="es. recesso, penale, pagamento" style="width:100%;margin:0 0 12px"> 161 <label style="display:block;margin-bottom:4px;font-weight:600">Regola</label> 162 <textarea id="swal-pb-rule" rows="3" placeholder="Descrivi la regola del playbook..." style="width:100%;padding:8px;border:1px solid #2a2d3e;border-radius:6px;background:#0f1117;color:#c7ccda;font-size:12px;margin:0 0 12px"></textarea> 163 <label style="display:block;margin-bottom:4px;font-weight:600">Severita</label> 164 <select id="swal-pb-severity" class="swal2-select" style="width:100%;margin:0;padding:8px;border:1px solid #2a2d3e;border-radius:6px;background:#0f1117;color:#c7ccda"> 165 <option value="low">Bassa</option> 166 <option value="medium" selected>Media</option> 167 <option value="high">Alta</option> 168 <option value="critical">Critica</option> 169 </select> 170 </div>`,showCancelButton:!0,confirmButtonText:"Salva",cancelButtonText:"Annulla",preConfirm:()=>({contractType:document.getElementById("swal-pb-ctype").value,clauseType:document.getElementById("swal-pb-clause").value.trim(),rule:document.getElementById("swal-pb-rule").value.trim(),severity:document.getElementById("swal-pb-severity").value})});if(!(!t||!t.clauseType||!t.rule))try{await x("savePlaybookRule")(t),Swal.fire({icon:"success",title:"Regola
170salvata",timer:1500,showConfirmButton:!1})}catch(n){Swal.fire({icon:"error",title:"Errore",text:n.message||"Errore salvataggio regola"})}}function D(){a&&(a.remove(),a=null)}function M(){if(document.getElementById("nc-copilot-styles"))return;const t=document.createElement("style");t.id="nc-copilot-styles",t.textContent=` 171 /* Negotiation Copilot Panel */ 172 .nc-panel { position:fixed; top:0; right:0; width:520px; height:100vh; background:#0f1117; border-left:1px solid #1e2030; z-index:9999; overflow-y:auto; padding:20px; box-shadow:-4px 0 24px rgba(0,0,0,.4); } 173 @media (max-width:600px) { .nc-panel { width:100vw; } } 174 .nc-panel-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; } 175 .nc-panel-title { font-size:18px; font-weight:700; color:#e2e8f0; margin:0; } 176 .nc-close-btn { width:32px; height:32px; border-radius:8px; border:1px solid #2a2d3e; background:transparent; color:#667085; font-size:18px; cursor:pointer; display:flex; align-items:center; justify-content:center; } 177 .nc-close-btn:hover { border-color:#ef4444; color:#ef4444; } 178 179 /* Risk Banner */ 180 .nc-risk-banner { display:flex; gap:16px; align-items:center; padding:16px; border-radius:12px; border:1px solid; margin-bottom:16px; } 181 .nc-risk-score { width:56px; height:56px; border-radius:50%; border:3px solid; display:flex; align-items:center; justify-content:center; font-size:22px; font-weight:800; flex-shrink:0; } 182 .nc-risk-score span { font-size:11px; font-weight:400; } 183 .nc-risk-info { flex:1; } 184 .nc-risk-level { font-size:13px; font-weight:700; margin-bottom:4px; } 185 .nc-risk-summary { font-size:12px; color:#94a3b8; } 186 187 /* Conformity Strip */ 188 .nc-conformity-strip { display:flex; gap:6px; margin-bottom:16px; flex-wrap:wrap; } 189 .nc-conf-chip { font-size:11px; padding:4px 10px; border-radius:8px; font-weight:600; white-space:nowrap; } 190 191 /* Vessatorie Banner */ 192 .nc-vessatorie-banner { display:flex; gap:12px; padding:12px 16px; border-radius:10px; background:#ef444410; border:1px solid #ef444430; margin-bottom:16px; } 193 .nc-vess-icon { font-size:24px; flex-shrink:0; } 194 .nc-vess-content strong { color:#ef4444; font-size:13px; } 195 .nc-vess-content p { color:#94a3b8; font-size:12px; margin:4px 0 0; } 196 197 /* Recommendations */ 198 .nc-recommendations { margin-bottom:16px; } 199 .nc-recommendations h4 { font-size:13px; font-weight:600; color:#e2e8f0; margin:0 0 8px; } 200 .nc-recommendations ul { margin:0; padding-left:16px; } 201 .nc-recommendations li { font-size:12px; color:#94a3b8; margin-bottom:4px; } 202 203 /* Clauses List */ 204 .nc-clauses-list h3 { font-size:14px; font-weight:600; color:#e2e8f0; margin:0 0 12px; } 205 .nc-clause-card { padding:12px; border-radius:10px; background:#141620; border:1px solid #1e2030; margin-bottom:8px; } 206 .nc-clause-header { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:8px; } 207 .nc-clause-conformity { font-size:10px; padding:2px 8px; border-radius:8px; font-weight:600; white-space:nowrap; } 208 .nc-clause-type { font-size:10px; color:#8b5cf6; font-weight:600; text-transform:uppercase; } 209 .nc-clause-title { font-size:12px; color:#e2e8f0; font-weight:600; flex:1; } 210 .nc-clause-risk { display:flex; align-items:center; gap:6px; font-size:10px; color:#667085; } 211 .nc-risk-bar { width:40px; height:4px; border-radius:2px; background:#1e2030; overflow:hidden; } 212 .nc-risk-fill { height:100%; border-radius:2px; transition:width .3s; } 213 .nc-clause-body { font-size:12px; color:#94a3b8; } 214 .nc-clause-original, .nc-clause-deviation, .nc-clause-playbook { margin-bottom:6px; } 215 .nc-clause-deviation { color:#f59e0b; } 216 .nc-clause-playbook { color:#6b7280; font-style:italic; } 217 .nc-clause-actions { margin-top:8px; } 218 219 /* Vessatorie Detail */ 220 .nc-vess-detail h3 { font-size:14px; font-weight:600; color:#ef4444; margin:16px 0 12px; } 221 .nc-vess-card { padding:12px; border-radius:10px; background:#ef444408; border:1px solid #ef444420; margin-bottom:8px; } 222 .nc-vess-card-header { display:flex; gap:8px; align-items:center; margin-bottom:6px; } 223 .nc-vess-severity { font-size:10px; padding:2px 8px; border-radius:4px; font-weight:700; } 224 .nc-vess-severity.high { background:#ef444420; color:#ef4444; } 225 .nc-vess-severity.critical { background:#dc262620; color:#dc2626; } 226 .nc-vess-article { font-size:11px; color:#667085; } 227 .nc-vess-type { font-size:12px; color:#e2e8f0; margin:0 0 4px; } 228 .nc-vess-desc { font-size:12px; color:#94a3b8; margin:0 0 4px; } 229 .nc-vess-approval { font-size:11px; color:#f59e0b; font-weight:600; margin:0; } 230 231 /* Buttons */ 232 .nc-btn { padding:8px 16px; border-radius:8px; font-size:12px; font-weight:600; cursor:pointer; border:none; transition:all .15s; } 233 .nc-btn-primary { background:#5D1ADB; color:#fff; } 234 .nc-btn-primary:hover { background:#4c15b5; } 235 .nc-btn-secondary { background:transparent; color:#a78bfa; border:1px solid #5D1ADB40; } 236 .nc-btn-secondary:hover { background:#5D1ADB10; } 237 .nc-btn-sm { padding:4px 10px; font-size:11px; border-radius:6px; background:#5D1ADB20; color:#a78bfa; border:1px solid #5D1ADB40; cursor:pointer; } 238 .nc-btn-sm:hover { background:#5D1ADB30; } 239 .nc-panel-footer { display:flex; gap:8px; margin-top:20px; padding-top:16px; border-top:1px solid #1e2030; } 240 `,document.head.appendChild(t)}window.NegotiationCopilotModule={init:b,uploadContract:v,renderAnalysis:k,showRedlines:I,applyRedline:z,closePanel:D,managePlaybook:B}})(),window.AuditLog={async log(e,o,i,f={}){try{const a=window.DbHelper?.getStudioId?.();if(!a)return;const l=firebase.auth().currentUser,u=l?{uid:l.uid,email:l.email||"",name:l.displayName||l.email||"Utente"}:{uid:"anonymous",email:"",name:"Anonimo"};await firebase.firestore().collection("studios").doc(a).collection("audit_trail").add({action:e,category:o||"system",details:i||"",actor:u,metadata:f||{},timestamp:firebase.firestore.FieldValue.serverTimestamp(),createdAt:new Date().toISOString()})}catch(a){console.warn("[AuditLog]",a.message)}},auth(e,o,i){return this.log(e,"auth",o,i)},client(e,o,i){return this.log(e,"client",o,i)},document(e,o,i){return this.log(e,"document",o,i)},email(e,o,i){return this.log(e,"email",o,i)},deadline(e,o,i){return this.log(e,"deadline",o,i)},connection(e,o,i){return this.log(e,"connection",o,i)},team(e,o,i){return this.log(e,"team",o,i)},settings(e,o,i){return this.log(e,"settings",o,i)},archive(e,o,i){return this.log(e,"archive",o,i)},chat(e,o,i){return this.log(e,"chat",o,i)}},console.log("\u{1F4CB} AuditLog ready"),window.CloudMapViewer={currentProvider:"google_drive",currentMap:null,studioId:null,
240searchQuery:"",hiddenFolderIds:new Set,_esc(e){return String(e||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">").replace(/'/g,"'")},async init(){const e=window.DbHelper?.getStudioContext?.();this.studioId=e?.studioId||window.DbHelper?.getStudioId?.()||window.userConfig?.studioId||null,console.log("\u{1F5FA}\uFE0F CloudMapViewer initialized, studioId:",this.studioId),await this.loadMap()},async loadMap(){if(this.studioId)try{const e=await firebase.firestore().collection("studios").doc(this.studioId).collection("cloud_map").doc(this.currentProvider).get();try{const o=await firebase.firestore().collection("studios").doc(this.studioId).collection("config").doc("hidden_folders").get();this.hiddenFolderIds=new Set((o.exists?o.data().folders||[]:[]).map(i=>i.folderId))}catch{this.hiddenFolderIds=new Set}e.exists?(this.currentMap=e.data(),this.renderStats(),this.renderTree()):this.showEmptyState()}catch(e){console.error("Load map error:",e),this.showEmptyState()}},setProvider(e){this.currentProvider=e,document.querySelectorAll(".map-provider-btn").forEach(i=>{i.classList.remove("active","bg-white","text-slate-700","shadow-sm"),i.classList.add("text-slate-500")});const o=document.getElementById(`map-provider-${e}`);o&&(o.classList.add("active","bg-white","text-slate-700","shadow-sm"),o.classList.remove("text-slate-500")),this.loadMap()},async startScan(){if(!this.studioId){Swal.fire({icon:"error",title:"Errore",text:"Sessione non valida"});return}this.showLoading();try{const o=await firebase.app().functions("europe-west1").httpsCallable("mapCloudStorage")({studioId:this.studioId,provider:this.currentProvider,maxDepth:5,includeFiles:!0});console.log("\u{1F5FA}\uFE0F Scan complete:",o.data),o.data&&o.data.tree&&(this.currentMap={tree:o.data.tree,stats:o.data.stats,provider:o.data.provider,lastScan:new Date},this.renderStats(),this.renderTree()),Swal.fire({icon:"success",title:"Scansione Completata!",html:` 241 <div class="text-left"> 242 <p class="mb-2">\u{1F4C1} <strong>${o.data.stats?.folders||0}</strong> cartelle trovate</p> 243 <p class="mb-2">\u{1F4C4} <strong>${o.data.stats?.files||0}</strong> file trovati</p> 244 <p>\u{1F4BE} Dimensione totale: <strong>${o.data.stats?.totalSize||"0 B"}</strong></p> 245 </div> 246 `,confirmButtonColor:"#10b981"})}catch(e){console.error("Scan error:",e),Swal.fire({icon:"error",title:"Errore Scansione",text:e.message||"Errore durante la scansione del cloud storage"}),this.showEmptyState()}},renderStats(){if(!this.currentMap)return;const e=this.currentMap.stats||{};document.getElementById("stat-folders").textContent=e.folders||0,document.getElementById("stat-files").textContent=e.files||0,document.getElementById("stat-size").textContent=e.totalSize||"0 B";const o=this.currentMap.lastScan;if(o){const i=o.toDate?o.toDate():new Date(o);document.getElementById("stat-last-scan").textContent=i.toLocaleDateString("it-IT")+" "+i.toLocaleTimeString("it-IT",{hour:"2-digit",minute:"2-digit"})}},renderTree(){const e=document.getElementById("tree-content"),o=document.getElementById("tree-empty"),i=document.getElementById("tree-loading");if(!this.currentMap||!this.currentMap.tree){this.showEmptyState();return}o.classList.add("hidden"),i.classList.add("hidden"),e.classList.remove("hidden");const f=this.currentMap.tree;e.innerHTML=this.renderNode(f,0),e.querySelectorAll(".tree-toggle").forEach(a=>{a.addEventListener("click",l=>{l.stopPropagation(),a.closest(".tree-node").classList.toggle("collapsed")})}),e.querySelectorAll(".tree-node-content").forEach(a=>{const l=a.querySelector(".hide-folder-btn");l&&(a.addEventListener("mouseenter",()=>l.style.opacity="1"),a.addEventListener("mouseleave",()=>l.style.opacity="0"))}),e.querySelectorAll(".hide-folder-btn").forEach(a=>{a.addEventListener("click",async l=>{l.stopPropagation();const u=a.dataset.id,r=a.dataset.name;if(!u)return;const x=this.studioId;if(x)try{const m=firebase.firestore().collection("studios").doc(x).collection("config").doc("hidden_folders"),b=await m.get(),v=b.exists?b.data().folders||[]:[];if(v.some(w=>w.folderId===u))return;v.push({folderId:u,name:r,provider:this.currentProvider||"unknown",hiddenAt:new Date().toISOString()}),await m.set({folders:v},{merge:!0}),this.hiddenFolderIds.add(u),this.renderTree(),typeof Swal<"u"&&Swal.mixin({toast:!0,position:"top-end",showConfirmButton:!1,timer:2e3}).fire({icon:"success",text:'Cartella "'+r+'" nascosta'})}catch(m){console.error("Hide folder error:",m)}})}),typeof LineIcons<"u"&&LineIcons.createIcons()},renderNode(e,o){if(!e||this.hiddenFolderIds.has(e.id))return"";const i=e.type==="folder",f=o*20,a=e.children&&e.children.length>0;let l="file",u="text-slate-400";if(i)l="folder",u="text-amber-500";else{const m=e.name?.split(".").pop()?.toLowerCase();["pdf"].includes(m)?(l="file-text",u="text-red-500"):["doc","docx"].includes(m)?(l="file-text",u="text-blue-500"):["xls","xlsx","csv"].includes(m)?(l="file-spreadsheet",u="text-green-500"):["jpg","jpeg","png","gif"].includes(m)&&(l="image",u="text-purple-500")}let r=e.name||"Unnamed";if(this.searchQuery&&r.toLowerCase().includes(this.searchQuery.toLowerCase())){const m=this.searchQuery.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b=new RegExp(`(${m})`,"gi");r=r.replace(b,'<mark class="bg-yellow-200 px-0.5 rounded">$1</mark>')}let x=` 247 <div class="tree-node ${i?"folder":"file"}" data-id="${this._esc(e.id)}" data-path="${this._esc(e.path)}" style="padding-left: ${f}px;"> 248 <div class="tree-node-content flex items-center gap-2 py-1.5 px-2 rounded-lg hover:bg-slate-50 cursor-pointer transition-colors"> 249 ${i&&a?` 250 <button class="tree-toggle w-5 h-5 flex items-center justify-center text-slate-400 hover:text-slate-600 transition-colors"> 251 <i data-line-icon="chevron-down" class="w-4 h-4 tree-chevron"></i> 252 </button> 253 `:'<span class="w-5"></span>'} 254 <i data-line-icon="${l}" class="w-4 h-4 ${u} flex-shrink-0"></i>
255 <span class="tree-name text-sm ${i?"font-medium text-slate-700":"text-slate-600"} truncate">${r}</span> 256 ${!i&&e.size?`<span class="text-[10px] text-slate-400 ml-auto">${this.formatBytes(e.size)}</span>`:""} 257 ${i&&a?`<span class="text-[10px] text-slate-400 ml-auto">${e.children.length}</span>`:""} 258 ${i?`<button class="hide-folder-btn" data-id="${this._esc(e.id)}" data-name="${this._esc(e.name)}" 259 style="opacity:0;transition:opacity 0.15s;padding:2px 6px;border:none;background:transparent;color:#94a3b8;cursor:pointer;font-size:11px;border-radius:4px;${a?"":"margin-left:auto;"}" 260 title="Nascondi"> 261 <i data-line-icon="eye-off" class="w-3 h-3"></i> 262 </button>`:""} 263 </div> 264 `;if(a){x+='<div class="tree-children">';for(const m of e.children)x+=this.renderNode(m,o+1);x+="</div>"}return x+="</div>",x},searchMap(e){if(this.searchQuery=e.trim(),!this.currentMap)return;if(!this.searchQuery){this.renderTree();return}this.renderTree(),document.getElementById("tree-content").querySelectorAll(".tree-node").forEach(f=>{const l=(f.querySelector(".tree-name")?.textContent||"").toLowerCase().includes(this.searchQuery.toLowerCase()),u=f.querySelector("mark")!==null;if(l||u){f.classList.remove("collapsed"),f.style.display="";let r=f.parentElement?.closest(".tree-node");for(;r;)r.classList.remove("collapsed"),r.style.display="",r=r.parentElement?.closest(".tree-node")}else u||(f.style.display="none")})},showLoading(){document.getElementById("tree-empty")?.classList.add("hidden"),document.getElementById("tree-content")?.classList.add("hidden"),document.getElementById("tree-loading")?.classList.remove("hidden"),typeof LineIcons<"u"&&LineIcons.createIcons()},showEmptyState(){document.getElementById("tree-loading")?.classList.add("hidden"),document.getElementById("tree-content")?.classList.add("hidden"),document.getElementById("tree-empty")?.classList.remove("hidden"),typeof LineIcons<"u"&&LineIcons.createIcons()},formatBytes(e){if(!e||e===0)return"0 B";const o=1024,i=["B","KB","MB","GB"],f=Math.floor(Math.log(e)/Math.log(o));return parseFloat((e/Math.pow(o,f)).toFixed(1))+" "+i[f]}},document.addEventListener("DOMContentLoaded",()=>{}),console.log("\u{1F5FA}\uFE0F CloudMapViewer component loaded");
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.