[I18N] Modulo Fornitori bilingue IT/EN (#8b) — v1.24.5
- supplier-portal.html (portale esterno OTP): i18n self-contained (dizionario inline + toggle IT/EN, ~79 stringhe), flusso OTP/token/ID/API invariato; domande questionario restano dati (non tradotte). - supply-chain.html (interno): i18n estesa (5->25 data-i18n + 97 I18n.t). - i18n.js: +82 chiavi sp.* (38->120). Cache-buster i18n.js su supply-chain.html. node --check OK, pagine 200, parita IT/EN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
14c4c2a7d2
commit
05a1436d8c
+118
-119
@@ -399,44 +399,44 @@
|
||||
<div class="risk-overview" id="risk-overview">
|
||||
<div class="risk-card">
|
||||
<div class="risk-card-value" id="stat-total">--</div>
|
||||
<div class="risk-card-label">Totale Fornitori</div>
|
||||
<div class="risk-card-label" data-i18n="sp.stat_total">Totale Fornitori</div>
|
||||
</div>
|
||||
<div class="risk-card">
|
||||
<div class="risk-card-value danger" id="stat-critical">--</div>
|
||||
<div class="risk-card-label">Critici Non Conformi</div>
|
||||
<div class="risk-card-label" data-i18n="sp.stat_critical">Critici Non Conformi</div>
|
||||
</div>
|
||||
<div class="risk-card">
|
||||
<div class="risk-card-value warning" id="stat-expiring">--</div>
|
||||
<div class="risk-card-label">Contratti in Scadenza</div>
|
||||
<div class="risk-card-label" data-i18n="sp.stat_expiring">Contratti in Scadenza</div>
|
||||
</div>
|
||||
<div class="risk-card">
|
||||
<div class="risk-card-value success" id="stat-avg-score">--</div>
|
||||
<div class="risk-card-label">Score Rischio Medio</div>
|
||||
<div class="risk-card-label" data-i18n="sp.stat_avg_score">Score Rischio Medio</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Criticality Chart -->
|
||||
<div class="card mb-24">
|
||||
<div class="card-header">
|
||||
<h3>Distribuzione Criticita'</h3>
|
||||
<h3 data-i18n="sp.crit_distribution">Distribuzione Criticita'</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="criticality-chart" id="criticality-chart">
|
||||
<div class="chart-bar-group">
|
||||
<div class="chart-bar" id="chart-bar-critical" style="height:0;background:var(--danger);"><span class="chart-bar-value" id="chart-val-critical">0</span></div>
|
||||
<span class="chart-bar-label">Critico</span>
|
||||
<span class="chart-bar-label" data-i18n="sp.crit_critical">Critico</span>
|
||||
</div>
|
||||
<div class="chart-bar-group">
|
||||
<div class="chart-bar" id="chart-bar-high" style="height:0;background:#f97316;"><span class="chart-bar-value" id="chart-val-high">0</span></div>
|
||||
<span class="chart-bar-label">Alto</span>
|
||||
<span class="chart-bar-label" data-i18n="sp.crit_high">Alto</span>
|
||||
</div>
|
||||
<div class="chart-bar-group">
|
||||
<div class="chart-bar" id="chart-bar-medium" style="height:0;background:var(--warning);"><span class="chart-bar-value" id="chart-val-medium">0</span></div>
|
||||
<span class="chart-bar-label">Medio</span>
|
||||
<span class="chart-bar-label" data-i18n="sp.crit_medium">Medio</span>
|
||||
</div>
|
||||
<div class="chart-bar-group">
|
||||
<div class="chart-bar" id="chart-bar-low" style="height:0;background:var(--secondary);"><span class="chart-bar-value" id="chart-val-low">0</span></div>
|
||||
<span class="chart-bar-label">Basso</span>
|
||||
<span class="chart-bar-label" data-i18n="sp.crit_low">Basso</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -446,18 +446,18 @@
|
||||
<div id="suppliers-list-section">
|
||||
<div class="filters-bar">
|
||||
<select class="form-select" id="filter-criticality" onchange="applyFilters()" aria-label="Filtra per criticità">
|
||||
<option value="">Tutte le Criticita'</option>
|
||||
<option value="critical">Critico</option>
|
||||
<option value="high">Alto</option>
|
||||
<option value="medium">Medio</option>
|
||||
<option value="low">Basso</option>
|
||||
<option value="" data-i18n="sp.filter_all_crit">Tutte le Criticita'</option>
|
||||
<option value="critical" data-i18n="sp.crit_critical">Critico</option>
|
||||
<option value="high" data-i18n="sp.crit_high">Alto</option>
|
||||
<option value="medium" data-i18n="sp.crit_medium">Medio</option>
|
||||
<option value="low" data-i18n="sp.crit_low">Basso</option>
|
||||
</select>
|
||||
<select class="form-select" id="filter-status" onchange="applyFilters()" aria-label="Filtra per stato">
|
||||
<option value="">Tutti gli Stati</option>
|
||||
<option value="active">Attivo</option>
|
||||
<option value="under_review">In Revisione</option>
|
||||
<option value="suspended">Sospeso</option>
|
||||
<option value="terminated">Terminato</option>
|
||||
<option value="" data-i18n="sp.filter_all_status">Tutti gli Stati</option>
|
||||
<option value="active" data-i18n="sp.status_active">Attivo</option>
|
||||
<option value="under_review" data-i18n="sp.status_under_review">In Revisione</option>
|
||||
<option value="suspended" data-i18n="sp.status_suspended">Sospeso</option>
|
||||
<option value="terminated" data-i18n="sp.status_terminated">Terminato</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -465,7 +465,7 @@
|
||||
<div class="table-container" id="suppliers-table">
|
||||
<div class="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
<span>Caricamento fornitori...</span>
|
||||
<span data-i18n="sp.loading_suppliers">Caricamento fornitori...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -489,7 +489,7 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260631"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/i18n.js?v=20260621sp"></script>
|
||||
<script src="/js/help.js?v=20260618p2"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
@@ -635,8 +635,8 @@
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" style="width:48px;height:48px;color:var(--gray-300);">
|
||||
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<h4>Nessun fornitore trovato</h4>
|
||||
<p>Aggiungi il tuo primo fornitore per iniziare il monitoraggio della supply chain.</p>
|
||||
<h4>${I18n.t('sp.no_suppliers')}</h4>
|
||||
<p>${I18n.t('sp.no_suppliers_hint')}</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
@@ -645,13 +645,13 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Tipo Servizio</th>
|
||||
<th>Criticita'</th>
|
||||
<th>Score Rischio</th>
|
||||
<th>Stato</th>
|
||||
<th>Ultima Valutazione</th>
|
||||
<th>Azioni</th>
|
||||
<th>${I18n.t('sp.col_name')}</th>
|
||||
<th>${I18n.t('sp.col_service_type')}</th>
|
||||
<th>${I18n.t('sp.col_criticality')}</th>
|
||||
<th>${I18n.t('sp.col_risk_score')}</th>
|
||||
<th>${I18n.t('sp.col_status')}</th>
|
||||
<th>${I18n.t('sp.col_last_assessment')}</th>
|
||||
<th>${I18n.t('sp.col_actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>`;
|
||||
@@ -752,28 +752,28 @@
|
||||
<div>
|
||||
<div style="margin-bottom:8px;">
|
||||
<span class="text-link" onclick="backToList()" style="font-size:0.82rem;">
|
||||
← Torna all'elenco
|
||||
← ${I18n.t('sp.back_to_list')}
|
||||
</span>
|
||||
</div>
|
||||
<h3>${escapeHtml(supplier.name)}</h3>
|
||||
<div style="margin-top:8px;display:flex;gap:8px;">
|
||||
<span class="badge ${critBadge}">${escapeHtml(critLabel)}</span>
|
||||
<span class="badge ${statusBadge}">${escapeHtml(statusLabel)}</span>
|
||||
${supplier.security_requirements_met ? '<span class="badge badge-success">Conforme</span>' : '<span class="badge badge-danger">Non Conforme</span>'}
|
||||
${supplier.security_requirements_met ? '<span class="badge badge-success">' + I18n.t('sp.compliant') + '</span>' : '<span class="badge badge-danger">' + I18n.t('sp.non_compliant') + '</span>'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-header-actions">
|
||||
<button class="btn btn-primary" onclick="openAssessmentModal(${supplier.id})">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm9.707 5.707a1 1 0 00-1.414-1.414L9 12.586l-1.293-1.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
|
||||
Valuta Fornitore
|
||||
${I18n.t('sp.assess_supplier')}
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="sendSupplierQuestionnaire(${supplier.id}, '${escapeHtml((supplier.name||'').replace(/'/g,"\\'"))}')">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/><path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/></svg>
|
||||
Invia questionario
|
||||
${I18n.t('sp.send_questionnaire')}
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="openEditModal(${supplier.id})">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg>
|
||||
Modifica
|
||||
${I18n.t('sp.edit')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -782,40 +782,40 @@
|
||||
<div>
|
||||
<div class="card" style="margin-bottom:16px;">
|
||||
<div class="card-header">
|
||||
<h3>Informazioni Fornitore</h3>
|
||||
<h3>${I18n.t('sp.info_title')}</h3>
|
||||
</div>
|
||||
<div class="card-body" style="padding:0;">
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Nome</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_name')}</div>
|
||||
<div class="info-item-value">${escapeHtml(supplier.name)}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Partita IVA</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_vat')}</div>
|
||||
<div class="info-item-value">${escapeHtml(supplier.vat_number || '-')}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Referente</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_contact_name')}</div>
|
||||
<div class="info-item-value">${escapeHtml(supplier.contact_name || '-')}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Email</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_email')}</div>
|
||||
<div class="info-item-value">${supplier.contact_email ? '<a href="mailto:' + escapeHtml(supplier.contact_email) + '">' + escapeHtml(supplier.contact_email) + '</a>' : '-'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Tipo Servizio</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_service_type')}</div>
|
||||
<div class="info-item-value">${escapeHtml(supplier.service_type || '-')}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Criticita'</div>
|
||||
<div class="info-item-label">${I18n.t('sp.col_criticality')}</div>
|
||||
<div class="info-item-value"><span class="badge ${critBadge}">${escapeHtml(critLabel)}</span></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Inizio Contratto</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_contract_start')}</div>
|
||||
<div class="info-item-value">${formatDate(supplier.contract_start_date)}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item-label">Scadenza Contratto</div>
|
||||
<div class="info-item-label">${I18n.t('sp.field_contract_expiry')}</div>
|
||||
<div class="info-item-value">${formatDate(supplier.contract_expiry_date)}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -824,7 +824,7 @@
|
||||
|
||||
${supplier.service_description ? `
|
||||
<div class="card" style="margin-bottom:16px;">
|
||||
<div class="card-header"><h3>Descrizione Servizio</h3></div>
|
||||
<div class="card-header"><h3>${I18n.t('sp.service_desc_title')}</h3></div>
|
||||
<div class="card-body">
|
||||
<p style="font-size:0.875rem;color:var(--gray-700);white-space:pre-wrap;">${escapeHtml(supplier.service_description)}</p>
|
||||
</div>
|
||||
@@ -832,7 +832,7 @@
|
||||
|
||||
${supplier.notes ? `
|
||||
<div class="card">
|
||||
<div class="card-header"><h3>Note</h3></div>
|
||||
<div class="card-header"><h3>${I18n.t('sp.notes_title')}</h3></div>
|
||||
<div class="card-body">
|
||||
<p style="font-size:0.875rem;color:var(--gray-700);white-space:pre-wrap;">${escapeHtml(supplier.notes)}</p>
|
||||
</div>
|
||||
@@ -842,7 +842,7 @@
|
||||
<div>
|
||||
<!-- Risk Score Gauge -->
|
||||
<div class="meta-card" style="text-align:center;">
|
||||
<h4 style="text-align:left;">Score Rischio</h4>
|
||||
<h4 style="text-align:left;">${I18n.t('sp.col_risk_score')}</h4>
|
||||
<div class="score-ring">
|
||||
<svg width="${ringSize}" height="${ringSize}" viewBox="0 0 ${ringSize} ${ringSize}">
|
||||
<circle class="score-ring-bg" cx="${ringSize/2}" cy="${ringSize/2}" r="${ringRadius}"/>
|
||||
@@ -853,37 +853,37 @@
|
||||
</svg>
|
||||
<div class="score-ring-value">
|
||||
<div class="score-ring-number" style="color:${scoreColor};">${riskScore !== null ? riskScore : '-'}</div>
|
||||
<div class="score-ring-label">su 100</div>
|
||||
<div class="score-ring-label">${I18n.t('sp.out_of_100')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:0.78rem;color:var(--gray-500);margin-top:8px;">
|
||||
${riskScore !== null
|
||||
? (riskScore >= 70 ? 'Requisiti di sicurezza soddisfatti' : 'Requisiti di sicurezza non soddisfatti')
|
||||
: 'Nessuna valutazione effettuata'}
|
||||
? (riskScore >= 70 ? I18n.t('sp.sec_req_met') : I18n.t('sp.sec_req_not_met'))
|
||||
: I18n.t('sp.no_assessment_done')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Assessment Info -->
|
||||
<div class="meta-card">
|
||||
<h4>Valutazione</h4>
|
||||
<h4>${I18n.t('sp.assessment_label')}</h4>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Ultima Valutazione</span>
|
||||
<span class="meta-label">${I18n.t('sp.last_assessment')}</span>
|
||||
<span class="meta-value">${formatDate(supplier.last_assessment_date)}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Prossima Valutazione</span>
|
||||
<span class="meta-label">${I18n.t('sp.next_assessment')}</span>
|
||||
<span class="meta-value">${formatDate(supplier.next_assessment_date)}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Requisiti Sicurezza</span>
|
||||
<span class="meta-label">${I18n.t('sp.sec_requirements')}</span>
|
||||
<span class="meta-value">
|
||||
${supplier.security_requirements_met
|
||||
? '<span class="badge badge-success">Soddisfatti</span>'
|
||||
: '<span class="badge badge-danger">Non Soddisfatti</span>'}
|
||||
? '<span class="badge badge-success">' + I18n.t('sp.met') + '</span>'
|
||||
: '<span class="badge badge-danger">' + I18n.t('sp.not_met') + '</span>'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Creato il</span>
|
||||
<span class="meta-label">${I18n.t('sp.created_at')}</span>
|
||||
<span class="meta-value">${formatDate(supplier.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -932,75 +932,75 @@
|
||||
|
||||
function showSupplierFormModal(supplier) {
|
||||
const isEdit = !!supplier;
|
||||
const title = isEdit ? 'Modifica Fornitore' : 'Nuovo Fornitore';
|
||||
const title = isEdit ? I18n.t('sp.edit_supplier') : I18n.t('sp.new_supplier_modal');
|
||||
|
||||
const criticalityOptions = Object.entries(CRITICALITY_LABELS).map(([val, label]) =>
|
||||
`<option value="${val}" ${supplier && supplier.criticality === val ? 'selected' : ''}>${label}</option>`
|
||||
).join('');
|
||||
|
||||
const categoryOptions = '<option value="">— Nessuna —</option>' +
|
||||
const categoryOptions = `<option value="">${I18n.t('sp.category_none')}</option>` +
|
||||
allCategories.map(c =>
|
||||
`<option value="${c.id}" ${supplier && String(supplier.category_id) === String(c.id) ? 'selected' : ''}>${escapeHtml(c.name)}${Number(c.is_system) === 1 ? '' : ' (personalizzata)'}</option>`
|
||||
`<option value="${c.id}" ${supplier && String(supplier.category_id) === String(c.id) ? 'selected' : ''}>${escapeHtml(c.name)}${Number(c.is_system) === 1 ? '' : ' (' + I18n.t('sp.cat_custom') + ')'}</option>`
|
||||
).join('');
|
||||
|
||||
const content = `
|
||||
<form id="supplier-form" onsubmit="return false;">
|
||||
<div class="form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div class="form-group">
|
||||
<label for="form-name" class="form-label">Nome Fornitore <span class="required">*</span></label>
|
||||
<input type="text" class="form-input" id="form-name" value="${isEdit ? escapeHtml(supplier.name) : ''}" required placeholder="Nome dell'azienda">
|
||||
<label for="form-name" class="form-label">${I18n.t('sp.field_supplier_name')} <span class="required">*</span></label>
|
||||
<input type="text" class="form-input" id="form-name" value="${isEdit ? escapeHtml(supplier.name) : ''}" required placeholder="${I18n.t('sp.ph_company_name')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-vat" class="form-label">Partita IVA</label>
|
||||
<label for="form-vat" class="form-label">${I18n.t('sp.field_vat')}</label>
|
||||
<input type="text" class="form-input" id="form-vat" value="${isEdit && supplier.vat_number ? escapeHtml(supplier.vat_number) : ''}" placeholder="IT01234567890">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div class="form-group">
|
||||
<label for="form-contact-name" class="form-label">Referente</label>
|
||||
<input type="text" class="form-input" id="form-contact-name" value="${isEdit && supplier.contact_name ? escapeHtml(supplier.contact_name) : ''}" placeholder="Nome e cognome">
|
||||
<label for="form-contact-name" class="form-label">${I18n.t('sp.field_contact_name')}</label>
|
||||
<input type="text" class="form-input" id="form-contact-name" value="${isEdit && supplier.contact_name ? escapeHtml(supplier.contact_name) : ''}" placeholder="${I18n.t('sp.ph_full_name')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-contact-email" class="form-label">Email Contatto</label>
|
||||
<label for="form-contact-email" class="form-label">${I18n.t('sp.field_contact_email')}</label>
|
||||
<input type="email" class="form-input" id="form-contact-email" value="${isEdit && supplier.contact_email ? escapeHtml(supplier.contact_email) : ''}" placeholder="email@azienda.it">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:2fr 1fr;gap:16px;">
|
||||
<div class="form-group">
|
||||
<label for="form-service-type" class="form-label">Tipo Servizio <span class="required">*</span></label>
|
||||
<input type="text" class="form-input" id="form-service-type" value="${isEdit && supplier.service_type ? escapeHtml(supplier.service_type) : ''}" required placeholder="es. Cloud Hosting, Sviluppo SW, Consulenza">
|
||||
<label for="form-service-type" class="form-label">${I18n.t('sp.field_service_type')} <span class="required">*</span></label>
|
||||
<input type="text" class="form-input" id="form-service-type" value="${isEdit && supplier.service_type ? escapeHtml(supplier.service_type) : ''}" required placeholder="${I18n.t('sp.ph_service_type')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-criticality" class="form-label">Criticita'</label>
|
||||
<label for="form-criticality" class="form-label">${I18n.t('sp.col_criticality')}</label>
|
||||
<select class="form-select" id="form-criticality">
|
||||
${criticalityOptions}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="form-category">Categoria fornitore</label>
|
||||
<label class="form-label" for="form-category">${I18n.t('sp.category_field')}</label>
|
||||
<select class="form-select" id="form-category">
|
||||
${categoryOptions}
|
||||
</select>
|
||||
<small style="color:var(--gray-500);font-size:0.75rem;">Classifica il fornitore (es. Cloud Provider). Gestisci le categorie dalla tab "Categorie".</small>
|
||||
<small style="color:var(--gray-500);font-size:0.75rem;">${I18n.t('sp.category_hint')}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-service-desc" class="form-label">Descrizione Servizio</label>
|
||||
<textarea class="form-textarea" id="form-service-desc" rows="3" placeholder="Descrivi il servizio fornito...">${isEdit && supplier.service_description ? escapeHtml(supplier.service_description) : ''}</textarea>
|
||||
<label for="form-service-desc" class="form-label">${I18n.t('sp.service_desc_title')}</label>
|
||||
<textarea class="form-textarea" id="form-service-desc" rows="3" placeholder="${I18n.t('sp.ph_service_desc')}">${isEdit && supplier.service_description ? escapeHtml(supplier.service_description) : ''}</textarea>
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div class="form-group">
|
||||
<label for="form-contract-start" class="form-label">Inizio Contratto</label>
|
||||
<label for="form-contract-start" class="form-label">${I18n.t('sp.field_contract_start')}</label>
|
||||
<input type="date" class="form-input" id="form-contract-start" value="${isEdit && supplier.contract_start_date ? supplier.contract_start_date.substring(0, 10) : ''}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-contract-expiry" class="form-label">Scadenza Contratto</label>
|
||||
<label for="form-contract-expiry" class="form-label">${I18n.t('sp.field_contract_expiry')}</label>
|
||||
<input type="date" class="form-input" id="form-contract-expiry" value="${isEdit && supplier.contract_expiry_date ? supplier.contract_expiry_date.substring(0, 10) : ''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="form-notes" class="form-label">Note</label>
|
||||
<textarea class="form-textarea" id="form-notes" rows="3" placeholder="Note aggiuntive...">${isEdit && supplier.notes ? escapeHtml(supplier.notes) : ''}</textarea>
|
||||
<label for="form-notes" class="form-label">${I18n.t('sp.notes_title')}</label>
|
||||
<textarea class="form-textarea" id="form-notes" rows="3" placeholder="${I18n.t('sp.ph_notes')}">${isEdit && supplier.notes ? escapeHtml(supplier.notes) : ''}</textarea>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
@@ -1008,8 +1008,8 @@
|
||||
showModal(title, content, {
|
||||
size: 'lg',
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="closeModal()">Annulla</button>
|
||||
<button class="btn btn-primary" onclick="saveSupplier(${isEdit ? supplier.id : 'null'})">${isEdit ? 'Salva Modifiche' : 'Aggiungi Fornitore'}</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal()">${I18n.t('sp.cancel')}</button>
|
||||
<button class="btn btn-primary" onclick="saveSupplier(${isEdit ? supplier.id : 'null'})">${isEdit ? I18n.t('sp.save_changes') : I18n.t('sp.add_supplier')}</button>
|
||||
`
|
||||
});
|
||||
}
|
||||
@@ -1076,9 +1076,9 @@
|
||||
<div class="assessment-question">
|
||||
<div class="assessment-question-text">${idx + 1}. ${escapeHtml(q.text)}</div>
|
||||
<div class="assessment-options">
|
||||
<span class="assessment-option" data-q="${q.id}" data-val="yes" onclick="selectAnswer('${q.id}', 'yes', this)">Si'</span>
|
||||
<span class="assessment-option" data-q="${q.id}" data-val="partial" onclick="selectAnswer('${q.id}', 'partial', this)">Parziale</span>
|
||||
<span class="assessment-option" data-q="${q.id}" data-val="no" onclick="selectAnswer('${q.id}', 'no', this)">No</span>
|
||||
<span class="assessment-option" data-q="${q.id}" data-val="yes" onclick="selectAnswer('${q.id}', 'yes', this)">${I18n.t('sp.answer_yes')}</span>
|
||||
<span class="assessment-option" data-q="${q.id}" data-val="partial" onclick="selectAnswer('${q.id}', 'partial', this)">${I18n.t('sp.answer_partial')}</span>
|
||||
<span class="assessment-option" data-q="${q.id}" data-val="no" onclick="selectAnswer('${q.id}', 'no', this)">${I18n.t('sp.answer_no')}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1086,23 +1086,22 @@
|
||||
|
||||
const content = `
|
||||
<p style="margin-bottom:16px;font-size:0.9rem;color:var(--gray-600);">
|
||||
Rispondi alle domande per valutare il livello di sicurezza del fornitore.
|
||||
Il punteggio di rischio verra' calcolato automaticamente.
|
||||
${I18n.t('sp.assess_intro')}
|
||||
</p>
|
||||
<div id="assessment-questions">
|
||||
${questionsHtml}
|
||||
</div>
|
||||
<div class="assessment-score-preview" id="assessment-score-preview">
|
||||
<div class="score-label">Score Rischio Calcolato</div>
|
||||
<div class="score-label">${I18n.t('sp.calculated_risk_score')}</div>
|
||||
<div class="score-val" id="assessment-live-score" style="color:var(--gray-300);">--</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
showModal('Valutazione Fornitore', content, {
|
||||
showModal(I18n.t('sp.assess_modal_title'), content, {
|
||||
size: 'lg',
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="closeModal()">Annulla</button>
|
||||
<button class="btn btn-primary" onclick="submitAssessment(${supplierId})">Salva Valutazione</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal()">${I18n.t('sp.cancel')}</button>
|
||||
<button class="btn btn-primary" onclick="submitAssessment(${supplierId})">${I18n.t('sp.save_assessment')}</button>
|
||||
`
|
||||
});
|
||||
}
|
||||
@@ -1225,7 +1224,7 @@
|
||||
ov.innerHTML = `
|
||||
<div style="background:#fff;border-radius:10px;max-width:640px;width:100%;max-height:90vh;overflow:auto;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #e2e8f0;">
|
||||
<h3 style="margin:0;"><svg class="icon icon-sm" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-upload"></use></svg> Importa fornitori</h3>
|
||||
<h3 style="margin:0;"><svg class="icon icon-sm" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-upload"></use></svg> ${I18n.t('sp.import_title')}</h3>
|
||||
<button onclick="document.getElementById('importModalDyn').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;line-height:1;">×</button>
|
||||
</div>
|
||||
<div style="padding:20px;">
|
||||
@@ -1241,8 +1240,8 @@
|
||||
<div id="importResult" style="margin-top:10px;font-size:14px;"></div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:10px;padding:16px 20px;border-top:1px solid #e2e8f0;">
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('importModalDyn').remove()">Annulla</button>
|
||||
<button class="btn btn-primary" id="importRunBtn" onclick="runSupplierImport()"><svg class="icon icon-sm icon-white" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-upload"></use></svg> Importa</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('importModalDyn').remove()">${I18n.t('sp.cancel')}</button>
|
||||
<button class="btn btn-primary" id="importRunBtn" onclick="runSupplierImport()"><svg class="icon icon-sm icon-white" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-upload"></use></svg> ${I18n.t('sp.import_run')}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(ov);
|
||||
@@ -1336,11 +1335,11 @@
|
||||
const rows = allCategories.map(c => {
|
||||
const preset = Number(c.is_system) === 1;
|
||||
const badge = preset
|
||||
? '<span class="badge badge-neutral" title="Categoria di sistema">Preset</span>'
|
||||
: '<span class="badge badge-info">Personalizzata</span>';
|
||||
const actions = preset ? '<span style="color:var(--gray-400);font-size:0.75rem;">non modificabile</span>' : `
|
||||
<button class="btn btn-sm btn-secondary" onclick="editCategory(${c.id})">Modifica</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="removeCategory(${c.id})">Elimina</button>`;
|
||||
? '<span class="badge badge-neutral" title="Categoria di sistema">' + I18n.t('sp.cat_preset') + '</span>'
|
||||
: '<span class="badge badge-info">' + I18n.t('sp.cat_custom') + '</span>';
|
||||
const actions = preset ? '<span style="color:var(--gray-400);font-size:0.75rem;">' + I18n.t('sp.cat_not_editable') + '</span>' : `
|
||||
<button class="btn btn-sm btn-secondary" onclick="editCategory(${c.id})">${I18n.t('sp.edit')}</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="removeCategory(${c.id})">${I18n.t('sp.delete')}</button>`;
|
||||
return `<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;border-bottom:1px solid var(--gray-100);flex-wrap:wrap;">
|
||||
<div style="flex:1;min-width:200px;">
|
||||
<div style="font-weight:600;display:flex;gap:8px;align-items:center;flex-wrap:wrap;">${escapeHtml(c.name)} ${badge}</div>
|
||||
@@ -1351,13 +1350,13 @@
|
||||
}).join('');
|
||||
const content = `
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:10px;">
|
||||
<button class="btn btn-sm btn-primary" onclick="newCategory()">+ Nuova categoria</button>
|
||||
<button class="btn btn-sm btn-primary" onclick="newCategory()">${I18n.t('sp.cat_new')}</button>
|
||||
</div>
|
||||
<div id="categories-list-body">${rows || '<p style="color:var(--gray-500);">Nessuna categoria.</p>'}</div>
|
||||
<p style="font-size:0.75rem;color:var(--gray-500);margin-top:12px;">Le categorie <strong>preset</strong> sono fornite dal sistema e non sono modificabili. Puoi creare categorie personalizzate per la tua organizzazione.</p>`;
|
||||
showModal('Categorie fornitore', content, {
|
||||
<div id="categories-list-body">${rows || '<p style="color:var(--gray-500);">' + I18n.t('sp.cat_empty') + '</p>'}</div>
|
||||
<p style="font-size:0.75rem;color:var(--gray-500);margin-top:12px;">${I18n.t('sp.cat_preset_note')}</p>`;
|
||||
showModal(I18n.t('sp.cat_title'), content, {
|
||||
size: 'lg',
|
||||
footer: `<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>`
|
||||
footer: `<button class="btn btn-secondary" onclick="closeModal()">${I18n.t('sp.close')}</button>`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1368,17 +1367,17 @@
|
||||
const isEdit = !!cat;
|
||||
const content = `
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="cat-name">Nome categoria <span class="required">*</span></label>
|
||||
<input type="text" class="form-input" id="cat-name" value="${isEdit ? escapeHtml(cat.name) : ''}" placeholder="es. Cloud Provider">
|
||||
<label class="form-label" for="cat-name">${I18n.t('sp.cat_name')} <span class="required">*</span></label>
|
||||
<input type="text" class="form-input" id="cat-name" value="${isEdit ? escapeHtml(cat.name) : ''}" placeholder="${I18n.t('sp.ph_cat_name')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="cat-desc">Descrizione</label>
|
||||
<textarea class="form-textarea" id="cat-desc" rows="3" placeholder="Descrizione opzionale...">${isEdit && cat.description ? escapeHtml(cat.description) : ''}</textarea>
|
||||
<label class="form-label" for="cat-desc">${I18n.t('sp.cat_desc')}</label>
|
||||
<textarea class="form-textarea" id="cat-desc" rows="3" placeholder="${I18n.t('sp.ph_cat_desc')}">${isEdit && cat.description ? escapeHtml(cat.description) : ''}</textarea>
|
||||
</div>`;
|
||||
showModal(isEdit ? 'Modifica categoria' : 'Nuova categoria', content, {
|
||||
showModal(isEdit ? I18n.t('sp.cat_edit') : I18n.t('sp.cat_new_title'), content, {
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="openCategoriesModal()">Annulla</button>
|
||||
<button class="btn btn-primary" onclick="saveCategory(${isEdit ? cat.id : 'null'})">${isEdit ? 'Salva' : 'Crea'}</button>`
|
||||
<button class="btn btn-secondary" onclick="openCategoriesModal()">${I18n.t('sp.cancel')}</button>
|
||||
<button class="btn btn-primary" onclick="saveCategory(${isEdit ? cat.id : 'null'})">${isEdit ? I18n.t('sp.save') : I18n.t('sp.create')}</button>`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1425,8 +1424,8 @@
|
||||
};
|
||||
|
||||
async function openTemplatesModal() {
|
||||
showModal('Template questionari', '<div class="loading-overlay"><div class="spinner"></div><span>Caricamento...</span></div>', {
|
||||
size: 'lg', footer: `<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>`
|
||||
showModal(I18n.t('sp.tpl_title'), '<div class="loading-overlay"><div class="spinner"></div><span>' + I18n.t('sp.loading') + '</span></div>', {
|
||||
size: 'lg', footer: `<button class="btn btn-secondary" onclick="closeModal()">${I18n.t('sp.close')}</button>`
|
||||
});
|
||||
try {
|
||||
const res = await api.getQuestionnaireTemplates();
|
||||
@@ -1442,18 +1441,18 @@
|
||||
const b = document.querySelector('#modal-overlay .modal-body');
|
||||
if (!b) return;
|
||||
if (!templates.length) {
|
||||
b.innerHTML = '<p style="color:var(--gray-500);">Nessun template questionario per questa organizzazione.</p>';
|
||||
b.innerHTML = '<p style="color:var(--gray-500);">' + I18n.t('sp.tpl_empty') + '</p>';
|
||||
return;
|
||||
}
|
||||
b.innerHTML = templates.map(t => {
|
||||
const qc = Number(t.question_count || 0);
|
||||
const st = t.status === 'active' ? '<span class="badge badge-success">Attivo</span>' : `<span class="badge badge-neutral">${escapeHtml(t.status || 'bozza')}</span>`;
|
||||
const st = t.status === 'active' ? '<span class="badge badge-success">' + I18n.t('sp.tpl_active') + '</span>' : `<span class="badge badge-neutral">${escapeHtml(t.status || 'bozza')}</span>`;
|
||||
return `<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;border-bottom:1px solid var(--gray-100);flex-wrap:wrap;">
|
||||
<div style="flex:1;min-width:200px;">
|
||||
<div style="font-weight:600;display:flex;gap:8px;align-items:center;flex-wrap:wrap;">${escapeHtml(t.name)} ${Number(t.is_default)===1?'<span class="badge badge-neutral">Default</span>':''} ${st}</div>
|
||||
<div style="font-weight:600;display:flex;gap:8px;align-items:center;flex-wrap:wrap;">${escapeHtml(t.name)} ${Number(t.is_default)===1?'<span class="badge badge-neutral">' + I18n.t('sp.tpl_default') + '</span>':''} ${st}</div>
|
||||
<div style="font-size:0.78rem;color:var(--gray-500);margin-top:2px;">${t.category_name?escapeHtml(t.category_name)+' · ':''}${qc} domand${qc===1?'a':'e'}${t.current_version?' · v'+escapeHtml(String(t.current_version)):''}</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-secondary" onclick="viewTemplate(${t.id})">Vedi domande</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="viewTemplate(${t.id})">${I18n.t('sp.tpl_view_questions')}</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
@@ -1483,17 +1482,17 @@
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px;padding-left:24px;">
|
||||
${q.nis2_ref?`<span class="badge badge-info" title="Riferimento normativo">${escapeHtml(q.nis2_ref)}</span>`:''}
|
||||
<span class="badge badge-neutral">${escapeHtml(QTYPE_LABELS[q.question_type]||q.question_type||'')}</span>
|
||||
${q.weight!=null?`<span class="badge badge-neutral">Peso ${escapeHtml(String(q.weight))}</span>`:''}
|
||||
${Number(q.is_required)===1?'<span class="badge badge-neutral">Obbligatoria</span>':''}
|
||||
${Number(q.high_criticality_only)===1?'<span class="badge badge-warning">Solo alta criticita</span>':''}
|
||||
${q.weight!=null?`<span class="badge badge-neutral">${I18n.t('sp.tpl_weight')} ${escapeHtml(String(q.weight))}</span>`:''}
|
||||
${Number(q.is_required)===1?'<span class="badge badge-neutral">' + I18n.t('sp.tpl_required') + '</span>':''}
|
||||
${Number(q.high_criticality_only)===1?'<span class="badge badge-warning">' + I18n.t('sp.tpl_high_crit') + '</span>':''}
|
||||
</div>
|
||||
</div>`).join('') : '<p style="color:var(--gray-500);">Nessuna domanda nel template.</p>';
|
||||
b.innerHTML = `
|
||||
<div style="margin-bottom:12px;"><button class="btn btn-sm btn-secondary" onclick="openTemplatesModal()">← Torna ai template</button></div>
|
||||
<div style="margin-bottom:12px;"><button class="btn btn-sm btn-secondary" onclick="openTemplatesModal()">← ${I18n.t('sp.tpl_back_plain')}</button></div>
|
||||
<h3 style="margin:0 0 4px;">${escapeHtml(tpl.name)}</h3>
|
||||
<div style="font-size:0.8rem;color:var(--gray-500);margin-bottom:12px;">${qs.length} domand${qs.length===1?'a':'e'}${tpl.current_version?' · versione '+escapeHtml(String(tpl.current_version)):''}${tpl.pass_threshold!=null?' · soglia '+escapeHtml(String(tpl.pass_threshold))+'%':''}</div>
|
||||
${tpl.description?`<p style="font-size:0.85rem;color:var(--gray-600);margin-bottom:12px;">${escapeHtml(tpl.description)}</p>`:''}
|
||||
<div style="background:var(--info-bg,#eff6ff);border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:0.78rem;color:var(--primary);">Sola lettura. L'editor delle domande sara' disponibile in una prossima versione.</div>
|
||||
<div style="background:var(--info-bg,#eff6ff);border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:0.78rem;color:var(--primary);">${I18n.t('sp.tpl_readonly')}</div>
|
||||
${qHtml}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user