Files
kakeibo/frontend/src/features/ledger/week-summary.tsx
T

71 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { TriangleAlert } from 'lucide-react'
import { budgetLevel } from '@/lib/budget-level'
import { formatMoney, formatPercent } from '@/lib/format'
import { group } from '@/lib/groups'
import { t } from '@/lib/strings'
import { cn } from '@/lib/utils'
import type { GroupBudget } from '@/types/GroupBudget'
const s = t.ledger
/**
* Grup bazlı bütçe / harcanan / kalan (+ ilerleme çubuğu): seçili hafta ya da `'month'` → tüm dönem.
* Defter (haftalık) ve Panel (haftalık/aylık) kullanır.
*/
export function WeekSummary({ budget, index }: { budget: GroupBudget[]; index: number | 'month' }) {
return (
<div className="@container">
<ul className="grid gap-3 @lg:grid-cols-2">
{budget.map((b) => {
const g = group(b.group)
const w = index === 'month' ? null : b.weeks[index - 1]
const planned = index === 'month' ? b.monthly : (w?.amount ?? 0)
const spent = index === 'month' ? b.spent : (w?.spent ?? 0)
const left = planned - spent
const level = budgetLevel(spent, planned)
const over = left < 0
const ratio = planned > 0 ? Math.min(spent / planned, 1) : spent > 0 ? 1 : 0
return (
<li key={b.group} className="rounded-xl border p-3">
<div className="flex flex-wrap items-center justify-between gap-x-2">
<span className={cn('font-medium whitespace-nowrap', g.className.text)}>
<span aria-hidden>{g.emoji}</span> {g.label}
</span>
<span className={cn('money text-sm font-semibold', over && 'text-expense')}>
{over ? `${s.over} ${formatMoney(-left)}` : `${s.left} ${formatMoney(left)}`}
</span>
</div>
<div
className="my-2 h-2 overflow-hidden rounded-full bg-muted"
role="progressbar"
aria-label={`${g.label}: ${s.spent}`}
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(ratio * 100)}
>
<div
className={cn(
'h-full rounded-full',
over ? 'bg-expense' : level === 'near' ? 'bg-warning' : g.className.fill,
)}
style={{ width: `${ratio * 100}%` }}
/>
</div>
{level === 'near' && (
<p className="mb-1 flex items-center gap-1 text-xs font-medium text-warning">
<TriangleAlert className="size-3.5 shrink-0" aria-hidden />
{s.near(formatPercent(spent / planned, 0))}
</p>
)}
<p className="text-xs text-muted-foreground">
{s.spent} <span className="money">{formatMoney(spent)}</span> / {s.budget}{' '}
<span className="money">{formatMoney(planned)}</span>
</p>
</li>
)
})}
</ul>
</div>
)
}