Files
billai/web/src/lib/components/analysis/TopExpenses.svelte

92 lines
3.2 KiB
Svelte

<script lang="ts">
import * as Card from '$lib/components/ui/card';
import Flame from '@lucide/svelte/icons/flame';
import { type UIBill } from '$lib/models/bill';
import BillDetailDrawer from './BillDetailDrawer.svelte';
interface Props {
records: UIBill[];
categories: string[]; // 可用的分类列表
onUpdate?: (record: UIBill) => void;
}
let { records, categories, onUpdate }: Props = $props();
let dialogOpen = $state(false);
let selectedRecord = $state<UIBill | null>(null);
let selectedRank = $state(0);
function openDetail(record: UIBill, rank: number) {
selectedRecord = record;
selectedRank = rank;
dialogOpen = true;
}
function handleRecordUpdated(updated: UIBill, original: UIBill) {
const idx = records.findIndex(r => r === original);
if (idx !== -1) records[idx] = updated;
selectedRecord = updated;
onUpdate?.(updated);
}
</script>
<Card.Root class="transition-all duration-200 hover:shadow-lg hover:-translate-y-1">
<Card.Header>
<Card.Title class="flex items-center gap-2">
<Flame class="h-5 w-5 text-orange-500" />
Top 10 单笔支出
</Card.Title>
<Card.Description>最大的单笔支出记录(点击查看详情)</Card.Description>
</Card.Header>
<Card.Content>
<div class="space-y-3">
{#each records as record, i}
<button
class="w-full flex items-center gap-4 p-3 rounded-lg bg-muted/50 transition-all duration-150 hover:bg-muted hover:scale-[1.02] hover:shadow-sm cursor-pointer text-left outline-none focus:outline-none"
onclick={() => openDetail(record, i + 1)}
>
<div class="flex h-8 w-8 items-center justify-center rounded-full font-bold text-sm {
i === 0 ? 'bg-gradient-to-br from-yellow-400 to-amber-500 text-white shadow-md' :
i === 1 ? 'bg-gradient-to-br from-slate-300 to-slate-400 text-white shadow-md' :
i === 2 ? 'bg-gradient-to-br from-orange-400 to-amber-600 text-white shadow-md' :
'bg-primary/10 text-primary'
}">
{i + 1}
</div>
<div class="flex-1 min-w-0">
<p class="font-medium truncate">{record.merchant}</p>
<p class="text-sm text-muted-foreground truncate">
{record.description || record.category}
</p>
</div>
<div class="font-mono font-bold text-red-600 dark:text-red-400">
¥{record.amount.toFixed(2)}
</div>
</button>
{/each}
</div>
</Card.Content>
</Card.Root>
<BillDetailDrawer
bind:open={dialogOpen}
bind:record={selectedRecord}
{categories}
title="账单详情"
viewDescription="查看这笔支出的完整信息"
editDescription="修改这笔支出的信息"
onUpdate={handleRecordUpdated}
>
{#snippet titleExtra({ isEditing })}
{#if selectedRank <= 3 && !isEditing}
<span class="ml-2 px-2 py-0.5 text-xs rounded-full {
selectedRank === 1 ? 'bg-gradient-to-r from-yellow-400 to-amber-500 text-white' :
selectedRank === 2 ? 'bg-gradient-to-r from-slate-300 to-slate-400 text-white' :
'bg-gradient-to-r from-orange-400 to-amber-600 text-white'
}">
Top {selectedRank}
</span>
{/if}
{/snippet}
</BillDetailDrawer>