mantenimento-app 2.2.4 → 2.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.js CHANGED
@@ -3457,15 +3457,17 @@ const defaultExpenseItems = [
3457
3457
  const housingUtilityNonColl = collocatario === 1 ? housingUtility2 : housingUtility1;
3458
3458
 
3459
3459
  const baseDuplicazione = speseConvivenza > 0 ? (speseTot - speseConvivenza) : null;
3460
+ // OECD-modified equivalence logic suggests split-household extra burden is material but not full duplication.
3461
+ const HOUSING_UTILITY_ADJ_FACTOR = 0.35;
3460
3462
  const separationAdjustmentHousingUtilities = baseDuplicazione !== null && baseDuplicazione <= 0.005
3461
- ? Math.max(0, housingUtilityNonColl)
3463
+ ? Math.max(0, housingUtilityNonColl * HOUSING_UTILITY_ADJ_FACTOR)
3462
3464
  : 0;
3463
- const speseConvivenzaEffettive = speseConvivenza > 0
3464
- ? Math.max(0, speseConvivenza - separationAdjustmentHousingUtilities)
3465
+ const speseConvivenzaEffettive = speseConvivenza > 0 ? speseConvivenza : null;
3466
+ const costoSeparazioneMensile = baseDuplicazione !== null
3467
+ ? Math.max(0, baseDuplicazione + separationAdjustmentHousingUtilities)
3465
3468
  : null;
3466
- const costoSeparazioneMensile = speseConvivenzaEffettive !== null ? (speseTot - speseConvivenzaEffettive) : null;
3467
3469
  const nettoInsiemeCombinato = speseConvivenzaEffettive !== null ? (r1 + r2 - speseConvivenzaEffettive) : null;
3468
- const nettoSeparatoTotale = post1 + post2;
3470
+ const nettoSeparatoTotale = (post1 + post2) - separationAdjustmentHousingUtilities;
3469
3471
  const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
3470
3472
  const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
3471
3473
  const totReddito = Math.max(0.001, r1 + r2);
@@ -4820,7 +4822,7 @@ const defaultExpenseItems = [
4820
4822
  const el = document.getElementById(`${spouseKey}d_${idx}`);
4821
4823
  const raw = String(el && el.value ? el.value : "").trim();
4822
4824
  if (!raw) return "";
4823
- return `${escapeHtml(raw)} <span class="expense-detail-meta">(${raw.length}/${EXPENSE_DETAIL_MAX_CHARS})</span>`;
4825
+ return `${escapeHtml(raw)}`;
4824
4826
  };
4825
4827
  const speseRowsBase = expenseItems.map((item, i) => {
4826
4828
  const c1 = num(`c1_${i}`);
@@ -4956,6 +4958,48 @@ const defaultExpenseItems = [
4956
4958
  const scenarioSectionClass = scenarioLab.length >= 3
4957
4959
  ? "section scenario-section compact-3"
4958
4960
  : "section scenario-section";
4961
+ const scenarioSavedTitle = currentLang === "en" ? "Saved scenarios overview" : "Quadro scenari salvati";
4962
+ const scenarioActiveLabel = currentLang === "en" ? "selected" : "selezionato";
4963
+ const scenarioSupportLabel = currentLang === "en" ? "Support" : "Assegno";
4964
+ const scenarioNeedsLabel = currentLang === "en" ? "Children needs" : "Fabbisogno figli";
4965
+ const scenarioPostLabel = currentLang === "en" ? "Post-support" : "Post-assegno";
4966
+ const scenarioCardsHtml = scenarioLab.length
4967
+ ? `<div class="scenario-mini-grid">${scenarioLab.map((scenario, idx) => {
4968
+ const sm = scenario.model || computeModelLocal(scenario.payload || {});
4969
+ const s1 = escapeHtml(String((scenario.payload && scenario.payload._nome1) || c1Name || tr("spouse1Default")));
4970
+ const s2 = escapeHtml(String((scenario.payload && scenario.payload._nome2) || c2Name || tr("spouse2Default")));
4971
+ const modeTxt = escapeHtml(getModeName(sm.mode, sm.simplePerc));
4972
+ const support = eur(Math.max(sm.assegnoDa1a2 || 0, sm.assegnoDa2a1 || 0));
4973
+ const selectedChip = idx === selectedScenarioIdx ? `<span class="scenario-mini-selected">${escapeHtml(scenarioActiveLabel)}</span>` : "";
4974
+ return `<div class="scenario-mini-card">
4975
+ <div class="scenario-mini-head"><span class="scenario-chip">Sc ${escapeHtml(scenario.label || SCENARIO_LABELS[idx] || String(idx + 1))}</span>${selectedChip}</div>
4976
+ <div class="scenario-mini-meta">${s1} / ${s2}</div>
4977
+ <div class="scenario-mini-mode">${modeTxt}</div>
4978
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioSupportLabel)}</span><strong>${support}</strong></div>
4979
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioNeedsLabel)}</span><strong>${eur(sm.fabbisognoFigli || 0)}</strong></div>
4980
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioPostLabel)} ${s1}</span><strong>${eur(sm.post1 || 0)}</strong></div>
4981
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioPostLabel)} ${s2}</span><strong>${eur(sm.post2 || 0)}</strong></div>
4982
+ </div>`;
4983
+ }).join("")}</div>`
4984
+ : "";
4985
+ const separationSectionHtml = m.speseConvivenza > 0
4986
+ ? `
4987
+ <div class="section">
4988
+ <div class="section-title">${tr("sepCostPanelTitle")}</div>
4989
+ <table>
4990
+ <tbody>
4991
+ <tr><td>${tr("sepCostNetTogether")}</td><td class="num"><strong>${eur(m.nettoInsiemeCombinato || 0)}</strong></td></tr>
4992
+ <tr><td>${tr("sepCostNetSeparated")}</td><td class="num"><strong>${eur(m.nettoSeparatoTotale || 0)}</strong></td></tr>
4993
+ ${m.separationAdjustmentHousingUtilities > 0 ? `<tr><td>${tr("sepCostHousingUtilityAdj")}</td><td class="num">${eur(m.separationAdjustmentHousingUtilities)}</td></tr>` : ""}
4994
+ <tr><td>${tr("sepCostDuplication")}</td><td class="num"><strong>${eur(m.costoSeparazioneMensile || 0)}</strong></td></tr>
4995
+ <tr><td>${tr("sepCostLossMonthly")}</td><td class="num"><strong>${eur(m.perditaMensile || 0)}</strong></td></tr>
4996
+ <tr><td>${tr("sepCostLossAnnually")}</td><td class="num"><strong>${eur(m.perditaAnnua || 0)}</strong></td></tr>
4997
+ <tr><td>${msg("sepCostLossSpouse", { spouse: c1NameEsc })}</td><td class="num">${eur(m.perditaSpouse1 || 0)}</td></tr>
4998
+ <tr><td>${msg("sepCostLossSpouse", { spouse: c2NameEsc })}</td><td class="num">${eur(m.perditaSpouse2 || 0)}</td></tr>
4999
+ </tbody>
5000
+ </table>
5001
+ </div>`
5002
+ : "";
4959
5003
 
