Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions src/features/budget-tracker/components/budget-tracker-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,56 @@ export function BudgetTrackerList() {
name: string
} | null>(null)

// Selected items for filtering summary calculations
const [selectedItemIds, setSelectedItemIds] = useState<Set<string>>(new Set())

if (isLoading) {
return <div className="p-4">Loading budget tracker...</div>
}

const currentBalance = balance ? parseFloat(balance.amount) : 0

// Calculate total amount and total paid based on stored values
const totalAmount = items.reduce((sum, item) => sum + parseFloat(item.amount), 0)
const totalPaid = items.reduce((sum, item) => sum + parseFloat(item.amountPaid), 0)
// Filter items based on selection (if any items are selected, only show those)
const filteredItems = selectedItemIds.size > 0 ? items.filter((item) => selectedItemIds.has(item.id)) : items

// Calculate total amount and total paid using effective amounts (includes sub-items)
const totalAmount = filteredItems.reduce((sum, item) => {
const itemWithEffect = item as typeof item & { _effectiveAmount?: number }
return sum + (itemWithEffect._effectiveAmount || parseFloat(item.amount))
}, 0)

const totalPaid = filteredItems.reduce((sum, item) => {
const itemWithSubs = item as typeof item & { subItems?: Array<{ amountPaid: string }> }
const subItemsPaidTotal = (itemWithSubs.subItems || []).reduce(
(subSum, subItem) => subSum + parseFloat(subItem.amountPaid),
0
)
return sum + parseFloat(item.amountPaid) + subItemsPaidTotal
}, 0)

const remainingToPay = totalAmount - totalPaid

// Handlers for item selection
const handleToggleSelectItem = (itemId: string) => {
setSelectedItemIds((prev) => {
const newSet = new Set(prev)
if (newSet.has(itemId)) {
newSet.delete(itemId)
} else {
newSet.add(itemId)
}
return newSet
})
}

const handleSelectAll = () => {
if (selectedItemIds.size === items.length) {
setSelectedItemIds(new Set())
} else {
setSelectedItemIds(new Set(items.map((item) => item.id)))
}
}

const handleEditItem = (item: BudgetItem) => {
setEditingItem(item)
}
Expand Down Expand Up @@ -120,7 +159,16 @@ export function BudgetTrackerList() {

{/* Summary Section */}
<div>
<h2 className="mb-4 text-xl font-semibold">Summary</h2>
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-semibold">
Summary {selectedItemIds.size > 0 && `(${selectedItemIds.size} selected)`}
</h2>
{items.length > 0 && (
<Button variant="outline" onClick={handleSelectAll}>
{selectedItemIds.size === items.length ? 'Deselect All' : 'Select All'}
</Button>
)}
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="grid gap-1 rounded-md border p-4">
<p className="text-muted-foreground text-sm">Total Budget</p>
Expand Down Expand Up @@ -150,6 +198,8 @@ export function BudgetTrackerList() {
<BudgetTrackerTable
data={items}
balance={currentBalance}
selectedItemIds={selectedItemIds}
onToggleSelectItem={handleToggleSelectItem}
onEditItem={handleEditItem}
onDeleteItem={handleDeleteItem}
onAddSubItem={handleAddSubItem}
Expand Down
24 changes: 18 additions & 6 deletions src/features/budget-tracker/components/budget-tracker-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useBudgetSubItems } from '@/hooks/use-budget'
interface BudgetTrackerTableProps {
data: BudgetItem[]
balance: number
selectedItemIds: Set<string>
onToggleSelectItem: (itemId: string) => void
onEditItem: (item: BudgetItem) => void
onDeleteItem: (item: BudgetItem) => void
onAddSubItem: (itemId: string) => void
Expand All @@ -23,6 +25,8 @@ interface BudgetTrackerTableProps {
export function BudgetTrackerTable({
data,
balance,
selectedItemIds,
onToggleSelectItem,
onEditItem,
onDeleteItem,
onAddSubItem,
Expand Down Expand Up @@ -69,6 +73,7 @@ export function BudgetTrackerTable({
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[40px]"></TableHead>
<TableHead className="w-[40px]"></TableHead>
<TableHead>Item</TableHead>
<TableHead>Amount</TableHead>
Expand All @@ -81,7 +86,7 @@ export function BudgetTrackerTable({
<TableBody>
{sortedData.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="h-24 text-center">
<TableCell colSpan={8} className="h-24 text-center">
No budget items yet.
</TableCell>
</TableRow>
Expand All @@ -94,11 +99,8 @@ export function BudgetTrackerTable({
)

function BudgetItemRow({ item }: { item: BudgetItem }) {
// Use pre-loaded sub-items if available (from API), otherwise fetch them
const itemWithSubItems = item as BudgetItem & { subItems?: BudgetSubItem[] }
const preLoadedSubItems = itemWithSubItems.subItems
const { data: fetchedSubItems = [] } = useBudgetSubItems(item.id)
const subItems = preLoadedSubItems || fetchedSubItems
// Always use the hook data to ensure real-time updates
const { data: subItems = [] } = useBudgetSubItems(item.id)
const hasSubItems = subItems.length > 0
const isExpanded = expandedItems.has(item.id)

Expand Down Expand Up @@ -126,9 +128,18 @@ export function BudgetTrackerTable({
return a.paid ? 1 : -1
})

const isSelected = selectedItemIds.has(item.id)

return (
<>
<TableRow className={cn({ 'opacity-50': item.paid })}>
<TableCell>
<Checkbox
checked={isSelected}
onCheckedChange={() => onToggleSelectItem(item.id)}
aria-label={`Select ${item.name}`}
/>
</TableCell>
<TableCell>
{hasSubItems && (
<Button
Expand Down Expand Up @@ -199,6 +210,7 @@ export function BudgetTrackerTable({
const subItemAffordable = isAffordable(subItem.amount, subItem.amountPaid)
return (
<TableRow key={subItem.id} className={cn('bg-muted/30', { 'opacity-50': subItem.paid })}>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell className="pl-8">
<span className="text-muted-foreground">↳</span> {subItem.name}
Expand Down