mantenimento-app 2.4.7 → 2.4.9
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 +175 -8
- package/frontend/public/app.js +175 -8
- package/frontend/public/index.html +2 -2
- package/frontend/public/styles.css +97 -8
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -30,6 +30,7 @@ const defaultExpenseItems = [
|
|
|
30
30
|
const SCENARIO_LABELS = ["A", "B", "C", "D", "E", "F"];
|
|
31
31
|
const EXPENSE_DETAIL_MAX_CHARS = 560;
|
|
32
32
|
const EXPENSE_DETAIL_MAX_LINES = 10;
|
|
33
|
+
const EXPENSE_DETAIL_MAX_ROWS = 8;
|
|
33
34
|
|
|
34
35
|
const QUOTA_MANTENIMENTO_PERC = 35;
|
|
35
36
|
|
|
@@ -250,6 +251,11 @@ const defaultExpenseItems = [
|
|
|
250
251
|
expenseDetailBtn: "Dettaglio",
|
|
251
252
|
expenseDetailTitle: "Apri dettaglio voce spesa",
|
|
252
253
|
expenseDetailPlaceholder: "Scrivi qui il dettaglio di questa cifra (es. mesi, quota, riferimento).",
|
|
254
|
+
expenseDetailColThing: "Cosa",
|
|
255
|
+
expenseDetailColAmount: "Quanto",
|
|
256
|
+
expenseDetailColDue: "Scadenza",
|
|
257
|
+
expenseDetailAddRow: "Aggiungi riga",
|
|
258
|
+
expenseDetailRemoveRow: "Rimuovi riga",
|
|
253
259
|
expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
|
|
254
260
|
expenseRemoveTitle: "Rimuovi voce spesa",
|
|
255
261
|
expenseRemoveBtn: "Rimuovi",
|
|
@@ -612,6 +618,11 @@ const defaultExpenseItems = [
|
|
|
612
618
|
expenseDetailBtn: "Detail",
|
|
613
619
|
expenseDetailTitle: "Open expense detail",
|
|
614
620
|
expenseDetailPlaceholder: "Write details for this amount (e.g. months, share, reference).",
|
|
621
|
+
expenseDetailColThing: "What",
|
|
622
|
+
expenseDetailColAmount: "How much",
|
|
623
|
+
expenseDetailColDue: "Due date",
|
|
624
|
+
expenseDetailAddRow: "Add row",
|
|
625
|
+
expenseDetailRemoveRow: "Remove row",
|
|
615
626
|
expenseDetailCharsRemaining: "Remaining characters: {count}",
|
|
616
627
|
expenseRemoveTitle: "Remove expense item",
|
|
617
628
|
expenseRemoveBtn: "Remove",
|
|
@@ -1457,6 +1468,105 @@ const defaultExpenseItems = [
|
|
|
1457
1468
|
if (!textarea) return;
|
|
1458
1469
|
autoResizeExpenseDetailTextarea(textarea, preferredHeight);
|
|
1459
1470
|
updateExpenseDetailCounter(textarea);
|
|
1471
|
+
syncExpenseDetailTableFromStore(textarea);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
function parseExpenseDetailRows(raw) {
|
|
1475
|
+
const lines = String(raw || "")
|
|
1476
|
+
.split(/\r?\n/)
|
|
1477
|
+
.map((line) => line.trim())
|
|
1478
|
+
.filter(Boolean);
|
|
1479
|
+
if (!lines.length) return [{ what: "", amount: "", due: "" }];
|
|
1480
|
+
return lines
|
|
1481
|
+
.map((line) => {
|
|
1482
|
+
const cols = line.split("|").map((part) => part.trim());
|
|
1483
|
+
if (cols.length >= 2) {
|
|
1484
|
+
return {
|
|
1485
|
+
what: cols[0] || "",
|
|
1486
|
+
amount: cols[1] || "",
|
|
1487
|
+
due: cols.slice(2).join(" | ") || ""
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
return { what: line, amount: "", due: "" };
|
|
1491
|
+
})
|
|
1492
|
+
.slice(0, EXPENSE_DETAIL_MAX_ROWS);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function sanitizeExpenseDetailCell(value) {
|
|
1496
|
+
return String(value || "")
|
|
1497
|
+
.replace(/[\r\n]+/g, " ")
|
|
1498
|
+
.replace(/\|/g, "/")
|
|
1499
|
+
.trim();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function serializeExpenseDetailRows(rows) {
|
|
1503
|
+
const compact = (Array.isArray(rows) ? rows : [])
|
|
1504
|
+
.map((row) => ({
|
|
1505
|
+
what: sanitizeExpenseDetailCell(row && row.what),
|
|
1506
|
+
amount: sanitizeExpenseDetailCell(row && row.amount),
|
|
1507
|
+
due: sanitizeExpenseDetailCell(row && row.due)
|
|
1508
|
+
}))
|
|
1509
|
+
.filter((row) => row.what || row.amount || row.due);
|
|
1510
|
+
return compact.map((row) => `${row.what} | ${row.amount} | ${row.due}`.trim()).join("\n");
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function buildExpenseDetailTableHtml(textareaId, rows) {
|
|
1514
|
+
const safeRows = (Array.isArray(rows) && rows.length ? rows : [{ what: "", amount: "", due: "" }])
|
|
1515
|
+
.slice(0, EXPENSE_DETAIL_MAX_ROWS);
|
|
1516
|
+
const rowsHtml = safeRows.map((row) => {
|
|
1517
|
+
const canRemove = safeRows.length > 1;
|
|
1518
|
+
return `<tr>
|
|
1519
|
+
<td><input class="spese-detail-cell-input" data-col="what" type="text" maxlength="120" value="${escapeHtml(row.what || "")}" /></td>
|
|
1520
|
+
<td><input class="spese-detail-cell-input" data-col="amount" type="text" maxlength="80" value="${escapeHtml(row.amount || "")}" /></td>
|
|
1521
|
+
<td><input class="spese-detail-cell-input" data-col="due" type="text" maxlength="80" value="${escapeHtml(row.due || "")}" /></td>
|
|
1522
|
+
<td class="spese-detail-actions-cell"><button type="button" class="spese-detail-row-remove" data-row-remove="1" ${canRemove ? "" : "disabled"} title="${escapeHtml(tr("expenseDetailRemoveRow"))}">-</button></td>
|
|
1523
|
+
</tr>`;
|
|
1524
|
+
}).join("");
|
|
1525
|
+
return `<div class="spese-detail-grid-wrap" data-detail-table="${textareaId}">
|
|
1526
|
+
<table class="spese-detail-grid" aria-label="${escapeHtml(tr("expenseDetailTitle"))}">
|
|
1527
|
+
<thead>
|
|
1528
|
+
<tr>
|
|
1529
|
+
<th>${escapeHtml(tr("expenseDetailColThing"))}</th>
|
|
1530
|
+
<th>${escapeHtml(tr("expenseDetailColAmount"))}</th>
|
|
1531
|
+
<th>${escapeHtml(tr("expenseDetailColDue"))}</th>
|
|
1532
|
+
<th></th>
|
|
1533
|
+
</tr>
|
|
1534
|
+
</thead>
|
|
1535
|
+
<tbody>${rowsHtml}</tbody>
|
|
1536
|
+
</table>
|
|
1537
|
+
<button type="button" class="btn-secondary spese-detail-add-row" data-row-add="${textareaId}">${escapeHtml(tr("expenseDetailAddRow"))}</button>
|
|
1538
|
+
</div>`;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function readExpenseDetailRowsFromTable(tableWrap) {
|
|
1542
|
+
if (!tableWrap) return [];
|
|
1543
|
+
return Array.from(tableWrap.querySelectorAll("tbody tr")).map((trEl) => ({
|
|
1544
|
+
what: String(trEl.querySelector('input[data-col="what"]')?.value || "").trim(),
|
|
1545
|
+
amount: String(trEl.querySelector('input[data-col="amount"]')?.value || "").trim(),
|
|
1546
|
+
due: String(trEl.querySelector('input[data-col="due"]')?.value || "").trim()
|
|
1547
|
+
}));
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
function syncExpenseDetailStoreFromTable(tableWrap) {
|
|
1551
|
+
if (!tableWrap) return;
|
|
1552
|
+
const textareaId = String(tableWrap.getAttribute("data-detail-table") || "");
|
|
1553
|
+
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
1554
|
+
if (!textarea) return;
|
|
1555
|
+
const serialized = serializeExpenseDetailRows(readExpenseDetailRowsFromTable(tableWrap));
|
|
1556
|
+
textarea.value = serialized.slice(0, EXPENSE_DETAIL_MAX_CHARS);
|
|
1557
|
+
updateExpenseDetailCounter(textarea);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function syncExpenseDetailTableFromStore(textarea) {
|
|
1561
|
+
if (!textarea || !textarea.id) return;
|
|
1562
|
+
const host = document.getElementById(`${textarea.id}TableHost`);
|
|
1563
|
+
if (!host) return;
|
|
1564
|
+
const existing = host.querySelector(`[data-detail-table='${textarea.id}']`);
|
|
1565
|
+
const existingSerialized = serializeExpenseDetailRows(existing ? readExpenseDetailRowsFromTable(existing) : []);
|
|
1566
|
+
const storeSerialized = String(textarea.value || "").trim();
|
|
1567
|
+
if (existing && existingSerialized === storeSerialized) return;
|
|
1568
|
+
host.innerHTML = buildExpenseDetailTableHtml(textarea.id, parseExpenseDetailRows(storeSerialized));
|
|
1569
|
+
syncExpenseDetailStoreFromTable(host.querySelector(`[data-detail-table='${textarea.id}']`));
|
|
1460
1570
|
}
|
|
1461
1571
|
|
|
1462
1572
|
function collectExpenseDetailUiMeta(spouseKey, idx) {
|
|
@@ -2938,7 +3048,8 @@ const defaultExpenseItems = [
|
|
|
2938
3048
|
<button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c1d_${idx}" data-detail-wrap="c1dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
|
|
2939
3049
|
</div>
|
|
2940
3050
|
<div class="spese-detail-wrap is-hidden" id="c1dw_${idx}">
|
|
2941
|
-
<
|
|
3051
|
+
<div id="c1d_${idx}TableHost"></div>
|
|
3052
|
+
<textarea id="c1d_${idx}" class="spese-detail-text spese-detail-store" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c1d_${idx}Counter"></textarea>
|
|
2942
3053
|
<div class="spese-detail-counter" id="c1d_${idx}Counter" aria-live="polite"></div>
|
|
2943
3054
|
</div>
|
|
2944
3055
|
<span class="spese-partial" id="p1_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
@@ -2951,7 +3062,8 @@ const defaultExpenseItems = [
|
|
|
2951
3062
|
<button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c2d_${idx}" data-detail-wrap="c2dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
|
|
2952
3063
|
</div>
|
|
2953
3064
|
<div class="spese-detail-wrap is-hidden" id="c2dw_${idx}">
|
|
2954
|
-
<
|
|
3065
|
+
<div id="c2d_${idx}TableHost"></div>
|
|
3066
|
+
<textarea id="c2d_${idx}" class="spese-detail-text spese-detail-store" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c2d_${idx}Counter"></textarea>
|
|
2955
3067
|
<div class="spese-detail-counter" id="c2d_${idx}Counter" aria-live="polite"></div>
|
|
2956
3068
|
</div>
|
|
2957
3069
|
<span class="spese-partial" id="p2_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
@@ -2966,6 +3078,9 @@ const defaultExpenseItems = [
|
|
|
2966
3078
|
rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
|
|
2967
3079
|
updateExpenseDetailTextareaUi(el);
|
|
2968
3080
|
});
|
|
3081
|
+
rowsSpese.querySelectorAll("textarea.spese-detail-store").forEach((el) => {
|
|
3082
|
+
syncExpenseDetailTableFromStore(el);
|
|
3083
|
+
});
|
|
2969
3084
|
refreshExpenseDetailButtonState();
|
|
2970
3085
|
}
|
|
2971
3086
|
|
|
@@ -4918,8 +5033,10 @@ const defaultExpenseItems = [
|
|
|
4918
5033
|
[tr("pdfAmountPerChild"), eur((Math.max(m.assegnoDa1a2, m.assegnoDa2a1)) / m.figli), "warn"]
|
|
4919
5034
|
];
|
|
4920
5035
|
|
|
4921
|
-
|
|
4922
|
-
|
|
5036
|
+
const kpiBenefitLines = getCompensativeBenefitRows(m, c1n(), c2n())
|
|
5037
|
+
.map((row) => `${escapeHtml(row.label)}: ${eur(row.amount)}`);
|
|
5038
|
+
if (kpiBenefitLines.length) {
|
|
5039
|
+
items.push([tr("calcCompBenefitsLabel"), kpiBenefitLines.join("<br />"), "warn", true]);
|
|
4923
5040
|
}
|
|
4924
5041
|
|
|
4925
5042
|
if (m.incomeMode === "cu") {
|
|
@@ -4932,13 +5049,20 @@ const defaultExpenseItems = [
|
|
|
4932
5049
|
);
|
|
4933
5050
|
}
|
|
4934
5051
|
|
|
4935
|
-
items.forEach(([label, value, cls]) => {
|
|
5052
|
+
items.forEach(([label, value, cls, isHtml = false]) => {
|
|
4936
5053
|
const el = document.createElement("div");
|
|
4937
5054
|
el.className = "kpi-item";
|
|
4938
|
-
|
|
5055
|
+
const isBenefitsRow = label === tr("calcCompBenefitsLabel");
|
|
5056
|
+
if (isBenefitsRow) {
|
|
4939
5057
|
el.classList.add("kpi-item--longtext");
|
|
4940
5058
|
}
|
|
4941
|
-
|
|
5059
|
+
const safeLabel = escapeHtml(String(label || ""));
|
|
5060
|
+
const safeValue = isHtml ? String(value || "") : escapeHtml(String(value || ""));
|
|
5061
|
+
if (isBenefitsRow) {
|
|
5062
|
+
el.innerHTML = `<span>${safeLabel}</span><p class="kpi-longtext-value ${cls}">${safeValue}</p>`;
|
|
5063
|
+
} else {
|
|
5064
|
+
el.innerHTML = `<span>${safeLabel}</span><strong class="${cls}">${safeValue}</strong>`;
|
|
5065
|
+
}
|
|
4942
5066
|
kpi.appendChild(el);
|
|
4943
5067
|
});
|
|
4944
5068
|
}
|
|
@@ -6308,12 +6432,49 @@ ${scenarioLab.length ? `
|
|
|
6308
6432
|
detailBtn.classList.toggle("is-open", willOpen);
|
|
6309
6433
|
if (target) {
|
|
6310
6434
|
updateExpenseDetailTextareaUi(target);
|
|
6311
|
-
if (willOpen)
|
|
6435
|
+
if (willOpen) {
|
|
6436
|
+
const firstField = document.querySelector(`#${targetId}TableHost .spese-detail-cell-input`);
|
|
6437
|
+
if (firstField) firstField.focus();
|
|
6438
|
+
}
|
|
6312
6439
|
}
|
|
6313
6440
|
}
|
|
6314
6441
|
return;
|
|
6315
6442
|
}
|
|
6316
6443
|
|
|
6444
|
+
const addRowBtn = e.target && e.target.closest("button[data-row-add]");
|
|
6445
|
+
if (addRowBtn) {
|
|
6446
|
+
const textareaId = String(addRowBtn.getAttribute("data-row-add") || "");
|
|
6447
|
+
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
6448
|
+
if (!textarea) return;
|
|
6449
|
+
const rows = parseExpenseDetailRows(textarea.value);
|
|
6450
|
+
if (rows.length < EXPENSE_DETAIL_MAX_ROWS) {
|
|
6451
|
+
rows.push({ what: "", amount: "", due: "" });
|
|
6452
|
+
textarea.value = serializeExpenseDetailRows(rows);
|
|
6453
|
+
syncExpenseDetailTableFromStore(textarea);
|
|
6454
|
+
refreshExpenseDetailButtonState();
|
|
6455
|
+
}
|
|
6456
|
+
return;
|
|
6457
|
+
}
|
|
6458
|
+
|
|
6459
|
+
const removeRowBtn = e.target && e.target.closest("button[data-row-remove]");
|
|
6460
|
+
if (removeRowBtn) {
|
|
6461
|
+
const tableWrap = removeRowBtn.closest("[data-detail-table]");
|
|
6462
|
+
const rowEl = removeRowBtn.closest("tr");
|
|
6463
|
+
if (!tableWrap || !rowEl) return;
|
|
6464
|
+
const rows = readExpenseDetailRowsFromTable(tableWrap);
|
|
6465
|
+
const rowEls = Array.from(tableWrap.querySelectorAll("tbody tr"));
|
|
6466
|
+
const removeIdx = rowEls.indexOf(rowEl);
|
|
6467
|
+
if (removeIdx >= 0) rows.splice(removeIdx, 1);
|
|
6468
|
+
const nextRows = rows.length ? rows : [{ what: "", amount: "", due: "" }];
|
|
6469
|
+
const textareaId = String(tableWrap.getAttribute("data-detail-table") || "");
|
|
6470
|
+
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
6471
|
+
if (!textarea) return;
|
|
6472
|
+
textarea.value = serializeExpenseDetailRows(nextRows);
|
|
6473
|
+
syncExpenseDetailTableFromStore(textarea);
|
|
6474
|
+
refreshExpenseDetailButtonState();
|
|
6475
|
+
return;
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6317
6478
|
const btn = e.target && e.target.closest("button[data-remove-expense-idx]");
|
|
6318
6479
|
if (!btn) return;
|
|
6319
6480
|
const idx = Number(btn.getAttribute("data-remove-expense-idx"));
|
|
@@ -6324,6 +6485,12 @@ ${scenarioLab.length ? `
|
|
|
6324
6485
|
if (e.target && e.target.matches("textarea.spese-detail-text")) {
|
|
6325
6486
|
updateExpenseDetailTextareaUi(e.target);
|
|
6326
6487
|
refreshExpenseDetailButtonState();
|
|
6488
|
+
return;
|
|
6489
|
+
}
|
|
6490
|
+
if (e.target && e.target.matches(".spese-detail-cell-input")) {
|
|
6491
|
+
const tableWrap = e.target.closest("[data-detail-table]");
|
|
6492
|
+
syncExpenseDetailStoreFromTable(tableWrap);
|
|
6493
|
+
refreshExpenseDetailButtonState();
|
|
6327
6494
|
}
|
|
6328
6495
|
});
|
|
6329
6496
|
|
package/frontend/public/app.js
CHANGED
|
@@ -30,6 +30,7 @@ const defaultExpenseItems = [
|
|
|
30
30
|
const SCENARIO_LABELS = ["A", "B", "C", "D", "E", "F"];
|
|
31
31
|
const EXPENSE_DETAIL_MAX_CHARS = 560;
|
|
32
32
|
const EXPENSE_DETAIL_MAX_LINES = 10;
|
|
33
|
+
const EXPENSE_DETAIL_MAX_ROWS = 8;
|
|
33
34
|
|
|
34
35
|
const QUOTA_MANTENIMENTO_PERC = 35;
|
|
35
36
|
|
|
@@ -250,6 +251,11 @@ const defaultExpenseItems = [
|
|
|
250
251
|
expenseDetailBtn: "Dettaglio",
|
|
251
252
|
expenseDetailTitle: "Apri dettaglio voce spesa",
|
|
252
253
|
expenseDetailPlaceholder: "Scrivi qui il dettaglio di questa cifra (es. mesi, quota, riferimento).",
|
|
254
|
+
expenseDetailColThing: "Cosa",
|
|
255
|
+
expenseDetailColAmount: "Quanto",
|
|
256
|
+
expenseDetailColDue: "Scadenza",
|
|
257
|
+
expenseDetailAddRow: "Aggiungi riga",
|
|
258
|
+
expenseDetailRemoveRow: "Rimuovi riga",
|
|
253
259
|
expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
|
|
254
260
|
expenseRemoveTitle: "Rimuovi voce spesa",
|
|
255
261
|
expenseRemoveBtn: "Rimuovi",
|
|
@@ -612,6 +618,11 @@ const defaultExpenseItems = [
|
|
|
612
618
|
expenseDetailBtn: "Detail",
|
|
613
619
|
expenseDetailTitle: "Open expense detail",
|
|
614
620
|
expenseDetailPlaceholder: "Write details for this amount (e.g. months, share, reference).",
|
|
621
|
+
expenseDetailColThing: "What",
|
|
622
|
+
expenseDetailColAmount: "How much",
|
|
623
|
+
expenseDetailColDue: "Due date",
|
|
624
|
+
expenseDetailAddRow: "Add row",
|
|
625
|
+
expenseDetailRemoveRow: "Remove row",
|
|
615
626
|
expenseDetailCharsRemaining: "Remaining characters: {count}",
|
|
616
627
|
expenseRemoveTitle: "Remove expense item",
|
|
617
628
|
expenseRemoveBtn: "Remove",
|
|
@@ -1457,6 +1468,105 @@ const defaultExpenseItems = [
|
|
|
1457
1468
|
if (!textarea) return;
|
|
1458
1469
|
autoResizeExpenseDetailTextarea(textarea, preferredHeight);
|
|
1459
1470
|
updateExpenseDetailCounter(textarea);
|
|
1471
|
+
syncExpenseDetailTableFromStore(textarea);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
function parseExpenseDetailRows(raw) {
|
|
1475
|
+
const lines = String(raw || "")
|
|
1476
|
+
.split(/\r?\n/)
|
|
1477
|
+
.map((line) => line.trim())
|
|
1478
|
+
.filter(Boolean);
|
|
1479
|
+
if (!lines.length) return [{ what: "", amount: "", due: "" }];
|
|
1480
|
+
return lines
|
|
1481
|
+
.map((line) => {
|
|
1482
|
+
const cols = line.split("|").map((part) => part.trim());
|
|
1483
|
+
if (cols.length >= 2) {
|
|
1484
|
+
return {
|
|
1485
|
+
what: cols[0] || "",
|
|
1486
|
+
amount: cols[1] || "",
|
|
1487
|
+
due: cols.slice(2).join(" | ") || ""
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
return { what: line, amount: "", due: "" };
|
|
1491
|
+
})
|
|
1492
|
+
.slice(0, EXPENSE_DETAIL_MAX_ROWS);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function sanitizeExpenseDetailCell(value) {
|
|
1496
|
+
return String(value || "")
|
|
1497
|
+
.replace(/[\r\n]+/g, " ")
|
|
1498
|
+
.replace(/\|/g, "/")
|
|
1499
|
+
.trim();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function serializeExpenseDetailRows(rows) {
|
|
1503
|
+
const compact = (Array.isArray(rows) ? rows : [])
|
|
1504
|
+
.map((row) => ({
|
|
1505
|
+
what: sanitizeExpenseDetailCell(row && row.what),
|
|
1506
|
+
amount: sanitizeExpenseDetailCell(row && row.amount),
|
|
1507
|
+
due: sanitizeExpenseDetailCell(row && row.due)
|
|
1508
|
+
}))
|
|
1509
|
+
.filter((row) => row.what || row.amount || row.due);
|
|
1510
|
+
return compact.map((row) => `${row.what} | ${row.amount} | ${row.due}`.trim()).join("\n");
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function buildExpenseDetailTableHtml(textareaId, rows) {
|
|
1514
|
+
const safeRows = (Array.isArray(rows) && rows.length ? rows : [{ what: "", amount: "", due: "" }])
|
|
1515
|
+
.slice(0, EXPENSE_DETAIL_MAX_ROWS);
|
|
1516
|
+
const rowsHtml = safeRows.map((row) => {
|
|
1517
|
+
const canRemove = safeRows.length > 1;
|
|
1518
|
+
return `<tr>
|
|
1519
|
+
<td><input class="spese-detail-cell-input" data-col="what" type="text" maxlength="120" value="${escapeHtml(row.what || "")}" /></td>
|
|
1520
|
+
<td><input class="spese-detail-cell-input" data-col="amount" type="text" maxlength="80" value="${escapeHtml(row.amount || "")}" /></td>
|
|
1521
|
+
<td><input class="spese-detail-cell-input" data-col="due" type="text" maxlength="80" value="${escapeHtml(row.due || "")}" /></td>
|
|
1522
|
+
<td class="spese-detail-actions-cell"><button type="button" class="spese-detail-row-remove" data-row-remove="1" ${canRemove ? "" : "disabled"} title="${escapeHtml(tr("expenseDetailRemoveRow"))}">-</button></td>
|
|
1523
|
+
</tr>`;
|
|
1524
|
+
}).join("");
|
|
1525
|
+
return `<div class="spese-detail-grid-wrap" data-detail-table="${textareaId}">
|
|
1526
|
+
<table class="spese-detail-grid" aria-label="${escapeHtml(tr("expenseDetailTitle"))}">
|
|
1527
|
+
<thead>
|
|
1528
|
+
<tr>
|
|
1529
|
+
<th>${escapeHtml(tr("expenseDetailColThing"))}</th>
|
|
1530
|
+
<th>${escapeHtml(tr("expenseDetailColAmount"))}</th>
|
|
1531
|
+
<th>${escapeHtml(tr("expenseDetailColDue"))}</th>
|
|
1532
|
+
<th></th>
|
|
1533
|
+
</tr>
|
|
1534
|
+
</thead>
|
|
1535
|
+
<tbody>${rowsHtml}</tbody>
|
|
1536
|
+
</table>
|
|
1537
|
+
<button type="button" class="btn-secondary spese-detail-add-row" data-row-add="${textareaId}">${escapeHtml(tr("expenseDetailAddRow"))}</button>
|
|
1538
|
+
</div>`;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function readExpenseDetailRowsFromTable(tableWrap) {
|
|
1542
|
+
if (!tableWrap) return [];
|
|
1543
|
+
return Array.from(tableWrap.querySelectorAll("tbody tr")).map((trEl) => ({
|
|
1544
|
+
what: String(trEl.querySelector('input[data-col="what"]')?.value || "").trim(),
|
|
1545
|
+
amount: String(trEl.querySelector('input[data-col="amount"]')?.value || "").trim(),
|
|
1546
|
+
due: String(trEl.querySelector('input[data-col="due"]')?.value || "").trim()
|
|
1547
|
+
}));
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
function syncExpenseDetailStoreFromTable(tableWrap) {
|
|
1551
|
+
if (!tableWrap) return;
|
|
1552
|
+
const textareaId = String(tableWrap.getAttribute("data-detail-table") || "");
|
|
1553
|
+
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
1554
|
+
if (!textarea) return;
|
|
1555
|
+
const serialized = serializeExpenseDetailRows(readExpenseDetailRowsFromTable(tableWrap));
|
|
1556
|
+
textarea.value = serialized.slice(0, EXPENSE_DETAIL_MAX_CHARS);
|
|
1557
|
+
updateExpenseDetailCounter(textarea);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function syncExpenseDetailTableFromStore(textarea) {
|
|
1561
|
+
if (!textarea || !textarea.id) return;
|
|
1562
|
+
const host = document.getElementById(`${textarea.id}TableHost`);
|
|
1563
|
+
if (!host) return;
|
|
1564
|
+
const existing = host.querySelector(`[data-detail-table='${textarea.id}']`);
|
|
1565
|
+
const existingSerialized = serializeExpenseDetailRows(existing ? readExpenseDetailRowsFromTable(existing) : []);
|
|
1566
|
+
const storeSerialized = String(textarea.value || "").trim();
|
|
1567
|
+
if (existing && existingSerialized === storeSerialized) return;
|
|
1568
|
+
host.innerHTML = buildExpenseDetailTableHtml(textarea.id, parseExpenseDetailRows(storeSerialized));
|
|
1569
|
+
syncExpenseDetailStoreFromTable(host.querySelector(`[data-detail-table='${textarea.id}']`));
|
|
1460
1570
|
}
|
|
1461
1571
|
|
|
1462
1572
|
function collectExpenseDetailUiMeta(spouseKey, idx) {
|
|
@@ -2938,7 +3048,8 @@ const defaultExpenseItems = [
|
|
|
2938
3048
|
<button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c1d_${idx}" data-detail-wrap="c1dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
|
|
2939
3049
|
</div>
|
|
2940
3050
|
<div class="spese-detail-wrap is-hidden" id="c1dw_${idx}">
|
|
2941
|
-
<
|
|
3051
|
+
<div id="c1d_${idx}TableHost"></div>
|
|
3052
|
+
<textarea id="c1d_${idx}" class="spese-detail-text spese-detail-store" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c1d_${idx}Counter"></textarea>
|
|
2942
3053
|
<div class="spese-detail-counter" id="c1d_${idx}Counter" aria-live="polite"></div>
|
|
2943
3054
|
</div>
|
|
2944
3055
|
<span class="spese-partial" id="p1_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
@@ -2951,7 +3062,8 @@ const defaultExpenseItems = [
|
|
|
2951
3062
|
<button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c2d_${idx}" data-detail-wrap="c2dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
|
|
2952
3063
|
</div>
|
|
2953
3064
|
<div class="spese-detail-wrap is-hidden" id="c2dw_${idx}">
|
|
2954
|
-
<
|
|
3065
|
+
<div id="c2d_${idx}TableHost"></div>
|
|
3066
|
+
<textarea id="c2d_${idx}" class="spese-detail-text spese-detail-store" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c2d_${idx}Counter"></textarea>
|
|
2955
3067
|
<div class="spese-detail-counter" id="c2d_${idx}Counter" aria-live="polite"></div>
|
|
2956
3068
|
</div>
|
|
2957
3069
|
<span class="spese-partial" id="p2_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
@@ -2966,6 +3078,9 @@ const defaultExpenseItems = [
|
|
|
2966
3078
|
rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
|
|
2967
3079
|
updateExpenseDetailTextareaUi(el);
|
|
2968
3080
|
});
|
|
3081
|
+
rowsSpese.querySelectorAll("textarea.spese-detail-store").forEach((el) => {
|
|
3082
|
+
syncExpenseDetailTableFromStore(el);
|
|
3083
|
+
});
|
|
2969
3084
|
refreshExpenseDetailButtonState();
|
|
2970
3085
|
}
|
|
2971
3086
|
|
|
@@ -4918,8 +5033,10 @@ const defaultExpenseItems = [
|
|
|
4918
5033
|
[tr("pdfAmountPerChild"), eur((Math.max(m.assegnoDa1a2, m.assegnoDa2a1)) / m.figli), "warn"]
|
|
4919
5034
|
];
|
|
4920
5035
|
|
|
4921
|
-
|
|
4922
|
-
|
|
5036
|
+
const kpiBenefitLines = getCompensativeBenefitRows(m, c1n(), c2n())
|
|
5037
|
+
.map((row) => `${escapeHtml(row.label)}: ${eur(row.amount)}`);
|
|
5038
|
+
if (kpiBenefitLines.length) {
|
|
5039
|
+
items.push([tr("calcCompBenefitsLabel"), kpiBenefitLines.join("<br />"), "warn", true]);
|
|
4923
5040
|
}
|
|
4924
5041
|
|
|
4925
5042
|
if (m.incomeMode === "cu") {
|
|
@@ -4932,13 +5049,20 @@ const defaultExpenseItems = [
|
|
|
4932
5049
|
);
|
|
4933
5050
|
}
|
|
4934
5051
|
|
|
4935
|
-
items.forEach(([label, value, cls]) => {
|
|
5052
|
+
items.forEach(([label, value, cls, isHtml = false]) => {
|
|
4936
5053
|
const el = document.createElement("div");
|
|
4937
5054
|
el.className = "kpi-item";
|
|
4938
|
-
|
|
5055
|
+
const isBenefitsRow = label === tr("calcCompBenefitsLabel");
|
|
5056
|
+
if (isBenefitsRow) {
|
|
4939
5057
|
el.classList.add("kpi-item--longtext");
|
|
4940
5058
|
}
|
|
4941
|
-
|
|
5059
|
+
const safeLabel = escapeHtml(String(label || ""));
|
|
5060
|
+
const safeValue = isHtml ? String(value || "") : escapeHtml(String(value || ""));
|
|
5061
|
+
if (isBenefitsRow) {
|
|
5062
|
+
el.innerHTML = `<span>${safeLabel}</span><p class="kpi-longtext-value ${cls}">${safeValue}</p>`;
|
|
5063
|
+
} else {
|
|
5064
|
+
el.innerHTML = `<span>${safeLabel}</span><strong class="${cls}">${safeValue}</strong>`;
|
|
5065
|
+
}
|
|
4942
5066
|
kpi.appendChild(el);
|
|
4943
5067
|
});
|
|
4944
5068
|
}
|
|
@@ -6308,12 +6432,49 @@ ${scenarioLab.length ? `
|
|
|
6308
6432
|
detailBtn.classList.toggle("is-open", willOpen);
|
|
6309
6433
|
if (target) {
|
|
6310
6434
|
updateExpenseDetailTextareaUi(target);
|
|
6311
|
-
if (willOpen)
|
|
6435
|
+
if (willOpen) {
|
|
6436
|
+
const firstField = document.querySelector(`#${targetId}TableHost .spese-detail-cell-input`);
|
|
6437
|
+
if (firstField) firstField.focus();
|
|
6438
|
+
}
|
|
6312
6439
|
}
|
|
6313
6440
|
}
|
|
6314
6441
|
return;
|
|
6315
6442
|
}
|
|
6316
6443
|
|
|
6444
|
+
const addRowBtn = e.target && e.target.closest("button[data-row-add]");
|
|
6445
|
+
if (addRowBtn) {
|
|
6446
|
+
const textareaId = String(addRowBtn.getAttribute("data-row-add") || "");
|
|
6447
|
+
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
6448
|
+
if (!textarea) return;
|
|
6449
|
+
const rows = parseExpenseDetailRows(textarea.value);
|
|
6450
|
+
if (rows.length < EXPENSE_DETAIL_MAX_ROWS) {
|
|
6451
|
+
rows.push({ what: "", amount: "", due: "" });
|
|
6452
|
+
textarea.value = serializeExpenseDetailRows(rows);
|
|
6453
|
+
syncExpenseDetailTableFromStore(textarea);
|
|
6454
|
+
refreshExpenseDetailButtonState();
|
|
6455
|
+
}
|
|
6456
|
+
return;
|
|
6457
|
+
}
|
|
6458
|
+
|
|
6459
|
+
const removeRowBtn = e.target && e.target.closest("button[data-row-remove]");
|
|
6460
|
+
if (removeRowBtn) {
|
|
6461
|
+
const tableWrap = removeRowBtn.closest("[data-detail-table]");
|
|
6462
|
+
const rowEl = removeRowBtn.closest("tr");
|
|
6463
|
+
if (!tableWrap || !rowEl) return;
|
|
6464
|
+
const rows = readExpenseDetailRowsFromTable(tableWrap);
|
|
6465
|
+
const rowEls = Array.from(tableWrap.querySelectorAll("tbody tr"));
|
|
6466
|
+
const removeIdx = rowEls.indexOf(rowEl);
|
|
6467
|
+
if (removeIdx >= 0) rows.splice(removeIdx, 1);
|
|
6468
|
+
const nextRows = rows.length ? rows : [{ what: "", amount: "", due: "" }];
|
|
6469
|
+
const textareaId = String(tableWrap.getAttribute("data-detail-table") || "");
|
|
6470
|
+
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
6471
|
+
if (!textarea) return;
|
|
6472
|
+
textarea.value = serializeExpenseDetailRows(nextRows);
|
|
6473
|
+
syncExpenseDetailTableFromStore(textarea);
|
|
6474
|
+
refreshExpenseDetailButtonState();
|
|
6475
|
+
return;
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6317
6478
|
const btn = e.target && e.target.closest("button[data-remove-expense-idx]");
|
|
6318
6479
|
if (!btn) return;
|
|
6319
6480
|
const idx = Number(btn.getAttribute("data-remove-expense-idx"));
|
|
@@ -6324,6 +6485,12 @@ ${scenarioLab.length ? `
|
|
|
6324
6485
|
if (e.target && e.target.matches("textarea.spese-detail-text")) {
|
|
6325
6486
|
updateExpenseDetailTextareaUi(e.target);
|
|
6326
6487
|
refreshExpenseDetailButtonState();
|
|
6488
|
+
return;
|
|
6489
|
+
}
|
|
6490
|
+
if (e.target && e.target.matches(".spese-detail-cell-input")) {
|
|
6491
|
+
const tableWrap = e.target.closest("[data-detail-table]");
|
|
6492
|
+
syncExpenseDetailStoreFromTable(tableWrap);
|
|
6493
|
+
refreshExpenseDetailButtonState();
|
|
6327
6494
|
}
|
|
6328
6495
|
});
|
|
6329
6496
|
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
]
|
|
89
89
|
}
|
|
90
90
|
</script>
|
|
91
|
-
<link rel="stylesheet" href="styles.css?v=2.4.
|
|
91
|
+
<link rel="stylesheet" href="styles.css?v=2.4.8" />
|
|
92
92
|
</head>
|
|
93
93
|
<body>
|
|
94
94
|
<div class="wrap">
|
|
@@ -630,7 +630,7 @@
|
|
|
630
630
|
<script src="supabase.min.js"></script>
|
|
631
631
|
<script src="fabric.min.js"></script>
|
|
632
632
|
<script src="html2pdf.bundle.min.js"></script>
|
|
633
|
-
<script src="app.js?v=2.4.
|
|
633
|
+
<script src="app.js?v=2.4.9"></script>
|
|
634
634
|
</body>
|
|
635
635
|
</html>
|
|
636
636
|
|
|
@@ -1000,7 +1000,7 @@
|
|
|
1000
1000
|
linear-gradient(180deg, #fafffe, #f2f8f7);
|
|
1001
1001
|
padding: 14px;
|
|
1002
1002
|
display: grid;
|
|
1003
|
-
grid-template-columns: minmax(
|
|
1003
|
+
grid-template-columns: minmax(170px, 0.9fr) minmax(380px, 2.9fr) minmax(170px, 0.9fr);
|
|
1004
1004
|
gap: 10px;
|
|
1005
1005
|
align-items: center;
|
|
1006
1006
|
box-shadow: 0 10px 18px rgba(27, 141, 127, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.65);
|
|
@@ -1039,7 +1039,9 @@
|
|
|
1039
1039
|
min-height: 74px;
|
|
1040
1040
|
display: grid;
|
|
1041
1041
|
align-content: center;
|
|
1042
|
+
justify-items: center;
|
|
1042
1043
|
gap: 4px;
|
|
1044
|
+
text-align: center;
|
|
1043
1045
|
box-shadow: 0 2px 6px rgba(27, 141, 127, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.7);
|
|
1044
1046
|
transition: all 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
1045
1047
|
position: relative;
|
|
@@ -1081,7 +1083,7 @@
|
|
|
1081
1083
|
|
|
1082
1084
|
.mortgage-split-side-right {
|
|
1083
1085
|
box-shadow: 0 2px 6px rgba(27, 141, 127, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.7), inset -3px 0 0 #d89a35;
|
|
1084
|
-
text-align:
|
|
1086
|
+
text-align: center;
|
|
1085
1087
|
background: linear-gradient(135deg, #fef9f1 0%, #fdf5e8 100%);
|
|
1086
1088
|
}
|
|
1087
1089
|
|
|
@@ -1097,6 +1099,7 @@
|
|
|
1097
1099
|
overflow-wrap: anywhere;
|
|
1098
1100
|
letter-spacing: 0.2px;
|
|
1099
1101
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
|
|
1102
|
+
text-align: center;
|
|
1100
1103
|
}
|
|
1101
1104
|
|
|
1102
1105
|
.mortgage-split-amount {
|
|
@@ -1109,6 +1112,7 @@
|
|
|
1109
1112
|
-webkit-text-fill-color: transparent;
|
|
1110
1113
|
background-clip: text;
|
|
1111
1114
|
filter: drop-shadow(0 1px 2px rgba(27, 141, 127, 0.2));
|
|
1115
|
+
text-align: center;
|
|
1112
1116
|
}
|
|
1113
1117
|
|
|
1114
1118
|
.mortgage-split-side-right .mortgage-split-amount {
|
|
@@ -1120,7 +1124,7 @@
|
|
|
1120
1124
|
|
|
1121
1125
|
.mortgage-split-range-wrap {
|
|
1122
1126
|
position: relative;
|
|
1123
|
-
padding:
|
|
1127
|
+
padding: 30px 0 8px;
|
|
1124
1128
|
--split-left: 50%;
|
|
1125
1129
|
}
|
|
1126
1130
|
|
|
@@ -1231,7 +1235,7 @@
|
|
|
1231
1235
|
|
|
1232
1236
|
.mortgage-split-center {
|
|
1233
1237
|
position: absolute;
|
|
1234
|
-
top:
|
|
1238
|
+
top: -14px;
|
|
1235
1239
|
left: var(--split-left);
|
|
1236
1240
|
transform: translateX(-50%);
|
|
1237
1241
|
font-size: 0.92rem;
|
|
@@ -1246,6 +1250,8 @@
|
|
|
1246
1250
|
transition: all 0.2s ease;
|
|
1247
1251
|
letter-spacing: 0.2px;
|
|
1248
1252
|
animation: split-center-breathe 2.6s ease-in-out infinite;
|
|
1253
|
+
z-index: 3;
|
|
1254
|
+
pointer-events: none;
|
|
1249
1255
|
}
|
|
1250
1256
|
|
|
1251
1257
|
@keyframes first-home-aurora {
|
|
@@ -1517,6 +1523,75 @@
|
|
|
1517
1523
|
color: #204644;
|
|
1518
1524
|
}
|
|
1519
1525
|
|
|
1526
|
+
.spese-detail-store {
|
|
1527
|
+
display: none;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
.spese-detail-grid-wrap {
|
|
1531
|
+
border: 1px solid #c8dad4;
|
|
1532
|
+
border-radius: 8px;
|
|
1533
|
+
background: #f9fcfb;
|
|
1534
|
+
padding: 6px;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
.spese-detail-grid {
|
|
1538
|
+
width: 100%;
|
|
1539
|
+
border-collapse: collapse;
|
|
1540
|
+
table-layout: fixed;
|
|
1541
|
+
font-size: 0.72rem;
|
|
1542
|
+
color: #204644;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.spese-detail-grid th {
|
|
1546
|
+
text-align: left;
|
|
1547
|
+
font-size: 0.68rem;
|
|
1548
|
+
font-weight: 800;
|
|
1549
|
+
color: #3b6964;
|
|
1550
|
+
padding: 2px 4px 5px;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
.spese-detail-grid td {
|
|
1554
|
+
padding: 3px;
|
|
1555
|
+
vertical-align: middle;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.spese-detail-cell-input {
|
|
1559
|
+
width: 100%;
|
|
1560
|
+
border: 1px solid #bfd5d0;
|
|
1561
|
+
border-radius: 6px;
|
|
1562
|
+
background: #ffffff;
|
|
1563
|
+
color: #214b47;
|
|
1564
|
+
font-size: 0.72rem;
|
|
1565
|
+
padding: 4px 6px;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.spese-detail-actions-cell {
|
|
1569
|
+
width: 30px;
|
|
1570
|
+
text-align: center;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
.spese-detail-row-remove {
|
|
1574
|
+
width: 24px;
|
|
1575
|
+
height: 24px;
|
|
1576
|
+
border: 1px solid #c4d7d2;
|
|
1577
|
+
border-radius: 6px;
|
|
1578
|
+
background: #ffffff;
|
|
1579
|
+
color: #355b57;
|
|
1580
|
+
font-weight: 800;
|
|
1581
|
+
cursor: pointer;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.spese-detail-row-remove:disabled {
|
|
1585
|
+
opacity: 0.45;
|
|
1586
|
+
cursor: not-allowed;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
.spese-detail-add-row {
|
|
1590
|
+
margin-top: 6px;
|
|
1591
|
+
padding: 3px 8px;
|
|
1592
|
+
font-size: 0.67rem;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1520
1595
|
.spese-detail-counter {
|
|
1521
1596
|
margin-top: 3px;
|
|
1522
1597
|
font-size: 0.66rem;
|
|
@@ -2552,19 +2627,33 @@
|
|
|
2552
2627
|
}
|
|
2553
2628
|
|
|
2554
2629
|
.kpi-item--longtext {
|
|
2630
|
+
flex-direction: column;
|
|
2555
2631
|
align-items: flex-start;
|
|
2632
|
+
gap: 5px;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
.kpi-item--longtext span {
|
|
2636
|
+
width: 100%;
|
|
2637
|
+
flex: none;
|
|
2556
2638
|
}
|
|
2557
2639
|
|
|
2558
|
-
.kpi-
|
|
2640
|
+
.kpi-longtext-value {
|
|
2641
|
+
margin: 0;
|
|
2642
|
+
width: 100%;
|
|
2559
2643
|
white-space: normal;
|
|
2560
2644
|
text-align: left;
|
|
2561
|
-
font-size: 0.
|
|
2562
|
-
|
|
2645
|
+
font-size: 0.95rem;
|
|
2646
|
+
font-weight: 700;
|
|
2647
|
+
line-height: 1.42;
|
|
2563
2648
|
overflow-wrap: anywhere;
|
|
2564
2649
|
word-break: break-word;
|
|
2565
|
-
|
|
2650
|
+
color: #9a5a00;
|
|
2566
2651
|
}
|
|
2567
2652
|
|
|
2653
|
+
.kpi-longtext-value.ok { color: #0c6c52; }
|
|
2654
|
+
.kpi-longtext-value.warn { color: #9a5a00; }
|
|
2655
|
+
.kpi-longtext-value.bad { color: #b53c2f; }
|
|
2656
|
+
|
|
2568
2657
|
.spieg-details {
|
|
2569
2658
|
border: 1px solid #bed9d4;
|
|
2570
2659
|
border-radius: 12px;
|