Added fee schedule picker
X-Lovable-Edit-ID: edt-509b2427-597f-430b-84f3-bfa465923d10 Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,21 @@ import { useAuth } from "@/lib/auth";
|
||||
import { Plus, Trash2, Loader2, Receipt as ReceiptIcon } from "lucide-react";
|
||||
import { formatCurrency, formatDate } from "@/lib/format";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
interface FeeItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
amount: number;
|
||||
billable: boolean;
|
||||
}
|
||||
|
||||
export function CaseExpensesTab({ caseId }: { caseId: string }) {
|
||||
const { user } = useAuth();
|
||||
@@ -24,6 +39,9 @@ export function CaseExpensesTab({ caseId }: { caseId: string }) {
|
||||
const [receipt, setReceipt] = useState<File | null>(null);
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [fees, setFees] = useState<FeeItem[]>([]);
|
||||
const [selectedFeeId, setSelectedFeeId] = useState<string>("");
|
||||
|
||||
const load = async () => {
|
||||
const { data } = await supabase
|
||||
.from("expenses")
|
||||
@@ -35,6 +53,19 @@ export function CaseExpensesTab({ caseId }: { caseId: string }) {
|
||||
|
||||
useEffect(() => { load(); }, [caseId]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const { data } = await supabase
|
||||
.from("fee_schedule_items")
|
||||
.select("id, name, description, amount, billable")
|
||||
.eq("category", "expense")
|
||||
.eq("active", true)
|
||||
.order("sort_order")
|
||||
.order("name");
|
||||
setFees((data ?? []) as FeeItem[]);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const submit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const amount = parseFloat(form.amount);
|
||||
@@ -108,6 +139,37 @@ export function CaseExpensesTab({ caseId }: { caseId: string }) {
|
||||
{showForm && (
|
||||
<Card className="border-border/60">
|
||||
<CardContent className="p-4">
|
||||
{fees.length > 0 && (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<Label className="text-xs">Fee schedule (optional)</Label>
|
||||
<Select
|
||||
value={selectedFeeId}
|
||||
onValueChange={(v) => {
|
||||
setSelectedFeeId(v);
|
||||
const fee = fees.find((f) => f.id === v);
|
||||
if (fee) {
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
amount: String(fee.amount),
|
||||
billable: fee.billable,
|
||||
description: f.description || fee.description || fee.name,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pick from the fee schedule…" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fees.map((f) => (
|
||||
<SelectItem key={f.id} value={f.id}>
|
||||
{f.name} — {formatCurrency(Number(f.amount))}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={submit} className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Date</Label>
|
||||
|
||||
@@ -11,6 +11,21 @@ import { Plus, Trash2, Loader2, FilePlus } from "lucide-react";
|
||||
import { formatCurrency, formatDate } from "@/lib/format";
|
||||
import { roundToTenth } from "@/lib/timer";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
interface FeeItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
amount: number;
|
||||
billable: boolean;
|
||||
}
|
||||
|
||||
interface CaseTimeTabProps {
|
||||
caseRecord: any;
|
||||
@@ -31,6 +46,9 @@ export function CaseTimeTab({ caseRecord, onInvoice }: CaseTimeTabProps) {
|
||||
billable: true,
|
||||
});
|
||||
|
||||
const [fees, setFees] = useState<FeeItem[]>([]);
|
||||
const [selectedFeeId, setSelectedFeeId] = useState<string>("");
|
||||
|
||||
const load = async () => {
|
||||
const { data, error } = await supabase
|
||||
.from("time_entries")
|
||||
@@ -49,6 +67,20 @@ export function CaseTimeTab({ caseRecord, onInvoice }: CaseTimeTabProps) {
|
||||
|
||||
useEffect(() => { load(); }, [caseRecord.id]);
|
||||
|
||||
// Load fee schedule (time category)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const { data } = await supabase
|
||||
.from("fee_schedule_items")
|
||||
.select("id, name, description, amount, billable")
|
||||
.eq("category", "time")
|
||||
.eq("active", true)
|
||||
.order("sort_order")
|
||||
.order("name");
|
||||
setFees((data ?? []) as FeeItem[]);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
// Pull this user's default hourly rate; use it to prefill when case has no override.
|
||||
useEffect(() => {
|
||||
if (!user?.id) return;
|
||||
@@ -128,6 +160,37 @@ export function CaseTimeTab({ caseRecord, onInvoice }: CaseTimeTabProps) {
|
||||
{showForm && (
|
||||
<Card className="border-border/60">
|
||||
<CardContent className="p-4">
|
||||
{fees.length > 0 && (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<Label className="text-xs">Fee schedule (optional)</Label>
|
||||
<Select
|
||||
value={selectedFeeId}
|
||||
onValueChange={(v) => {
|
||||
setSelectedFeeId(v);
|
||||
const fee = fees.find((f) => f.id === v);
|
||||
if (fee) {
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
hourly_rate: String(fee.amount),
|
||||
billable: fee.billable,
|
||||
description: f.description || fee.description || fee.name,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pick a rate from the fee schedule…" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fees.map((f) => (
|
||||
<SelectItem key={f.id} value={f.id}>
|
||||
{f.name} — {formatCurrency(Number(f.amount))}/hr
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={submit} className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Date</Label>
|
||||
|
||||
Reference in New Issue
Block a user