4960
5004
  const html = `<!DOCTYPE html>
4961
5005
  <html lang="${pdfLang}">
@@ -5082,6 +5126,14 @@ const defaultExpenseItems = [
5082
5126
 
5083
5127
  /* ── SCENARIO LAB ── */
5084
5128
  .scenario-compare-wrap { overflow-x: auto; }
5129
+ .scenario-mini-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; margin-bottom: 10px; }
5130
+ .scenario-mini-card { border: 1px solid #c6ddd8; border-radius: 8px; background: #f7fcfb; padding: 7px 8px; }
5131
+ .scenario-mini-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 3px; }
5132
+ .scenario-mini-selected { font-size: 6.7pt; color: #0f6a61; font-weight: 700; border: 1px solid #a9d4c0; border-radius: 999px; padding: 0 6px; background: #eaf7f1; }
5133
+ .scenario-mini-meta { font-size: 7.1pt; color: #4c6964; margin-bottom: 2px; }
5134
+ .scenario-mini-mode { font-size: 7.2pt; font-weight: 700; color: #183d39; margin-bottom: 5px; }
5135
+ .scenario-mini-row { display: flex; justify-content: space-between; gap: 8px; font-size: 7.4pt; padding: 1px 0; }
5136
+ .scenario-mini-row strong { font-variant-numeric: tabular-nums; }
5085
5137
  .scenario-compare-table { width: 100%; border-collapse: collapse; font-size: 8.5pt; }
5086
5138
  .scenario-compare-table th, .scenario-compare-table td { border: 1px solid #c6ddd8; padding: 5px 8px; }
5087
5139
  .scenario-compare-table thead th { background: linear-gradient(90deg,#f0f7f5,#e6f1ee); color: #183d39; font-weight: 700; }
@@ -5385,6 +5437,8 @@ const defaultExpenseItems = [
5385
5437
  </div>
5386
5438
  </div>
5387
5439
 
5440
+ ${separationSectionHtml}
5441
+
5388
5442
  <div class="section">
5389
5443
  <div class="section-title">${tr("spiegTitle")}</div>
5390
5444
  <div class="pdf-explain-grid">
@@ -5419,6 +5473,8 @@ const defaultExpenseItems = [
5419
5473
  ${scenarioLab.length ? `
