57272a4c7f
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
3.0 KiB
TypeScript
71 lines
3.0 KiB
TypeScript
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>
|
||
)
|
||
}
|