295 lines
15 KiB
JavaScript
295 lines
15 KiB
JavaScript
/**
|
||
* userConfig.js
|
||
* User Configurations View: Upload, preview and manage custom signature images stored in SQLite.
|
||
*/
|
||
const UserConfigView = {
|
||
async render() {
|
||
const container = document.getElementById('view-container');
|
||
container.innerHTML = '<div class="loading-screen"><div class="spinner"></div><p>Caricamento configurazioni utente...</p></div>';
|
||
|
||
const titleEl = document.getElementById('page-title');
|
||
if (titleEl) {
|
||
titleEl.textContent = 'Configurazioni Utente';
|
||
}
|
||
|
||
try {
|
||
const [signatures, shapes] = await Promise.all([
|
||
App.api('/api/config/user-signatures'),
|
||
App.api('/api/config/user-shapes')
|
||
]);
|
||
this.renderUI(container, signatures, shapes);
|
||
} catch (err) {
|
||
container.innerHTML = `
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">⚠️</div>
|
||
<div class="empty-state-text">Errore caricamento configurazioni utente</div>
|
||
<div class="empty-state-sub">${App.escapeHtml(err.message)}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
},
|
||
|
||
renderUI(container, signatures = [], shapes = []) {
|
||
container.innerHTML = `
|
||
<div class="user-config-page">
|
||
<!-- Section 1: Custom Shapes -->
|
||
<div class="card" style="margin-bottom: var(--space-lg);">
|
||
<div class="card-title" style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||
<span>📐 Forme Personalizzate & Preferiti</span>
|
||
<span style="font-size:0.8rem; font-weight:normal; color:var(--text-muted);">Configura forme salvate per l'editor PDF</span>
|
||
</div>
|
||
|
||
<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);">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;" />
|
||
</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>
|
||
</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>
|
||
</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;" />
|
||
</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;" />
|
||
</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>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Shapes Gallery -->
|
||
<div style="display:grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap:14px;">
|
||
${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;">
|
||
<button class="btn-fav-shape" data-id="${sh.id}" style="background:none; border:none; cursor:pointer; font-size:16px;" title="Aggiungi/Rimuovi Preferito">${sh.is_favorite ? '⭐' : '☆'}</button>
|
||
<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>
|
||
|
||
<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
|
||
</div>
|
||
</div>
|
||
`).join('') : `
|
||
<div style="grid-column: 1 / -1; padding:20px; text-align:center; color:var(--text-muted); font-style:italic;">
|
||
Nessuna forma personalizzata salvata.
|
||
</div>
|
||
`}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 2: Signatures -->
|
||
<div class="card" style="margin-bottom: var(--space-lg);">
|
||
<div class="card-title" style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||
<span>✍️ Immagini e Firme Utente</span>
|
||
<span style="font-size:0.8rem; font-weight:normal; color:var(--text-muted);">Saranno disponibili per l'apposizione rapida sui PDF</span>
|
||
</div>
|
||
|
||
<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;">
|
||
<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;" />
|
||
</div>
|
||
<div style="flex:1; min-width:220px;">
|
||
<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>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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);">
|
||
<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;">
|
||
<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;">
|
||
${App.escapeHtml(sig.name)}
|
||
</div>
|
||
<div style="font-size:0.72rem; color:var(--text-muted); margin-top:2px;">
|
||
${App.formatDateTime(sig.created_at)}
|
||
</div>
|
||
</div>
|
||
`).join('') : `
|
||
<div style="grid-column: 1 / -1; padding:32px; text-align:center; color:var(--text-muted); font-style:italic;">
|
||
Nessuna firma salvata. Carica la prima immagine con il modulo qui sopra.
|
||
</div>
|
||
`}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
this.bindEvents(container);
|
||
},
|
||
|
||
bindEvents(container) {
|
||
// Shapes handlers
|
||
const btnSaveShape = container.querySelector('#btn-save-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);
|
||
|
||
if (!name) {
|
||
Toast.warning('Inserisci un nome per la forma.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
btnSaveShape.disabled = true;
|
||
await App.api('/api/config/user-shapes', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
name,
|
||
shape_type: shapeType,
|
||
fill_color: fillColor,
|
||
border_color: borderColor,
|
||
border_width: borderWidth,
|
||
is_favorite: 1
|
||
})
|
||
});
|
||
Toast.success('Forma salvata!');
|
||
this.render();
|
||
} catch (err) {
|
||
Toast.error('Errore salvataggio forma: ' + err.message);
|
||
btnSaveShape.disabled = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
container.querySelectorAll('.btn-fav-shape').forEach(btn => {
|
||
btn.addEventListener('click', async (e) => {
|
||
e.stopPropagation();
|
||
try {
|
||
await App.api(`/api/config/user-shapes/${btn.dataset.id}/toggle-favorite`, { method: 'PUT' });
|
||
this.render();
|
||
} catch (err) {
|
||
Toast.error('Errore aggiornamento preferiti: ' + err.message);
|
||
}
|
||
});
|
||
});
|
||
|
||
container.querySelectorAll('.btn-delete-shape').forEach(btn => {
|
||
btn.addEventListener('click', async (e) => {
|
||
e.stopPropagation();
|
||
const ok = await App.confirm('Elimina Forma', 'Eliminare questa forma personalizzata?', { confirmText: 'Elimina', cancelText: 'Annulla' });
|
||
if (ok) {
|
||
try {
|
||
await App.api(`/api/config/user-shapes/${btn.dataset.id}`, { method: 'DELETE' });
|
||
Toast.success('Forma eliminata!');
|
||
this.render();
|
||
} catch (err) {
|
||
Toast.error('Errore eliminazione: ' + err.message);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Signatures handlers
|
||
const btnSave = container.querySelector('#btn-save-user-sig');
|
||
const inputName = container.querySelector('#user-sig-name');
|
||
const inputFile = container.querySelector('#user-sig-file');
|
||
|
||
if (btnSave && inputFile) {
|
||
btnSave.addEventListener('click', async () => {
|
||
const name = inputName.value.trim();
|
||
const file = inputFile.files[0];
|
||
|
||
if (!name) {
|
||
Toast.warning('Inserisci un nome descrittivo per la firma/immagine.');
|
||
return;
|
||
}
|
||
if (!file) {
|
||
Toast.warning('Seleziona un file immagine.');
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = async () => {
|
||
const base64Data = reader.result;
|
||
try {
|
||
btnSave.disabled = true;
|
||
btnSave.textContent = 'Salvataggio...';
|
||
|
||
const res = await App.api('/api/config/user-signatures', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
name,
|
||
image_data: base64Data
|
||
})
|
||
});
|
||
|
||
Toast.success(res.message || 'Firma salvata!');
|
||
this.render();
|
||
} catch (err) {
|
||
Toast.error('Errore durante il salvataggio: ' + err.message);
|
||
btnSave.disabled = false;
|
||
btnSave.textContent = '➕ Salva in SQLite';
|
||
}
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
container.querySelectorAll('.btn-delete-sig').forEach(btn => {
|
||
btn.addEventListener('click', async (e) => {
|
||
e.stopPropagation();
|
||
const id = btn.dataset.id;
|
||
const ok = await App.confirm('Elimina Firma', 'Sei sicuro di voler eliminare questa firma dal database?', { confirmText: 'Elimina', cancelText: 'Annulla' });
|
||
if (ok) {
|
||
try {
|
||
await App.api(`/api/config/user-signatures/${id}`, { method: 'DELETE' });
|
||
Toast.success('Firma eliminata!');
|
||
this.render();
|
||
} catch (err) {
|
||
Toast.error('Errore eliminazione: ' + err.message);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
};
|