5420
5474
  <div class="${scenarioSectionClass}">
5421
5475
  <div class="section-title">${tr("pdfScenarioSection")}</div>
5476
+ <div style="font-size:8pt;font-weight:700;color:#1a4e49;margin-bottom:6px;">${escapeHtml(scenarioSavedTitle)}</div>
5477
+ ${scenarioCardsHtml}
5422
5478
  ${scenarioPdfTable}
5423
5479
  </div>
5424
5480
  ` : ""}
@@ -132,15 +132,16 @@ function calculateModel(input) {
132
132
  const housingUtility2 = c2Spese.reduce((acc, n, idx) => acc + (housingIdx.has(idx) ? toNumber(n) : 0), 0) + quotaMutuoSpese2;
133
133
  const housingUtilityNonColl = collocatario === 1 ? housingUtility2 : housingUtility1;
134
134
  const baseDuplicazione = speseConvivenza > 0 ? (speseTot - speseConvivenza) : null;
135
+ const HOUSING_UTILITY_ADJ_FACTOR = 0.35;
135
136
  const separationAdjustmentHousingUtilities = baseDuplicazione !== null && baseDuplicazione <= 0.005
136
- ? Math.max(0, housingUtilityNonColl)
137
+ ? Math.max(0, housingUtilityNonColl * HOUSING_UTILITY_ADJ_FACTOR)
137
138
  : 0;
138
- const speseConvivenzaEffettive = speseConvivenza > 0
139
- ? Math.max(0, speseConvivenza - separationAdjustmentHousingUtilities)
139
+ const speseConvivenzaEffettive = speseConvivenza > 0 ? speseConvivenza : null;
140
+ const costoSeparazioneMensile = baseDuplicazione !== null
141
+ ? Math.max(0, baseDuplicazione + separationAdjustmentHousingUtilities)
140
142
  : null;
141
- const costoSeparazioneMensile = speseConvivenzaEffettive !== null ? (speseTot - speseConvivenzaEffettive) : null;
142
143
  const nettoInsiemeCombinato = speseConvivenzaEffettive !== null ? (r1 + r2 - speseConvivenzaEffettive) : null;
143
- const nettoSeparatoTotale = post1 + post2;
144
+ const nettoSeparatoTotale = (post1 + post2) - separationAdjustmentHousingUtilities;
144
145
  const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
145
146
  const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
146
147
  const totReddito = Math.max(0.001, r1 + r2);
@@ -3457,15 +3457,17 @@ const defaultExpenseItems = [
3457
3457
  const housingUtilityNonColl = collocatario === 1 ? housingUtility2 : housingUtility1;
3458
3458
 
3459
3459
  const baseDuplicazione = speseConvivenza > 0 ? (speseTot - speseConvivenza) : null;
3460
+ // OECD-modified equivalence logic suggests split-household extra burden is material but not full duplication.
3461
+ const HOUSING_UTILITY_ADJ_FACTOR = 0.35;
3460
3462
  const separationAdjustmentHousingUtilities = baseDuplicazione !== null && baseDuplicazione <= 0.005
3461
- ? Math.max(0, housingUtilityNonColl)
3463
+ ? Math.max(0, housingUtilityNonColl * HOUSING_UTILITY_ADJ_FACTOR)
3462
3464
  : 0;
3463
- const speseConvivenzaEffettive = speseConvivenza > 0
3464
- ? Math.max(0, speseConvivenza - separationAdjustmentHousingUtilities)
3465
+ const speseConvivenzaEffettive = speseConvivenza > 0 ? speseConvivenza : null;
3466
+ const costoSeparazioneMensile = baseDuplicazione !== null
3467
+ ? Math.max(0, baseDuplicazione + separationAdjustmentHousingUtilities)
3465
3468
  : null;
3466
- const costoSeparazioneMensile = speseConvivenzaEffettive !== null ? (speseTot - speseConvivenzaEffettive) : null;
3467
3469
  const nettoInsiemeCombinato = speseConvivenzaEffettive !== null ? (r1 + r2 - speseConvivenzaEffettive) : null;
3468
- const nettoSeparatoTotale = post1 + post2;
3470
+ const nettoSeparatoTotale = (post1 + post2) - separationAdjustmentHousingUtilities;
3469
3471
  const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
3470
3472
  const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
3471
3473
  const totReddito = Math.max(0.001, r1 + r2);
@@ -4820,7 +4822,7 @@ const defaultExpenseItems = [
4820
4822
  const el = document.getElementById(`${spouseKey}d_${idx}`);
4821
4823
  const raw = String(el && el.value ? el.value : "").trim();
4822
4824
  if (!raw) return "";
4823
- return `${escapeHtml(raw)} <span class="expense-detail-meta">(${raw.length}/${EXPENSE_DETAIL_MAX_CHARS})</span>`;
4825
+ return `${escapeHtml(raw)}`;
4824
4826
  };
4825
4827
  const speseRowsBase = expenseItems.map((item, i) => {
4826
4828
  const c1 = num(`c1_${i}`);
@@ -4956,6 +4958,48 @@ const defaultExpenseItems = [
4956
4958
  const scenarioSectionClass = scenarioLab.length >= 3
4957
4959
  ? "section scenario-section compact-3"
4958
4960
  : "section scenario-section";
4961
+ const scenarioSavedTitle = currentLang === "en" ? "Saved scenarios overview" : "Quadro scenari salvati";
4962
+ const scenarioActiveLabel = currentLang === "en" ? "selected" : "selezionato";
4963
+ const scenarioSupportLabel = currentLang === "en" ? "Support" : "Assegno";
4964
+ const scenarioNeedsLabel = currentLang === "en" ? "Children needs" : "Fabbisogno figli";
4965
+ const scenarioPostLabel = currentLang === "en" ? "Post-support" : "Post-assegno";
4966
+ const scenarioCardsHtml = scenarioLab.length
4967
+ ? `<div class="scenario-mini-grid">${scenarioLab.map((scenario, idx) => {
4968
+ const sm = scenario.model || computeModelLocal(scenario.payload || {});
4969
+ const s1 = escapeHtml(String((scenario.payload && scenario.payload._nome1) || c1Name || tr("spouse1Default")));
4970
+ const s2 = escapeHtml(String((scenario.payload && scenario.payload._nome2) || c2Name || tr("spouse2Default")));
4971
+ const modeTxt = escapeHtml(getModeName(sm.mode, sm.simplePerc));
4972
+ const support = eur(Math.max(sm.assegnoDa1a2 || 0, sm.assegnoDa2a1 || 0));
4973
+ const selectedChip = idx === selectedScenarioIdx ? `<span class="scenario-mini-selected">${escapeHtml(scenarioActiveLabel)}</span>` : "";
4974
+ return `<div class="scenario-mini-card">
4975
+ <div class="scenario-mini-head"><span class="scenario-chip">Sc ${escapeHtml(scenario.label || SCENARIO_LABELS[idx] || String(idx + 1))}</span>${selectedChip}</div>
4976
+ <div class="scenario-mini-meta">${s1} / ${s2}</div>
4977
+ <div class="scenario-mini-mode">${modeTxt}</div>
4978
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioSupportLabel)}</span><strong>${support}</strong></div>
4979
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioNeedsLabel)}</span><strong>${eur(sm.fabbisognoFigli || 0)}</strong></div>
4980
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioPostLabel)} ${s1}</span><strong>${eur(sm.post1 || 0)}</strong></div>
4981
+ <div class="scenario-mini-row"><span>${escapeHtml(scenarioPostLabel)} ${s2}</span><strong>${eur(sm.post2 || 0)}</strong></div>
4982
+ </div>`;
4983
+ }).join("")}</div>`
4984
+ : "";
4985
+ const separationSectionHtml = m.speseConvivenza > 0
4986
+ ? `
4987
+ <div class="section">
4988
+ <div class="section-title">${tr("sepCostPanelTitle")}</div>
4989
+ <table>
4990
+ <tbody>
4991
+ <tr><td>${tr("sepCostNetTogether")}</td><td class="num"><strong>${eur(m.nettoInsiemeCombinato || 0)}</strong></td></tr>
4992
+ <tr><td>${tr("sepCostNetSeparated")}</td><td class="num"><strong>${eur(m.nettoSeparatoTotale || 0)}</strong></td></tr>
4993
+ ${m.separationAdjustmentHousingUtilities > 0 ? `<tr><td>${tr("sepCostHousingUtilityAdj")}</td><td class="num">${eur(m.separationAdjustmentHousingUtilities)}</td></tr>` : ""}
4994
+ <tr><td>${tr("sepCostDuplication")}</td><td class="num"><strong>${eur(m.costoSeparazioneMensile || 0)}</strong></td></tr>
4995
+ <tr><td>${tr("sepCostLossMonthly")}</td><td class="num"><strong>${eur(m.perditaMensile || 0)}</strong></td></tr>
4996
+ <tr><td>${tr("sepCostLossAnnually")}</td><td class="num"><strong>${eur(m.perditaAnnua || 0)}</strong></td></tr>
4997
+ <tr><td>${msg("sepCostLossSpouse", { spouse: c1NameEsc })}</td><td class="num">${eur(m.perditaSpouse1 || 0)}</td></tr>
4998
+ <tr><td>${msg("sepCostLossSpouse", { spouse: c2NameEsc })}</td><td class="num">${eur(m.perditaSpouse2 || 0)}</td></tr>
4999
+ </tbody>
5000
+ </table>
5001
+ </div>`
5002
+ : "";
4959
5003
 
4960
5004
  const html = `<!DOCTYPE html>
