PageSourceSearch

https://prisma-optlyx.web.app/dist/shell-index-4.18228a98.js

js prisma-optlyx.web.app collected 2026-10-03 12:34:00 UTC 34,082 bytes, 264 lines download raw bytes

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=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[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()">&times;</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">&#9888;</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,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#39;")},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.