feat: potenziata visualizzazione storico consuntivazione. style: disabilitata apertura massiva. feat: migliorata modifica pdf e scaricamento di tutti gli allegati tramite shortcut.

This commit is contained in:
Gabriele Cimaschi committed 2026-09-10 01:02:25 +02:00
1 parent b1754c3a67
commit c7b18ac959
10 files changed
+1545 -191

No files matched your search

+86 -39
View File
@@ -30,6 +30,11 @@ const UserConfigView = {
},
renderUI(container, signatures = [], shapes = []) {
const activeAgentId = App.currentAgentId;
const activeUser = (App.lookups.users || []).find(u => String(u.id) === String(activeAgentId));
const agentName = activeUser ? `${activeUser.first_name} ${activeUser.last_name}`.trim() : '';
const placeholderText = agentName ? `Es. Firma ${agentName}, Timbro Aziendale...` : 'Es. Firma Utente, Timbro Aziendale...';
container.innerHTML = `
<div class="user-config-page">
<!-- Section 1: Custom Shapes -->
@@ -43,46 +48,42 @@ const UserConfigView = {
<h4 style="margin-top:0; margin-bottom:12px; font-size:0.95rem; color:var(--text-primary);">Crea Nuova Forma Personalizzata</h4>
<div style="display:grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap:12px; align-items:flex-end;">
<div>
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Nome Forma</label>
<input type="text" id="shape-name" class="form-input" placeholder="Es. Evidenziatore Blu, Timbro Approvato..." style="height:34px; font-size:0.85rem;" />
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Nome Identificativo</label>
<input type="text" id="user-shape-name" class="form-input" placeholder="Es. Rettangolo Blu Evidenziatore" style="height:34px; font-size:0.85rem;" />
</div>
<div>
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Tipo Forma</label>
<select id="shape-type" class="form-select" style="height:34px; font-size:0.85rem;">
<option value="rectangle">🟦 Rettangolo</option>
<option value="circle">⚪ Cerchio / Ellisse</option>
<select id="user-shape-type" class="form-select" style="height:34px; font-size:0.85rem;">
<option value="rect">Rettangolo</option>
<option value="circle">Cerchio / Ellisse</option>
<option value="line">Linea Rettilinea</option>
<option value="arrow">Freccia di Indicazione</option>
</select>
</div>
<div>
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Colore Riempimento</label>
<div style="display:flex; align-items:center; gap:6px;">
<input type="color" id="shape-fill-color" value="#3b82f6" style="width:34px; height:34px; border:none; border-radius:4px; cursor:pointer; background:none;" />
<label style="font-size:0.78rem; color:var(--text-muted); cursor:pointer;"><input type="checkbox" id="shape-transparent-fill" /> Trasparente</label>
</div>
<input type="color" id="user-shape-fill" value="#3b82f6" style="width:100%; height:34px; border:1px solid var(--border-subtle); border-radius:4px; cursor:pointer; background:none;" />
</div>
<div>
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Colore Bordo</label>
<input type="color" id="shape-border-color" value="#1e3a8a" style="width:34px; height:34px; border:none; border-radius:4px; cursor:pointer; background:none;" />
<input type="color" id="user-shape-border" value="#1e3a8a" style="width:100%; height:34px; border:1px solid var(--border-subtle); border-radius:4px; cursor:pointer; background:none;" />
</div>
<div>
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Spessore Bordo (px)</label>
<input type="number" id="shape-border-width" min="0" max="30" value="3" class="form-input" style="height:34px; font-size:0.85rem;" />
<input type="number" id="user-shape-width" min="0" max="30" value="3" class="form-input" style="height:34px; font-size:0.85rem;" />
</div>
<div style="display:flex; gap:8px;">
<button id="btn-save-shape" class="btn btn-primary btn-sm" style="height:34px; padding:0 16px; font-weight:600; display:inline-flex; align-items:center; gap:6px;">
⭐ Salva Forma
</button>
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
<input type="checkbox" id="user-shape-fav" style="cursor:pointer;" />
<label for="user-shape-fav" style="font-size:0.8rem; cursor:pointer; color:var(--text-primary); font-weight:600;">⭐ Preferito</label>
</div>
<button id="btn-save-user-shape" class="btn btn-primary btn-sm" style="height:34px; padding:0 16px; font-weight:600; display:inline-flex; align-items:center; gap:6px;">
➕ Salva Forma
</button>
</div>
</div>
<!-- Shapes Gallery -->
<div style="display:grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap:14px;">
<!-- Shapes Gallery Grid -->
<div id="user-shapes-list" style="display:grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap:12px;">
${shapes.length > 0 ? shapes.map(sh => `
<div class="user-shape-card" style="background:var(--bg-card); border:1px solid var(--border-subtle); border-radius:var(--radius-md); padding:12px; display:flex; flex-direction:column; position:relative; box-shadow:var(--shadow-sm);">
<div style="position:absolute; top:8px; right:8px; display:flex; gap:4px;">
@@ -90,15 +91,36 @@ const UserConfigView = {
<button class="btn-delete-shape" data-id="${sh.id}" style="background:var(--bg-tertiary); border:1px solid var(--border-subtle); color:var(--danger); border-radius:50%; width:24px; height:24px; cursor:pointer; display:flex; align-items:center; justify-content:center; font-size:11px;" title="Elimina Forma">✕</button>
</div>
<div style="width:100%; height:70px; display:flex; align-items:center; justify-content:center; background:var(--bg-tertiary); border-radius:var(--radius-sm); margin-bottom:8px; border:1px dashed var(--border-subtle);">
<div style="width:50px; height:34px; border-radius:${sh.shape_type === 'circle' ? '50%' : '3px'}; background:${sh.fill_color === 'transparent' ? 'transparent' : sh.fill_color}; border:${sh.border_width}px solid ${sh.border_color}; box-sizing:border-box;"></div>
<div style="width:100%; height:70px; display:flex; align-items:center; justify-content:center; background:var(--bg-tertiary); border-radius:var(--radius-sm); margin-bottom:8px; border:1px dashed var(--border-subtle); padding:4px;">
${(() => {
const st = sh.shape_type;
const fill = sh.fill_color === 'transparent' ? 'none' : sh.fill_color;
const stroke = sh.border_color || '#3b82f6';
const sw = sh.border_width || 3;
if (st === 'circle') {
return `<svg width="60" height="50" viewBox="0 0 60 50"><ellipse cx="30" cy="25" rx="22" ry="18" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" /></svg>`;
} else if (st === 'line') {
return `<svg width="60" height="50" viewBox="0 0 60 50"><line x1="8" y1="42" x2="52" y2="8" stroke="${stroke}" stroke-width="${Math.max(sw, 2)}" stroke-linecap="round" /></svg>`;
} else if (st === 'arrow') {
return `<svg width="60" height="50" viewBox="0 0 60 50">
<defs>
<marker id="arr-${sh.id}" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="${stroke}" />
</marker>
</defs>
<line x1="8" y1="40" x2="48" y2="12" stroke="${stroke}" stroke-width="${Math.max(sw, 2)}" stroke-linecap="round" marker-end="url(#arr-${sh.id})" />
</svg>`;
} else {
return `<svg width="60" height="50" viewBox="0 0 60 50"><rect x="8" y="10" width="44" height="30" rx="3" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" /></svg>`;
}
})()}
</div>
<div style="font-weight:600; font-size:0.85rem; color:var(--text-primary); margin-bottom:4px;">
${App.escapeHtml(sh.name)}
</div>
<div style="font-size:0.74rem; color:var(--text-muted);">
${sh.shape_type === 'circle' ? 'Cerchio' : 'Rettangolo'} • Bordo ${sh.border_width}px
${sh.shape_type === 'circle' ? 'Cerchio' : sh.shape_type === 'line' ? 'Linea' : sh.shape_type === 'arrow' ? 'Freccia' : 'Rettangolo'} • Bordo ${sh.border_width}px
</div>
</div>
`).join('') : `
@@ -119,14 +141,18 @@ const UserConfigView = {
<div style="background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 16px; margin-bottom: 20px;">
<h4 style="margin-top:0; margin-bottom:12px; font-size:0.95rem; color:var(--text-primary);">Aggiungi nuova immagine / firma</h4>
<div style="display:flex; gap:12px; align-items:flex-end; flex-wrap:wrap;">
<div style="flex:1; min-width:220px;">
<div style="flex:1; min-width:200px;">
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Nome Firma / Immagine</label>
<input type="text" id="user-sig-name" class="form-input" placeholder="Es. Firma Gabriele Cimaschi, Timbro Aziendale..." style="height:34px; font-size:0.85rem;" />
<input type="text" id="user-sig-name" class="form-input" placeholder="${App.escapeHtml(placeholderText)}" style="height:34px; font-size:0.85rem;" />
</div>
<div style="flex:1; min-width:220px;">
<div style="flex:1; min-width:200px;">
<label style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-bottom:4px; display:block;">Seleziona File Immagine (PNG, JPG, SVG)</label>
<input type="file" id="user-sig-file" accept="image/*" class="form-input" style="height:34px; padding:3px 8px; font-size:0.8rem;" />
</div>
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
<input type="checkbox" id="user-sig-default" style="cursor:pointer;" />
<label for="user-sig-default" style="font-size:0.8rem; cursor:pointer; color:var(--text-primary); font-weight:600;">⭐ Firma Predefinita</label>
</div>
<button id="btn-save-user-sig" class="btn btn-primary btn-sm" style="height:34px; padding:0 16px; font-weight:600; display:inline-flex; align-items:center; gap:6px;">
➕ Salva in SQLite
</button>
@@ -136,9 +162,14 @@ const UserConfigView = {
<!-- Signatures Gallery Grid -->
<div id="user-sig-grid" style="display:grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap:16px;">
${signatures.length > 0 ? signatures.map(sig => `
<div class="user-sig-card" style="background:var(--bg-card); border:1px solid var(--border-subtle); border-radius:var(--radius-md); padding:12px; display:flex; flex-direction:column; align-items:center; position:relative; box-shadow:var(--shadow-sm);">
<div class="user-sig-card" style="background:var(--bg-card); border:${sig.is_default ? '2px solid var(--accent-primary)' : '1px solid var(--border-subtle)'}; border-radius:var(--radius-md); padding:12px; display:flex; flex-direction:column; align-items:center; position:relative; box-shadow:var(--shadow-sm);">
<div style="position:absolute; top:8px; left:8px; display:flex; gap:4px; align-items:center;">
<button class="btn-default-sig" data-id="${sig.id}" style="background:${sig.is_default ? 'var(--accent-primary)' : 'var(--bg-tertiary)'}; border:1px solid var(--border-subtle); color:${sig.is_default ? '#fff' : 'var(--text-secondary)'}; border-radius:12px; padding:2px 8px; cursor:pointer; font-size:0.7rem; font-weight:600;" title="${sig.is_default ? 'Firma Predefinita attuale' : 'Imposta come predefinita'}">
${sig.is_default ? '⭐ Predefinita' : '☆ Imposta Predefinita'}
</button>
</div>
<button class="btn-delete-sig" data-id="${sig.id}" style="position:absolute; top:8px; right:8px; background:var(--bg-tertiary); border:1px solid var(--border-subtle); color:var(--danger); border-radius:50%; width:26px; height:26px; cursor:pointer; display:flex; align-items:center; justify-content:center; font-size:12px;" title="Elimina Firma">✕</button>
<div style="width:100%; height:110px; display:flex; align-items:center; justify-content:center; background:#ffffff; border-radius:var(--radius-sm); border:1px solid var(--border-subtle); padding:8px; margin-bottom:8px;">
<div style="width:100%; height:110px; display:flex; align-items:center; justify-content:center; background:#ffffff; border-radius:var(--radius-sm); border:1px solid var(--border-subtle); padding:8px; margin-bottom:8px; margin-top:24px;">
<img src="${sig.image_data}" alt="${App.escapeHtml(sig.name)}" style="max-width:100%; max-height:100%; object-fit:contain;" />
</div>
<div style="font-weight:600; font-size:0.85rem; color:var(--text-primary); text-align:center; word-break:break-word;">
@@ -163,15 +194,15 @@ const UserConfigView = {
bindEvents(container) {
// Shapes handlers
const btnSaveShape = container.querySelector('#btn-save-shape');
const btnSaveShape = container.querySelector('#btn-save-user-shape');
if (btnSaveShape) {
btnSaveShape.addEventListener('click', async () => {
const name = container.querySelector('#shape-name').value.trim();
const shapeType = container.querySelector('#shape-type').value;
const isTransparent = container.querySelector('#shape-transparent-fill').checked;
const fillColor = isTransparent ? 'transparent' : container.querySelector('#shape-fill-color').value;
const borderColor = container.querySelector('#shape-border-color').value;
const borderWidth = parseInt(container.querySelector('#shape-border-width').value || 3, 10);
const name = container.querySelector('#user-shape-name').value.trim();
const shapeType = container.querySelector('#user-shape-type').value;
const fillColor = container.querySelector('#user-shape-fill').value;
const borderColor = container.querySelector('#user-shape-border').value;
const borderWidth = parseInt(container.querySelector('#user-shape-width').value || 3, 10);
const isFavorite = container.querySelector('#user-shape-fav').checked;
if (!name) {
Toast.warning('Inserisci un nome per la forma.');
@@ -188,7 +219,7 @@ const UserConfigView = {
fill_color: fillColor,
border_color: borderColor,
border_width: borderWidth,
is_favorite: 1
is_favorite: isFavorite ? 1 : 0
})
});
Toast.success('Forma salvata!');
@@ -232,11 +263,13 @@ const UserConfigView = {
const btnSave = container.querySelector('#btn-save-user-sig');
const inputName = container.querySelector('#user-sig-name');
const inputFile = container.querySelector('#user-sig-file');
const checkDefault = container.querySelector('#user-sig-default');
if (btnSave && inputFile) {
btnSave.addEventListener('click', async () => {
const name = inputName.value.trim();
const file = inputFile.files[0];
const isDefault = checkDefault ? checkDefault.checked : false;
if (!name) {
Toast.warning('Inserisci un nome descrittivo per la firma/immagine.');
@@ -258,7 +291,8 @@ const UserConfigView = {
method: 'POST',
body: JSON.stringify({
name,
image_data: base64Data
image_data: base64Data,
is_default: isDefault ? 1 : 0
})
});
@@ -274,6 +308,19 @@ const UserConfigView = {
});
}
container.querySelectorAll('.btn-default-sig').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
try {
await App.api(`/api/config/user-signatures/${btn.dataset.id}/set-default`, { method: 'PUT' });
Toast.success('Firma predefinita aggiornata!');
this.render();
} catch (err) {
Toast.error('Errore aggiornamento firma predefinita: ' + err.message);
}
});
});
container.querySelectorAll('.btn-delete-sig').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();