4961
5005
  <html lang="${pdfLang}">
@@ -5082,6 +5126,14 @@ const defaultExpenseItems = [
5082
5126
 
5083
5127
  /* ── SCENARIO LAB ── */
5084
5128
  .scenario-compare-wrap { overflow-x: auto; }
5129
+ .scenario-mini-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; margin-bottom: 10px; }
5130
+ .scenario-mini-card { border: 1px solid #c6ddd8; border-radius: 8px; background: #f7fcfb; padding: 7px 8px; }
5131
+ .scenario-mini-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 3px; }
5132
+ .scenario-mini-selected { font-size: 6.7pt; color: #0f6a61; font-weight: 700; border: 1px solid #a9d4c0; border-radius: 999px; padding: 0 6px; background: #eaf7f1; }
5133
+ .scenario-mini-meta { font-size: 7.1pt; color: #4c6964; margin-bottom: 2px; }
5134
+ .scenario-mini-mode { font-size: 7.2pt; font-weight: 700; color: #183d39; margin-bottom: 5px; }
5135
+ .scenario-mini-row { display: flex; justify-content: space-between; gap: 8px; font-size: 7.4pt; padding: 1px 0; }
5136
+ .scenario-mini-row strong { font-variant-numeric: tabular-nums; }
5085
5137
  .scenario-compare-table { width: 100%; border-collapse: collapse; font-size: 8.5pt; }
5086
5138
  .scenario-compare-table th, .scenario-compare-table td { border: 1px solid #c6ddd8; padding: 5px 8px; }
5087
5139
  .scenario-compare-table thead th { background: linear-gradient(90deg,#f0f7f5,#e6f1ee); color: #183d39; font-weight: 700; }
@@ -5385,6 +5437,8 @@ const defaultExpenseItems = [
5385
5437
  </div>
5386
5438
  </div>
5387
5439
 
5440
+ ${separationSectionHtml}
5441
+
5388
5442
  <div class="section">
5389
5443
  <div class="section-title">${tr("spiegTitle")}</div>
5390
5444
  <div class="pdf-explain-grid">
@@ -5419,6 +5473,8 @@ const defaultExpenseItems = [
5419
5473
  ${scenarioLab.length ? `
5420
5474
  <div class="${scenarioSectionClass}">
5421
5475
  <div class="section-title">${tr("pdfScenarioSection")}</div>
5476
+ <div style="font-size:8pt;font-weight:700;color:#1a4e49;margin-bottom:6px;">${escapeHtml(scenarioSavedTitle)}</div>
5477
+ ${scenarioCardsHtml}
5422
5478
  ${scenarioPdfTable}
5423
5479
  </div>
5424
5480
  ` : ""}
@@ -599,7 +599,7 @@
599
599
  <script src="supabase.min.js"></script>
600
600
  <script src="fabric.min.js"></script>
601
601
  <script src="html2pdf.bundle.min.js"></script>
602
- <script src="app.js?v=2.2.4"></script>
602
+ <script src="app.js?v=2.2.6"></script>
603
603
  </body>
604
604
  </html>
605
605
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mantenimento-app",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "description": "Frontend + backend architecture for the mantenimento calculator",
5
5
  "type": "commonjs",
6
6
  "main": "backend/calculate-model.js",