Differenze
Queste sono le differenze tra la revisione selezionata e la versione attuale della pagina.
Prossima revisione | Revisione precedente | ||
gestione_turni_med [2025/08/12 09:45] – creata neoadmin | gestione_turni_med [2025/08/12 10:16] (versione attuale) – neoadmin | ||
---|---|---|---|
Linea 7: | Linea 7: | ||
body { | body { | ||
font-family: | font-family: | ||
- | margin: | + | margin: |
background: #f5f5f5; | background: #f5f5f5; | ||
+ | line-height: | ||
} | } | ||
| | ||
Linea 15: | Linea 16: | ||
margin: 0 auto; | margin: 0 auto; | ||
background: white; | background: white; | ||
- | padding: | + | padding: |
- | border-radius: | + | border-radius: |
box-shadow: 0 2px 10px rgba(0, | box-shadow: 0 2px 10px rgba(0, | ||
} | } | ||
Linea 24: | Linea 25: | ||
background: linear-gradient(135deg, | background: linear-gradient(135deg, | ||
color: white; | color: white; | ||
- | padding: | + | padding: |
- | border-radius: | + | border-radius: |
- | margin-bottom: | + | margin-bottom: |
+ | } | ||
+ | |||
+ | .header h1 { | ||
+ | margin: 0 0 8px 0; | ||
+ | font-size: 22px; | ||
+ | font-weight: | ||
+ | } | ||
+ | |||
+ | .header h2 { | ||
+ | margin: 0 0 5px 0; | ||
+ | font-size: 16px; | ||
+ | font-weight: | ||
+ | } | ||
+ | |||
+ | .header p { | ||
+ | margin: 0; | ||
+ | font-size: 13px; | ||
+ | opacity: 0.9; | ||
} | } | ||
| | ||
.controls { | .controls { | ||
display: flex; | display: flex; | ||
- | gap: 20px; | + | gap: 15px; |
- | margin-bottom: | + | margin-bottom: |
flex-wrap: wrap; | flex-wrap: wrap; | ||
+ | align-items: | ||
} | } | ||
| | ||
Linea 39: | Linea 59: | ||
display: flex; | display: flex; | ||
flex-direction: | flex-direction: | ||
- | gap: 5px; | + | gap: 4px; |
+ | } | ||
+ | |||
+ | .control-group label { | ||
+ | font-size: 12px; | ||
+ | font-weight: | ||
+ | color: #2c3e50; | ||
} | } | ||
| | ||
select, input, button { | select, input, button { | ||
- | padding: | + | padding: |
border: 1px solid #ddd; | border: 1px solid #ddd; | ||
- | border-radius: | + | border-radius: |
- | font-size: | + | font-size: |
} | } | ||
| | ||
Linea 54: | Linea 80: | ||
cursor: pointer; | cursor: pointer; | ||
font-weight: | font-weight: | ||
+ | transition: background 0.3s; | ||
} | } | ||
| | ||
Linea 253: | Linea 280: | ||
< | < | ||
<button onclick=" | <button onclick=" | ||
+ | </ | ||
+ | | ||
+ | <div class=" | ||
+ | < | ||
+ | <button onclick=" | ||
</ | </ | ||
| | ||
Linea 278: | Linea 310: | ||
<script src=" | <script src=" | ||
< | < | ||
- | // Database medici con competenze | + | // Database medici con caratteristiche complete |
- | | + | |
' | ' | ||
- | nome: 'MC - Direttore SS', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | limitazioniGravidanza: |
- | ptn: true, | + | |
- | | + | // Competenze turni |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: true, urgenzeSalaParto: |
- | maxOreSettimanali: | + | maxOreSettimanali: |
- | | + | maxNottiConsecutive: |
- | | + | }, |
- | maxNottiConsecutive: | + | ' |
- | maxWeekendMese: | + | |
+ | ruolo: ' | ||
+ | anzianita: '< | ||
+ | limitazioniGravidanza: | ||
+ | decCalabria: | ||
+ | // Competenze turni | ||
+ | reperibilita: | ||
+ | ptn: true, nido: true, followUp: false, urgenzeSalaParto: | ||
+ | maxOreSettimanali: | ||
+ | maxNottiConsecutive: | ||
}, | }, | ||
' | ' | ||
- | nome: 'CM - Aiuto Senior', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | // Competenze turni |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: true, urgenzeSalaParto: |
- | maxOreSettimanali: | + | maxOreSettimanali: |
- | | + | maxNottiConsecutive: |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'FM - Aiuto Senior', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'FCa - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: true, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'EF - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'FF - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'LF - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'MF - Dirigente PT', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | limitazioniGravidanza: |
- | ptn: true, | + | |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'FC - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'VE - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | limitazioniGravidanza: |
- | ptn: true, | + | |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'CC - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'FT - Dirigente', | + | nome: 'F', |
- | reperibilita: false, | + | |
- | guardiaGiorno: true, | + | |
- | | + | |
- | ptn: true, | + | |
- | nido: true, | + | reperibilita: |
- | followUp: false, | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | urgenzeSalaParto: true, | + | maxOreSettimanali: |
- | | + | maxNottiConsecutive: |
- | minOreSettimanali: 38, | + | |
- | prioritaRiposo: 5, | + | |
- | | + | |
- | maxWeekendMese: 4 | + | |
- | }, | + | |
- | | + | |
- | | + | |
- | reperibilita: | + | |
- | | + | |
- | | + | |
- | ptn: true, | + | |
- | | + | |
- | | + | |
- | | + | |
- | maxOreSettimanali: | + | |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'GA - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | followUp: false, | + | |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
}, | }, | ||
' | ' | ||
- | nome: 'SM - Dirigente', | + | nome: ' |
- | | + | |
- | | + | |
- | guardiaNotte: | + | |
- | ptn: true, | + | decCalabria: |
- | | + | reperibilita: |
- | | + | ptn: true, nido: true, followUp: false, urgenzeSalaParto: |
- | | + | maxOreSettimanali: |
- | maxOreSettimanali: | + | maxNottiConsecutive: |
- | | + | |
- | | + | |
- | maxNottiConsecutive: | + | |
- | | + | |
} | } | ||
}; | }; | ||
- | let turniMensili | + | |
- | let statisticheMensili | + | const configurazioniMaster = { |
+ | ruoli: [' | ||
+ | posizioni: [' | ||
+ | anzianita: ['< | ||
+ | fte: [25, 50, 75, 100], | ||
+ | privilege: [1, 2, 3, 4, 5] // Scala da definire | ||
+ | }; | ||
+ | |||
+ | // 👥 GESTIONE MEDICI DINAMICA | ||
+ | function mostraGestioneMedici() { | ||
+ | const gestioneHtml = ` | ||
+ | <div id=" | ||
+ | < | ||
+ | |||
+ | <div style=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | </ | ||
+ | |||
+ | <div style=" | ||
+ | <table id=" | ||
+ | < | ||
+ | <tr style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | <th style=" | ||
+ | </ | ||
+ | </ | ||
+ | <tbody id=" | ||
+ | ${generaRigheMedici()} | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div style=" | ||
+ | < | ||
+ | <div style=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div style=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | </ | ||
+ | </ | ||
+ | `; | ||
+ | |||
+ | const controlsDiv = document.querySelector(' | ||
+ | controlsDiv.insertAdjacentHTML(' | ||
+ | } | ||
+ | |||
+ | function generaRigheMedici() { | ||
+ | | ||
+ | Object.keys(medici).forEach(codice => { | ||
+ | const medico = medici[codice]; | ||
+ | html += ` | ||
+ | <tr id=" | ||
+ | <td style=" | ||
+ | <td style=" | ||
+ | <td style=" | ||
+ | <select id=" | ||
+ | ${configurazioniMaster.ruoli.map(r => `<option value=" | ||
+ | </ | ||
+ | </ | ||
+ | <td style=" | ||
+ | <select id=" | ||
+ | ${configurazioniMaster.posizioni.map(p => `<option value=" | ||
+ | </ | ||
+ | </ | ||
+ | <td style=" | ||
+ | <input type=" | ||
+ | </ | ||
+ | <td style=" | ||
+ | <select id=" | ||
+ | <option value=""> | ||
+ | ${configurazioniMaster.privilege.map(p => `<option value=" | ||
+ | </ | ||
+ | </ | ||
+ | <td style=" | ||
+ | <select id=" | ||
+ | ${configurazioniMaster.anzianita.map(a => `<option value=" | ||
+ | </ | ||
+ | </ | ||
+ | <td style=" | ||
+ | <select id=" | ||
+ | ${configurazioniMaster.fte.map(f => `<option value=" | ||
+ | </ | ||
+ | </ | ||
+ | <td style=" | ||
+ | <input type=" | ||
+ | </ | ||
+ | <td style=" | ||
+ | <td style=" | ||
+ | <input type=" | ||
+ | </ | ||
+ | <td style=" | ||
+ | <td style=" | ||
+ | <td style=" | ||
+ | <td style=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | </ | ||
+ | </ | ||
+ | `; | ||
+ | }); | ||
+ | return html; | ||
+ | | ||
+ | |||
+ | function aggiungiNuovoMedico() { | ||
+ | const nome = prompt(" | ||
+ | const cognome = prompt(" | ||
+ | |||
+ | if (!nome || !cognome) { | ||
+ | mostraMessaggio(' | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | const codice = (nome.charAt(0) + cognome.charAt(0)).toUpperCase(); | ||
+ | | ||
+ | let counter = 1; | ||
+ | |||
+ | // Evita duplicati | ||
+ | while (medici[codiceFinal]) | ||
+ | codiceFinal = codice + counter; | ||
+ | counter++; | ||
+ | } | ||
+ | |||
+ | // Nuovo medico con valori default | ||
+ | medici[codiceFinal] = { | ||
+ | nome: nome, cognome: cognome, nomeCompleto: | ||
+ | ruolo: ' | ||
+ | anzianita: '< | ||
+ | limitazioniGravidanza: | ||
+ | decCalabria: | ||
+ | // Competenze turni default | ||
+ | reperibilita: | ||
+ | ptn: true, nido: true, followUp: false, urgenzeSalaParto: | ||
+ | maxOreSettimanali: | ||
+ | maxNottiConsecutive: | ||
+ | | ||
+ | |||
+ | // Aggiorna tabella | ||
+ | document.getElementById(' | ||
+ | mostraMessaggio(`✅ Medico ${codiceFinal} aggiunto con successo`, ' | ||
+ | } | ||
+ | |||
+ | function eliminaMedico(codice) { | ||
+ | if (confirm(`Sei sicuro di voler eliminare il medico ${medici[codice].nomeCompleto}? | ||
+ | delete medici[codice]; | ||
+ | document.getElementById(' | ||
+ | mostraMessaggio(`✅ Medico ${codice} eliminato`, ' | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function modificaCompetenze(codice) { | ||
+ | const medico = medici[codice]; | ||
+ | const competenzeHtml = ` | ||
+ | <div id=" | ||
+ | < | ||
+ | |||
+ | <div style=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | <div style=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | <div style=" | ||
+ | <button onclick=" | ||
+ | <button onclick=" | ||
+ | </ | ||
+ | </ | ||
+ | <div id=" | ||
+ | `; | ||
+ | |||
+ | document.body.insertAdjacentHTML(' | ||
+ | } | ||
+ | |||
+ | function salvaCompetenze(codice) { | ||
+ | const medico = medici[codice]; | ||
+ | |||
+ | medico.reperibilita = document.getElementById(`comp_reperibilita_${codice}`).checked; | ||
+ | medico.guardiaGiorno = document.getElementById(`comp_guardiaGiorno_${codice}`).checked; | ||
+ | medico.guardiaNotte = document.getElementById(`comp_guardiaNotte_${codice}`).checked; | ||
+ | medico.ptn = document.getElementById(`comp_ptn_${codice}`).checked; | ||
+ | medico.nido = document.getElementById(`comp_nido_${codice}`).checked; | ||
+ | medico.followUp = document.getElementById(`comp_followUp_${codice}`).checked; | ||
+ | medico.urgenzeSalaParto = document.getElementById(`comp_urgenze_${codice}`).checked; | ||
+ | |||
+ | medico.minOreSettimanali = parseInt(document.getElementById(`comp_minOre_${codice}`).value); | ||
+ | medico.maxOreSettimanali = parseInt(document.getElementById(`comp_maxOre_${codice}`).value); | ||
+ | medico.maxNottiConsecutive = parseInt(document.getElementById(`comp_maxNotti_${codice}`).value); | ||
+ | medico.maxWeekendMese = parseInt(document.getElementById(`comp_maxWeekend_${codice}`).value); | ||
+ | |||
+ | chiudiCompetenze(codice); | ||
+ | mostraMessaggio(`✅ Competenze salvate per ${medico.nomeCompleto}`, | ||
+ | } | ||
+ | |||
+ | function chiudiCompetenze(codice) { | ||
+ | const overlay = document.getElementById(`overlay_${codice}`); | ||
+ | const modal = document.getElementById(`modificaCompetenze_${codice}`); | ||
+ | if (overlay) overlay.remove(); | ||
+ | if (modal) modal.remove(); | ||
+ | } | ||
+ | |||
+ | function salvaMedici() { | ||
+ | // Salva tutte le modifiche dalla tabella | ||
+ | Object.keys(medici).forEach(codice => { | ||
+ | const medico = medici[codice]; | ||
+ | |||
+ | medico.nome = document.getElementById(`nome_${codice}`).value; | ||
+ | medico.cognome = document.getElementById(`cognome_${codice}`).value; | ||
+ | medico.ruolo = document.getElementById(`ruolo_${codice}`).value; | ||
+ | medico.posizione = document.getElementById(`posizione_${codice}`).value; | ||
+ | medico.reperibile = document.getElementById(`reperibile_${codice}`).checked; | ||
+ | medico.privilege = document.getElementById(`privilege_${codice}`).value; | ||
+ | medico.anzianita = document.getElementById(`anzianita_${codice}`).value; | ||
+ | medico.fte = parseInt(document.getElementById(`fte_${codice}`).value); | ||
+ | medico.limitazioniGravidanza = document.getElementById(`limitGrav_${codice}`).checked; | ||
+ | medico.altreLimitazioni = document.getElementById(`altreLim_${codice}`).value; | ||
+ | medico.assente = document.getElementById(`assente_${codice}`).checked; | ||
+ | medico.decCalabria = document.getElementById(`decCal_${codice}`).value; | ||
+ | medico.ferieResidue = parseInt(document.getElementById(`ferie_${codice}`).value) || 0; | ||
+ | medico.oreEccedenza = parseInt(document.getElementById(`oreEcc_${codice}`).value) || 0; | ||
+ | |||
+ | // Aggiorna nome completo | ||
+ | medico.nomeCompleto = `${codice} - ${medico.ruolo}`; | ||
+ | }); | ||
+ | |||
+ | mostraMessaggio(' | ||
+ | } | ||
+ | |||
+ | function applicaCaratteristicheATurni() { | ||
+ | // Applica le limitazioni e caratteristiche all' | ||
+ | Object.keys(medici).forEach(codice => { | ||
+ | const medico = medici[codice]; | ||
+ | |||
+ | // Regole automatiche basate su caratteristiche | ||
+ | if (medico.fte < 100) { | ||
+ | medico.maxOreSettimanali = Math.floor(42 * medico.fte / 100); | ||
+ | medico.minOreSettimanali = Math.floor(38 * medico.fte / 100); | ||
+ | } | ||
+ | |||
+ | if (medico.limitazioniGravidanza || medico.altreLimitazioni.includes(' | ||
+ | medico.guardiaNotte = false; | ||
+ | medico.reperibilita = false; | ||
+ | medico.maxOreSettimanali = Math.min(medico.maxOreSettimanali, | ||
+ | } | ||
+ | |||
+ | if (medico.assente) { | ||
+ | medico.guardiaGiorno = false; | ||
+ | medico.guardiaNotte = false; | ||
+ | medico.reperibilita = false; | ||
+ | medico.ptn = false; | ||
+ | medico.nido = false; | ||
+ | medico.urgenzeSalaParto = false; | ||
+ | } | ||
+ | |||
+ | if (medico.anzianita === '< | ||
+ | medico.maxWeekendMese = Math.max(medico.maxWeekendMese, | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | mostraMessaggio(' | ||
+ | } | ||
+ | |||
+ | function resetMediciDefault() { | ||
+ | if (confirm(' | ||
+ | // Ripristina database originale | ||
+ | location.reload(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function applicaStiliCaratteristiche(ws, | ||
+ | const range = XLSX.utils.decode_range(ws[' | ||
+ | |||
+ | const borderStyle = { | ||
+ | border: { | ||
+ | top: { style: ' | ||
+ | bottom: { style: ' | ||
+ | left: { style: ' | ||
+ | right: { style: ' | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // Intestazione (riga 0) | ||
+ | for (let col = 0; col < numColonne; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: 0, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | ws[cellAddress].s = { | ||
+ | font: { bold: true, color: { rgb: ' | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | // Trova dove inizia la legenda | ||
+ | let inizioLegenda = -1; | ||
+ | for (let row = 0; row <= range.e.r; row++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: 0 }); | ||
+ | if (ws[cellAddress] && ws[cellAddress].v === ' | ||
+ | inizioLegenda = row; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Applica stili ai dati medici | ||
+ | for (let row = 1; row < (inizioLegenda > 0 ? inizioLegenda - 1 : range.e.r + 1); row++) { | ||
+ | for (let col = 0; col < numColonne; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | ws[cellAddress].s = { | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Applica stili alla legenda | ||
+ | if (inizioLegenda > 0) { | ||
+ | for (let row = inizioLegenda; | ||
+ | for (let col = 0; col < numColonne; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | |||
+ | if (row === inizioLegenda) { | ||
+ | // Titolo legenda | ||
+ | ws[cellAddress].s = { | ||
+ | font: { bold: true, size: 12 }, | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } else { | ||
+ | // Contenuto legenda | ||
+ | ws[cellAddress].s = { | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Larghezza colonne ottimizzata | ||
+ | ws[' | ||
+ | { width: 8 }, { width: 10 }, { width: 15 }, { width: 10 }, { width: 10 }, | ||
+ | { width: 8 }, { width: 10 }, { width: 15 }, { width: 8 }, { width: 10 }, | ||
+ | { width: 15 }, { width: 8 }, { width: 10 }, { width: 10 }, { width: 10 } | ||
+ | ]; | ||
+ | } | ||
+ | |||
+ | function esportaCaratteristiche() { | ||
+ | try { | ||
+ | const wb = XLSX.utils.book_new(); | ||
+ | |||
+ | // Prepara dati per export | ||
+ | const exportData = [ | ||
+ | [' | ||
+ | ]; | ||
+ | |||
+ | Object.keys(medici).forEach(codice => { | ||
+ | const m = medici[codice]; | ||
+ | exportData.push([ | ||
+ | m.nome, m.cognome, m.ruolo, m.posizione, | ||
+ | m.privilege, | ||
+ | m.limitazioniGravidanza ? ' | ||
+ | m.assente ? ' | ||
+ | ]); | ||
+ | }); | ||
+ | |||
+ | // Aggiungi legenda | ||
+ | exportData.push([]); | ||
+ | exportData.push([' | ||
+ | exportData.push(['', | ||
+ | exportData.push(['', | ||
+ | exportData.push(['', | ||
+ | exportData.push(['', | ||
+ | exportData.push(['', | ||
+ | |||
+ | const ws = XLSX.utils.aoa_to_sheet(exportData); | ||
+ | |||
+ | // Applica stili anche al foglio esportazione caratteristiche | ||
+ | applicaStiliCaratteristiche(ws, | ||
+ | |||
+ | XLSX.utils.book_append_sheet(wb, | ||
+ | |||
+ | const fileName = `Caratteristiche_Medici_${new Date().toISOString().split(' | ||
+ | XLSX.writeFile(wb, | ||
+ | |||
+ | mostraMessaggio(`📄 File esportato: ${fileName}`, | ||
+ | |||
+ | } catch (error) { | ||
+ | mostraMessaggio(' | ||
+ | } | ||
+ | } | ||
// SCHEMA COPERTURA CONFIGURABILE | // SCHEMA COPERTURA CONFIGURABILE | ||
Linea 1185: | Linea 1588: | ||
controlliSuperati: | controlliSuperati: | ||
avvisi: [], | avvisi: [], | ||
- | errori: [] | + | errori: [], |
+ | successiImportanti: | ||
+ | suggerimenti: | ||
+ | punteggioQualita: | ||
}; | }; | ||
| | ||
const giorni = Object.keys(turniMensili).length; | const giorni = Object.keys(turniMensili).length; | ||
+ | if (giorni === 0) { | ||
+ | validazione.errori.push(" | ||
+ | return validazione; | ||
+ | } | ||
| | ||
- | // 1. Verifica copertura minima giornaliera | + | |
+ | |||
+ | let coperturaTotale = 0; | ||
+ | let coperturaCompleta = 0; | ||
+ | |||
+ | | ||
for (let giorno = 1; giorno <= giorni; giorno++) { | for (let giorno = 1; giorno <= giorni; giorno++) { | ||
- | const turni = turniMensili[giorno].turni; | + | const turni = turniMensili[giorno]; |
+ | if (!turni || !turni.turni) { | ||
+ | validazione.errori.push(`Giorno ${giorno}: Struttura turni mancante`); | ||
+ | continue; | ||
+ | } | ||
| | ||
- | if (!turni.guardiaGiorno) { | + | |
+ | let coperturaGiorno = 0; | ||
+ | |||
+ | // Guardia giorno | ||
+ | | ||
validazione.errori.push(`Giorno ${giorno}: Manca guardia giorno`); | validazione.errori.push(`Giorno ${giorno}: Manca guardia giorno`); | ||
} else { | } else { | ||
validazione.controlliSuperati++; | validazione.controlliSuperati++; | ||
+ | coperturaGiorno++; | ||
} | } | ||
| | ||
- | if (!turni.guardiaNotte) { | + | |
+ | | ||
validazione.errori.push(`Giorno ${giorno}: Manca guardia notte`); | validazione.errori.push(`Giorno ${giorno}: Manca guardia notte`); | ||
} else { | } else { | ||
validazione.controlliSuperati++; | validazione.controlliSuperati++; | ||
+ | coperturaGiorno++; | ||
} | } | ||
| | ||
- | if (!turni.reperibilita) { | + | |
+ | | ||
validazione.errori.push(`Giorno ${giorno}: Manca reperibilità`); | validazione.errori.push(`Giorno ${giorno}: Manca reperibilità`); | ||
} else { | } else { | ||
validazione.controlliSuperati++; | validazione.controlliSuperati++; | ||
+ | coperturaGiorno++; | ||
} | } | ||
+ | | ||
+ | coperturaTotale += 3; // 3 controlli per giorno | ||
+ | coperturaCompleta += coperturaGiorno; | ||
+ | } | ||
+ | | ||
+ | // Aggiungi successo per copertura H24 | ||
+ | if (coperturaCompleta === coperturaTotale) { | ||
+ | validazione.successiImportanti.push(" | ||
} | } | ||
| | ||
- | // 2. Verifica vincoli per medico | + | // 2. ✅ VERIFICA VINCOLI PER MEDICO |
+ | let mediciValidati = 0; | ||
Object.keys(medici).forEach(medico => { | Object.keys(medici).forEach(medico => { | ||
const stats = statisticheMensili[medico]; | const stats = statisticheMensili[medico]; | ||
const config = medici[medico]; | const config = medici[medico]; | ||
| | ||
- | // Verifica ore settimanali | + | |
+ | validazione.avvisi.push(`${medico}: | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | mediciValidati++; | ||
+ | |||
+ | | ||
const oreStimate = (stats.guardie.giorno + stats.guardie.notte) * 12 + | const oreStimate = (stats.guardie.giorno + stats.guardie.notte) * 12 + | ||
- | (stats.turni.ptn + stats.turni.urgenze) * 8 + | + | (stats.turni.ptn + stats.turni.urgenze |
stats.turni.nido * 4.5; | stats.turni.nido * 4.5; | ||
const settimane = Math.ceil(giorni / 7); | const settimane = Math.ceil(giorni / 7); | ||
- | const oreSettimanaliMedie = oreStimate / settimane; | + | const oreSettimanaliMedie = settimane > 0 ? oreStimate / settimane |
| | ||
if (oreSettimanaliMedie < config.minOreSettimanali) { | if (oreSettimanaliMedie < config.minOreSettimanali) { | ||
- | validazione.avvisi.push(`${medico}: | + | validazione.avvisi.push(`${medico}: |
} else if (oreSettimanaliMedie > config.maxOreSettimanali) { | } else if (oreSettimanaliMedie > config.maxOreSettimanali) { | ||
- | validazione.avvisi.push(`${medico}: | + | validazione.avvisi.push(`${medico}: |
} else { | } else { | ||
validazione.controlliSuperati++; | validazione.controlliSuperati++; | ||
Linea 1234: | Linea 1678: | ||
| | ||
// Verifica riposi minimi | // Verifica riposi minimi | ||
- | const riposiSettimanaliMedi = stats.riposi / settimane; | + | const riposiSettimanaliMedi = settimane > 0 ? stats.riposi / settimane |
if (riposiSettimanaliMedi < vincoliAvanzati.riposiMinimi) { | if (riposiSettimanaliMedi < vincoliAvanzati.riposiMinimi) { | ||
- | validazione.avvisi.push(`${medico}: | + | validazione.avvisi.push(`${medico}: |
} else { | } else { | ||
validazione.controlliSuperati++; | validazione.controlliSuperati++; | ||
+ | } | ||
+ | | ||
+ | // Verifica competenze utilizzate correttamente | ||
+ | if (stats.guardie.notte > 0 && !config.guardiaNotte) { | ||
+ | validazione.errori.push(`${medico}: | ||
+ | } | ||
+ | if (stats.turni.reperibilita > 0 && !config.reperibilita) { | ||
+ | validazione.errori.push(`${medico}: | ||
} | } | ||
}); | }); | ||
+ | | ||
+ | // 3. ✅ VERIFICA BILANCIAMENTO | ||
+ | const guardieDiurne = Object.values(statisticheMensili).map(s => s.guardie.giorno).filter(g => g > 0); | ||
+ | const guardieNotturne = Object.values(statisticheMensili).map(s => s.guardie.notte).filter(g => g > 0); | ||
+ | | ||
+ | if (guardieDiurne.length > 0) { | ||
+ | const maxGD = Math.max(...guardieDiurne); | ||
+ | const minGD = Math.min(...guardieDiurne); | ||
+ | const differenzaGD = maxGD - minGD; | ||
+ | | ||
+ | if (differenzaGD <= 2) { | ||
+ | validazione.successiImportanti.push(" | ||
+ | validazione.controlliSuperati++; | ||
+ | } else { | ||
+ | validazione.avvisi.push(`Guardie diurne sbilanciate: | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | if (guardieNotturne.length > 0) { | ||
+ | const maxGN = Math.max(...guardieNotturne); | ||
+ | const minGN = Math.min(...guardieNotturne); | ||
+ | const differenzaGN = maxGN - minGN; | ||
+ | | ||
+ | if (differenzaGN <= 2) { | ||
+ | validazione.successiImportanti.push(" | ||
+ | validazione.controlliSuperati++; | ||
+ | } else { | ||
+ | validazione.avvisi.push(`Guardie notturne sbilanciate: | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | // 4. ✅ VERIFICA REPERIBILITÀ 7/7 | ||
+ | let reperibilita7su7 = true; | ||
+ | for (let giorno = 1; giorno <= giorni; giorno++) { | ||
+ | if (!turniMensili[giorno].turni.reperibilita) { | ||
+ | reperibilita7su7 = false; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | if (reperibilita7su7) { | ||
+ | validazione.successiImportanti.push(" | ||
+ | validazione.controlliSuperati++; | ||
+ | } | ||
+ | | ||
+ | // 5. ✅ CONTROLLI SPECIALI MC e VE | ||
+ | const mcStats = statisticheMensili[' | ||
+ | const veStats = statisticheMensili[' | ||
+ | | ||
+ | if (mcStats && mcStats.guardie.giorno === 0 && mcStats.guardie.notte === 0) { | ||
+ | validazione.successiImportanti.push(" | ||
+ | validazione.controlliSuperati++; | ||
+ | } | ||
+ | | ||
+ | if (veStats && veStats.turni.nido >= 4) { | ||
+ | validazione.successiImportanti.push(" | ||
+ | validazione.controlliSuperati++; | ||
+ | } | ||
+ | | ||
+ | // 6. 💡 SUGGERIMENTI | ||
+ | if (validazione.avvisi.length > validazione.errori.length * 2) { | ||
+ | validazione.suggerimenti.push(" | ||
+ | } | ||
+ | | ||
+ | if (giorni >= 28 && validazione.controlliSuperati < giorni * 2) { | ||
+ | validazione.suggerimenti.push(" | ||
+ | } | ||
+ | | ||
+ | if (Object.keys(statisticheMensili).length < Object.keys(medici).length) { | ||
+ | validazione.suggerimenti.push(" | ||
+ | } | ||
+ | | ||
+ | // 7. 📊 CALCOLA PUNTEGGIO QUALITÀ | ||
+ | const controlliTotali = coperturaTotale + mediciValidati * 2 + 5; // Stima controlli totali | ||
+ | const percentualeSuccesso = controlliTotali > 0 ? Math.round((validazione.controlliSuperati / controlliTotali) * 100) : 0; | ||
+ | | ||
+ | // Penalità per errori critici | ||
+ | const penalitaErrori = validazione.errori.length * 10; | ||
+ | const penalitaAvvisi = validazione.avvisi.length * 2; | ||
+ | | ||
+ | validazione.punteggioQualita = Math.max(0, Math.min(100, | ||
+ | | ||
+ | console.log(`Validazione completata: ${validazione.controlliSuperati} controlli superati, ${validazione.errori.length} errori, ${validazione.avvisi.length} avvisi`); | ||
+ | console.log(`Punteggio qualità: ${validazione.punteggioQualita}%`); | ||
| | ||
return validazione; | return validazione; | ||
Linea 1459: | Linea 1995: | ||
const wb = XLSX.utils.book_new(); | const wb = XLSX.utils.book_new(); | ||
| | ||
- | // FOGLIO 1: TURNI | + | // FOGLIO 1: TURNI CON BORDI |
const excelData = []; | const excelData = []; | ||
| | ||
- | // Intestazioni | + | // Intestazioni |
excelData.push([' | excelData.push([' | ||
excelData.push([' | excelData.push([' | ||
Linea 1471: | Linea 2007: | ||
| | ||
// Intestazioni giorni | // Intestazioni giorni | ||
- | const rigaIntestazioni = ['', | + | const rigaIntestazioni = ['MEDICO', '' |
for (let giorno = 1; giorno <= giorni; giorno++) { | for (let giorno = 1; giorno <= giorni; giorno++) { | ||
const data = new Date(anno, mese, giorno); | const data = new Date(anno, mese, giorno); | ||
Linea 1484: | Linea 2020: | ||
const tuttiMedici = Object.keys(medici); | const tuttiMedici = Object.keys(medici); | ||
tuttiMedici.forEach(codMedico => { | tuttiMedici.forEach(codMedico => { | ||
- | const rigaMedico = [medici[codMedico].nome, '' | + | const rigaMedico = [medici[codMedico].nomeCompleto, '' |
| | ||
for (let giorno = 1; giorno <= giorni; giorno++) { | for (let giorno = 1; giorno <= giorni; giorno++) { | ||
Linea 1505: | Linea 2041: | ||
const ws1 = XLSX.utils.aoa_to_sheet(excelData); | const ws1 = XLSX.utils.aoa_to_sheet(excelData); | ||
+ | | ||
+ | // Applica stili e bordi al foglio turni | ||
+ | applicaStiliExcel(ws1, | ||
+ | | ||
XLSX.utils.book_append_sheet(wb, | XLSX.utils.book_append_sheet(wb, | ||
Linea 1536: | Linea 2076: | ||
| | ||
const ws2 = XLSX.utils.aoa_to_sheet(statsData); | const ws2 = XLSX.utils.aoa_to_sheet(statsData); | ||
+ | | ||
+ | // Applica stili al foglio statistiche | ||
+ | applicaStiliStatistiche(ws2, | ||
+ | | ||
XLSX.utils.book_append_sheet(wb, | XLSX.utils.book_append_sheet(wb, | ||
- | // FOGLIO 3: VALIDAZIONE | + | // FOGLIO 3: VALIDAZIONE |
const validazione = validaTurniCompleti(); | const validazione = validaTurniCompleti(); | ||
const validazioneData = []; | const validazioneData = []; | ||
validazioneData.push([' | validazioneData.push([' | ||
+ | validazioneData.push([]); | ||
+ | validazioneData.push([' | ||
validazioneData.push([' | validazioneData.push([' | ||
validazioneData.push([' | validazioneData.push([' | ||
- | validazioneData.push([' | + | validazioneData.push([' |
+ | validazioneData.push([' | ||
validazioneData.push([]); | validazioneData.push([]); | ||
+ | | ||
+ | if (validazione.successiImportanti.length > 0) { | ||
+ | validazioneData.push([' | ||
+ | validazione.successiImportanti.forEach(successo => { | ||
+ | validazioneData.push([successo, | ||
+ | }); | ||
+ | validazioneData.push([]); | ||
+ | } | ||
| | ||
if (validazione.avvisi.length > 0) { | if (validazione.avvisi.length > 0) { | ||
- | validazioneData.push([' | + | validazioneData.push([' |
validazione.avvisi.forEach(avviso => { | validazione.avvisi.forEach(avviso => { | ||
- | validazioneData.push([avviso]); | + | validazioneData.push([avviso, ' |
}); | }); | ||
validazioneData.push([]); | validazioneData.push([]); | ||
Linea 1556: | Linea 2111: | ||
| | ||
if (validazione.errori.length > 0) { | if (validazione.errori.length > 0) { | ||
- | validazioneData.push([' | + | validazioneData.push([' |
validazione.errori.forEach(errore => { | validazione.errori.forEach(errore => { | ||
- | validazioneData.push([errore]); | + | validazioneData.push([errore, ' |
}); | }); | ||
} | } | ||
| | ||
const ws3 = XLSX.utils.aoa_to_sheet(validazioneData); | const ws3 = XLSX.utils.aoa_to_sheet(validazioneData); | ||
+ | | ||
+ | // Applica stili al foglio validazione | ||
+ | applicaStiliValidazione(ws3, | ||
+ | | ||
XLSX.utils.book_append_sheet(wb, | XLSX.utils.book_append_sheet(wb, | ||
- | | + | // 🎨 FUNZIONI PER STILI EXCEL CON BORDI |
+ | function applicaStiliExcel(ws, | ||
+ | const range = XLSX.utils.decode_range(ws[' | ||
+ | |||
+ | // Stili per bordi | ||
+ | const borderStyle = { | ||
+ | border: { | ||
+ | top: { style: ' | ||
+ | bottom: { style: ' | ||
+ | left: { style: ' | ||
+ | right: { style: ' | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | const headerStyle = { | ||
+ | font: { bold: true, color: { rgb: ' | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: { | ||
+ | top: { style: ' | ||
+ | bottom: { style: ' | ||
+ | left: { style: ' | ||
+ | right: { style: ' | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | const titleStyle = { | ||
+ | font: { bold: true, size: 14 }, | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | |||
+ | // Applica stili alle intestazioni (riga 7 - giorni) | ||
+ | for (let col = 0; col <= range.e.c; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: 6, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | ws[cellAddress].s = headerStyle; | ||
+ | } | ||
+ | |||
+ | // Applica bordi a tutte le celle dei dati | ||
+ | for (let row = 7; row <= range.e.r; row++) { | ||
+ | for (let col = 0; col <= range.e.c; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | |||
+ | if (col === 0) { | ||
+ | // Prima colonna (nomi medici) - stile speciale | ||
+ | ws[cellAddress].s = { | ||
+ | font: { bold: true }, | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } else { | ||
+ | // Altre celle - bordi normali | ||
+ | ws[cellAddress].s = { | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Applica stili ai titoli (prime 5 righe) | ||
+ | for (let row = 0; row < 5; row++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: 0 }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | ws[cellAddress].s = titleStyle; | ||
+ | } | ||
+ | |||
+ | // Imposta larghezza colonne | ||
+ | const colWidths = [{ width: 20 }, { width: 3 }]; | ||
+ | for (let i = 2; i < numColonne; i++) { | ||
+ | colWidths.push({ width: 12 }); | ||
+ | } | ||
+ | ws[' | ||
+ | } | ||
+ | |||
+ | function applicaStiliStatistiche(ws, | ||
+ | const range = XLSX.utils.decode_range(ws[' | ||
+ | |||
+ | const borderStyle = { | ||
+ | border: { | ||
+ | top: { style: ' | ||
+ | bottom: { style: ' | ||
+ | left: { style: ' | ||
+ | right: { style: ' | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // Intestazione (riga 1) | ||
+ | for (let col = 0; col < numColonne; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: 1, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | ws[cellAddress].s = { | ||
+ | font: { bold: true, color: { rgb: ' | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | // Dati | ||
+ | for (let row = 2; row <= range.e.r; row++) { | ||
+ | for (let col = 0; col < numColonne; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | ws[cellAddress].s = { | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Larghezza colonne | ||
+ | ws[' | ||
+ | { width: 25 }, { width: 12 }, { width: 12 }, { width: 8 }, { width: 8 }, | ||
+ | { width: 10 }, { width: 10 }, { width: 12 }, { width: 8 }, { width: 12 }, { width: 12 } | ||
+ | ]; | ||
+ | } | ||
+ | |||
+ | function applicaStiliValidazione(ws, | ||
+ | const range = XLSX.utils.decode_range(ws[' | ||
+ | |||
+ | const borderStyle = { | ||
+ | border: { | ||
+ | top: { style: ' | ||
+ | bottom: { style: ' | ||
+ | left: { style: ' | ||
+ | right: { style: ' | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // Applica bordi a tutte le celle | ||
+ | for (let row = 0; row <= range.e.r; row++) { | ||
+ | for (let col = 0; col < numColonne; col++) { | ||
+ | const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); | ||
+ | if (!ws[cellAddress]) ws[cellAddress] = { v: '', | ||
+ | |||
+ | // Titoli sezioni in grassetto | ||
+ | if (ws[cellAddress].v && typeof ws[cellAddress].v === ' | ||
+ | if (ws[cellAddress].v.includes(' | ||
+ | ws[cellAddress].v.includes(' | ||
+ | ws[cellAddress].v.includes(' | ||
+ | ws[cellAddress].v.includes(' | ||
+ | ws[cellAddress].s = { | ||
+ | font: { bold: true, size: 12 }, | ||
+ | fill: { fgColor: { rgb: ' | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } else { | ||
+ | ws[cellAddress].s = { | ||
+ | alignment: { horizontal: ' | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | } else { | ||
+ | ws[cellAddress].s = { | ||
+ | border: borderStyle.border | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Larghezza colonne | ||
+ | ws[' | ||
+ | } | ||
+ | |||
+ | | ||
const fileName = `Turni_TIN_Perfezionati_${nomiMesi[mese]}_${anno}.xlsx`; | const fileName = `Turni_TIN_Perfezionati_${nomiMesi[mese]}_${anno}.xlsx`; | ||
XLSX.writeFile(wb, | XLSX.writeFile(wb, | ||
| | ||
- | mostraMessaggio(`✅ File Excel esportato con successo: ${fileName}`, | + | mostraMessaggio(`✅ File Excel esportato con bordi e stili: ${fileName}`, |
| | ||
} catch (error) { | } catch (error) { |