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) { | ||