feat(pitch-deck): collapsible year view in Finanzplan + remove section labels
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 31s

- Year navigation: "Alle Jahre" shows 5 annual columns, individual years show 12 months
- Default starts at single year view
- Annual view: flow rows show yearly sum, balance rows show Dec value
- Removed [section] labels from row display
- Footer sum only shown in monthly view (not annual)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-20 10:31:47 +02:00
parent ec7326cfe1
commit 66fb265f22

View File

@@ -500,18 +500,24 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
</div> </div>
)} )}
{/* Year Navigation — not for GuV, KPIs, Charts (annual views) */} {/* Year Navigation — not for GuV, KPIs, Charts */}
{!['guv', 'kpis', 'charts'].includes(activeSheet) && ( {!['guv', 'kpis', 'charts'].includes(activeSheet) && (
<div className="flex items-center justify-center gap-4 mb-2"> <div className="flex items-center justify-center gap-1 mb-2">
<button onClick={() => setYearOffset(Math.max(0, yearOffset - 1))} disabled={yearOffset === 0} <button
className="p-1 text-white/40 hover:text-white/70 disabled:opacity-20"> onClick={() => setYearOffset(-1)}
<ChevronLeft className="w-4 h-4" /> className={`px-3 py-1 text-[10px] font-medium rounded-lg transition-colors ${yearOffset === -1 ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' : 'text-white/40 hover:text-white/70 hover:bg-white/[0.05]'}`}
>
{de ? 'Alle Jahre' : 'All Years'}
</button> </button>
<span className="text-sm font-bold text-white">{currentYear}</span> {[2026, 2027, 2028, 2029, 2030].map((y, idx) => (
<button onClick={() => setYearOffset(Math.min(4, yearOffset + 1))} disabled={yearOffset === 4} <button
className="p-1 text-white/40 hover:text-white/70 disabled:opacity-20"> key={y}
<ChevronRight className="w-4 h-4" /> onClick={() => setYearOffset(idx)}
className={`px-3 py-1 text-[10px] font-medium rounded-lg transition-colors ${yearOffset === idx ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' : 'text-white/40 hover:text-white/70 hover:bg-white/[0.05]'}`}
>
{y}
</button> </button>
))}
</div> </div>
)} )}
@@ -558,13 +564,20 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
</tbody> </tbody>
</table> </table>
) : ( ) : (
/* === Monthly Grid (all other sheets) === */ /* === Monthly/Annual Grid (all other sheets) === */
<table className="w-full text-[10px]"> <table className="w-full text-[10px]">
<thead> <thead>
<tr className="border-b border-white/10"> <tr className="border-b border-white/10">
<th className="text-left py-1.5 px-2 text-white/60 font-medium sticky left-0 bg-slate-900/90 backdrop-blur min-w-[160px]"> <th className="text-left py-1.5 px-2 text-white/60 font-medium sticky left-0 bg-slate-900/90 backdrop-blur min-w-[160px]">
{de ? 'Position' : 'Item'} {de ? 'Position' : 'Item'}
</th> </th>
{yearOffset === -1 ? (
// All years view
[2026, 2027, 2028, 2029, 2030].map(y => (
<th key={y} className="text-right py-1.5 px-3 text-white/60 font-medium min-w-[80px]">{y}</th>
))
) : (
<>
<th className="text-right py-1.5 px-2 text-white/60 font-medium min-w-[70px]"> <th className="text-right py-1.5 px-2 text-white/60 font-medium min-w-[70px]">
{currentYear} {currentYear}
</th> </th>
@@ -573,6 +586,8 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
{label} {label}
</th> </th>
))} ))}
</>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -668,16 +683,35 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
{isEditable && <span className="w-1 h-1 rounded-full bg-indigo-400 flex-shrink-0" />} {isEditable && <span className="w-1 h-1 rounded-full bg-indigo-400 flex-shrink-0" />}
<span className="truncate"><LabelWithTooltip label={label} /></span> <span className="truncate"><LabelWithTooltip label={label} /></span>
{row.position && <span className="text-white/50 ml-1">({row.position})</span>} {row.position && <span className="text-white/50 ml-1">({row.position})</span>}
{row.section && <span className="text-white/50 ml-1">[{row.section}]</span>}
</div> </div>
</td> </td>
{yearOffset === -1 ? (
// All years view: show annual values per year
[2026, 2027, 2028, 2029, 2030].map(y => {
const yStart = (y - 2026) * 12 + 1
const yEnd = yStart + 11
let yVal = 0
if (isUnitPrice) {
yVal = values[`m${yEnd}`] || 0
} else if (isBalanceRow) {
yVal = values[`m${yEnd}`] || 0
} else {
for (let m = yStart; m <= yEnd; m++) yVal += values[`m${m}`] || 0
}
return (
<td key={y} className={`text-right py-1 px-3 ${yVal < 0 ? 'text-red-400' : yVal > 0 ? (isSumRow ? 'text-white/80' : 'text-white/50') : 'text-white/15'} ${isSumRow ? 'font-bold' : ''}`}>
{formatCell(Math.round(yVal))}
</td>
)
})
) : (
<>
<td className={`text-right py-1 px-2 font-medium ${annual < 0 ? 'text-red-400' : isSumRow ? 'text-white/80' : 'text-white/50'}`}> <td className={`text-right py-1 px-2 font-medium ${annual < 0 ? 'text-red-400' : isSumRow ? 'text-white/80' : 'text-white/50'}`}>
{formatCell(annual)} {formatCell(annual)}
</td> </td>
{Array.from({ length: 12 }, (_, idx) => { {Array.from({ length: 12 }, (_, idx) => {
const mKey = `m${monthStart + idx}` const mKey = `m${monthStart + idx}`
const v = values[mKey] || 0 const v = values[mKey] || 0
return ( return (
<td <td
key={idx} key={idx}
@@ -694,13 +728,14 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
</td> </td>
) )
})} })}
</>
)}
</tr> </tr>
) )
})} })}
</tbody> </tbody>
{/* Summenzeile für relevante Sheets */} {/* Summenzeile für relevante Sheets */}
{['personalkosten', 'betriebliche', 'investitionen', 'sonst_ertraege'].includes(activeSheet) && rows.length > 0 && (() => { {yearOffset !== -1 && ['personalkosten', 'betriebliche', 'investitionen'].includes(activeSheet) && rows.length > 0 && (() => {
// Berechne Summe über alle Zeilen die keine Summenzeilen sind
const sumValues: Record<string, number> = {} const sumValues: Record<string, number> = {}
let sumAnnual = 0 let sumAnnual = 0
const nonSumRows = rows.filter(r => { const nonSumRows = rows.filter(r => {
@@ -724,10 +759,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
{de ? 'SUMME' : 'TOTAL'} {de ? 'SUMME' : 'TOTAL'}
</td> </td>
<td className={`text-right py-1.5 px-2 font-bold text-xs ${sumAnnual < 0 ? 'text-red-400' : 'text-white/80'}`}> <td className={`text-right py-1.5 px-2 font-bold text-xs ${sumAnnual < 0 ? 'text-red-400' : 'text-white/80'}`}>
{['kunden', 'kunden_summary'].includes(activeSheet) {formatCell(sumAnnual)}
? formatCell(sumValues[`m${monthEnd}`] || 0)
: formatCell(sumAnnual)
}
</td> </td>
{Array.from({ length: 12 }, (_, idx) => { {Array.from({ length: 12 }, (_, idx) => {
const mKey = `m${monthStart + idx}` const mKey = `m${monthStart + idx}`