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]'}`}
</button> >
<span className="text-sm font-bold text-white">{currentYear}</span> {de ? 'Alle Jahre' : 'All Years'}
<button onClick={() => setYearOffset(Math.min(4, yearOffset + 1))} disabled={yearOffset === 4}
className="p-1 text-white/40 hover:text-white/70 disabled:opacity-20">
<ChevronRight className="w-4 h-4" />
</button> </button>
{[2026, 2027, 2028, 2029, 2030].map((y, idx) => (
<button
key={y}
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>
))}
</div> </div>
)} )}
@@ -558,21 +564,30 @@ 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>
<th className="text-right py-1.5 px-2 text-white/60 font-medium min-w-[70px]"> {yearOffset === -1 ? (
{currentYear} // All years view
</th> [2026, 2027, 2028, 2029, 2030].map(y => (
{MONTH_LABELS.map((label, idx) => ( <th key={y} className="text-right py-1.5 px-3 text-white/60 font-medium min-w-[80px]">{y}</th>
<th key={idx} className="text-right py-1.5 px-1.5 text-white/50 font-normal min-w-[55px]"> ))
{label} ) : (
</th> <>
))} <th className="text-right py-1.5 px-2 text-white/60 font-medium min-w-[70px]">
{currentYear}
</th>
{MONTH_LABELS.map((label, idx) => (
<th key={idx} className="text-right py-1.5 px-1.5 text-white/50 font-normal min-w-[55px]">
{label}
</th>
))}
</>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -668,39 +683,59 @@ 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>
<td className={`text-right py-1 px-2 font-medium ${annual < 0 ? 'text-red-400' : isSumRow ? 'text-white/80' : 'text-white/50'}`}> {yearOffset === -1 ? (
{formatCell(annual)} // All years view: show annual values per year
</td> [2026, 2027, 2028, 2029, 2030].map(y => {
{Array.from({ length: 12 }, (_, idx) => { const yStart = (y - 2026) * 12 + 1
const mKey = `m${monthStart + idx}` const yEnd = yStart + 11
const v = values[mKey] || 0 let yVal = 0
if (isUnitPrice) {
return ( yVal = values[`m${yEnd}`] || 0
<td } else if (isBalanceRow) {
key={idx} yVal = values[`m${yEnd}`] || 0
className={`text-right py-1 px-1.5 ${ } else {
v < 0 ? 'text-red-400/70' : v > 0 ? (isSumRow ? 'text-white/70' : 'text-white/50') : 'text-white/15' for (let m = yStart; m <= yEnd; m++) yVal += values[`m${m}`] || 0
} ${isEditable ? 'cursor-pointer hover:bg-indigo-500/10' : ''}`} }
onDoubleClick={() => { return (
if (!isEditable) 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' : ''}`}>
const input = prompt(`${label}${MONTH_LABELS[idx]} ${currentYear}`, String(v)) {formatCell(Math.round(yVal))}
if (input !== null) handleCellEdit(row.id, mKey, input) </td>
}} )
> })
{formatCell(v)} ) : (
<>
<td className={`text-right py-1 px-2 font-medium ${annual < 0 ? 'text-red-400' : isSumRow ? 'text-white/80' : 'text-white/50'}`}>
{formatCell(annual)}
</td> </td>
) {Array.from({ length: 12 }, (_, idx) => {
})} const mKey = `m${monthStart + idx}`
const v = values[mKey] || 0
return (
<td
key={idx}
className={`text-right py-1 px-1.5 ${
v < 0 ? 'text-red-400/70' : v > 0 ? (isSumRow ? 'text-white/70' : 'text-white/50') : 'text-white/15'
} ${isEditable ? 'cursor-pointer hover:bg-indigo-500/10' : ''}`}
onDoubleClick={() => {
if (!isEditable) return
const input = prompt(`${label}${MONTH_LABELS[idx]} ${currentYear}`, String(v))
if (input !== null) handleCellEdit(row.id, mKey, input)
}}
>
{formatCell(v)}
</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}`