Fix edit invoice initialization and routing

The form initialization logic for editing invoices was improved to
handle route changes correctly. The edit link path was fixed and cache
invalidation was added to ensure fresh data on navigation.
This commit is contained in:
2025-08-01 03:33:19 -04:00
parent 5e30d338af
commit 9de86df070
2 changed files with 48 additions and 37 deletions

View File

@@ -131,7 +131,7 @@ function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
> >
<PDFDownloadButton invoiceId={invoice.id} variant="outline" /> <PDFDownloadButton invoiceId={invoice.id} variant="outline" />
<Button asChild variant="default"> <Button asChild variant="default">
<Link href={`/dashboard/invoices/${invoice.id}`}> <Link href={`/dashboard/invoices/${invoice.id}/edit`}>
<Edit className="h-5 w-5" /> <Edit className="h-5 w-5" />
<span>Edit</span> <span>Edit</span>
</Link> </Link>

View File

@@ -143,19 +143,26 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
}, },
}); });
// Single initialization effect - only runs once when data is ready // Reset initialization when invoiceId changes
useEffect(() => { useEffect(() => {
if (initialized) return; setInitialized(false);
}, [invoiceId]);
const dataReady = // Initialize form data when invoice data is loaded
!loadingClients && useEffect(() => {
!loadingBusinesses && if (invoiceId && invoiceId !== "new" && existingInvoice && !initialized) {
(!invoiceId || invoiceId === "new" || !loadingInvoice);
if (!dataReady) return;
if (invoiceId && invoiceId !== "new" && existingInvoice) {
// Initialize with existing invoice data // Initialize with existing invoice data
const formDataToSet = { const mappedItems =
existingInvoice.items?.map((item) => ({
id: crypto.randomUUID(),
date: new Date(item.date),
description: item.description,
hours: item.hours,
rate: item.rate,
amount: item.amount,
})) || [];
setFormData({
invoiceNumber: existingInvoice.invoiceNumber, invoiceNumber: existingInvoice.invoiceNumber,
businessId: existingInvoice.businessId ?? "", businessId: existingInvoice.businessId ?? "",
clientId: existingInvoice.clientId, clientId: existingInvoice.clientId,
@@ -166,43 +173,43 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
taxRate: existingInvoice.taxRate, taxRate: existingInvoice.taxRate,
defaultHourlyRate: null, defaultHourlyRate: null,
items: items:
existingInvoice.items?.map((item) => ({ mappedItems.length > 0
id: crypto.randomUUID(), ? mappedItems
date: new Date(item.date), : [
description: item.description, {
hours: item.hours, id: crypto.randomUUID(),
rate: item.rate, date: new Date(),
amount: item.amount, description: "",
})) || [], hours: 1,
}; rate: 0,
setFormData(formDataToSet); amount: 0,
} else if ((!invoiceId || invoiceId === "new") && businesses) { },
],
});
firstItemEditedRef.current = false;
setInitialized(true);
} else if (
(!invoiceId || invoiceId === "new") &&
businesses &&
!initialized
) {
// New invoice - set default business // New invoice - set default business
const defaultBusiness = businesses.find((b) => b.isDefault); const defaultBusiness = businesses.find((b) => b.isDefault);
if (defaultBusiness) { if (defaultBusiness) {
setFormData((prev) => ({ ...prev, businessId: defaultBusiness.id })); setFormData((prev) => ({ ...prev, businessId: defaultBusiness.id }));
} else if (businesses.length > 0) { } else if (businesses.length > 0) {
// If no default business, use the first one
setFormData((prev) => ({ ...prev, businessId: businesses[0]!.id })); setFormData((prev) => ({ ...prev, businessId: businesses[0]!.id }));
} }
setInitialized(true);
} }
}, [invoiceId, existingInvoice, businesses, initialized]);
// Reset the first item edited flag when initializing
firstItemEditedRef.current = false;
setInitialized(true);
}, [
loadingClients,
loadingBusinesses,
loadingInvoice,
existingInvoice,
businesses,
invoiceId,
initialized,
]);
// Update the first line item when defaultHourlyRate changes (if it hasn't been manually edited) // Update the first line item when defaultHourlyRate changes (if it hasn't been manually edited)
// Only for new invoices, not existing ones being edited
useEffect(() => { useEffect(() => {
if ( if (
(!invoiceId || invoiceId === "new") &&
!firstItemEditedRef.current && !firstItemEditedRef.current &&
formData.items.length === 1 && formData.items.length === 1 &&
formData.items[0]?.description === "" && formData.items[0]?.description === "" &&
@@ -220,7 +227,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
], ],
})); }));
} }
}, [formData.defaultHourlyRate, formData.items]); }, [formData.defaultHourlyRate]);
// Update default hourly rate when client changes // Update default hourly rate when client changes
useEffect(() => { useEffect(() => {
@@ -353,6 +360,10 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
onSuccess: async () => { onSuccess: async () => {
toast.success("Invoice updated successfully"); toast.success("Invoice updated successfully");
await utils.invoices.getAll.invalidate(); await utils.invoices.getAll.invalidate();
// Invalidate the specific invoice cache to ensure fresh data on navigation
if (invoiceId && invoiceId !== "new") {
await utils.invoices.getById.invalidate({ id: invoiceId });
}
// The update mutation returns { success: true }, so we use the current invoiceId // The update mutation returns { success: true }, so we use the current invoiceId
if (invoiceId && invoiceId !== "new") { if (invoiceId && invoiceId !== "new") {
router.push(`/dashboard/invoices/${invoiceId}`); router.push(`/dashboard/invoices/${invoiceId}`);