Modified by www.SourceFiles.app
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronRight, FileText } from "lucide-react";
|
||||
import { money } from "@/lib/reports";
|
||||
|
||||
export const Route = createFileRoute("/_authenticated/ledger")({
|
||||
head: () => ({ meta: [{ title: "Tuition — School Portal" }] }),
|
||||
component: LedgerListPage,
|
||||
});
|
||||
|
||||
function LedgerListPage() {
|
||||
const { data: students } = useQuery({
|
||||
queryKey: ["students-with-balance"],
|
||||
queryFn: async () => {
|
||||
const { data: studs } = await supabase
|
||||
.from("students")
|
||||
.select("id, first_name, last_name, daily_tuition_cents");
|
||||
const { data: entries } = await supabase
|
||||
.from("ledger_entries")
|
||||
.select("student_id, amount_cents, kind");
|
||||
const balances: Record<string, number> = {};
|
||||
(entries ?? []).forEach((e) => {
|
||||
balances[e.student_id] =
|
||||
(balances[e.student_id] ?? 0) + (e.kind === "charge" ? e.amount_cents : -e.amount_cents);
|
||||
});
|
||||
return (studs ?? []).map((s) => ({ ...s, balance: balances[s.id] ?? 0 }));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-4xl">
|
||||
<h1 className="text-2xl font-semibold mb-1">Tuition ledger</h1>
|
||||
<p className="text-muted-foreground text-sm mb-6">
|
||||
Click a student to view and manage their ledger, or build an invoice. Students with a daily
|
||||
rate are charged automatically when marked present or late.
|
||||
</p>
|
||||
<div className="bg-card border rounded-lg divide-y">
|
||||
{(students ?? []).map((s) => (
|
||||
<div key={s.id} className="flex items-center gap-3 p-3 hover:bg-muted/50">
|
||||
<Link
|
||||
to="/students/$id"
|
||||
params={{ id: s.id }}
|
||||
className="flex-1 min-w-0 flex items-center gap-2"
|
||||
>
|
||||
<span className="font-medium truncate">
|
||||
{s.last_name}, {s.first_name}
|
||||
</span>
|
||||
{s.daily_tuition_cents ? (
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
{money(s.daily_tuition_cents)}/day
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground shrink-0">no rate</span>
|
||||
)}
|
||||
</Link>
|
||||
<span
|
||||
className={`text-sm font-medium shrink-0 ${s.balance > 0 ? "text-destructive" : "text-green-700"}`}
|
||||
>
|
||||
{money(s.balance)}
|
||||
</span>
|
||||
<Link to="/invoice/$id" params={{ id: s.id }} className="shrink-0">
|
||||
<Button size="sm" variant="outline">
|
||||
<FileText className="h-4 w-4 mr-1" /> Invoice
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/students/$id" params={{ id: s.id }} className="shrink-0">
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
{students?.length === 0 && (
|
||||
<div className="p-6 text-center text-sm text-muted-foreground">No students.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user