From 505d47918e4d9b20338eb3194af64e95899c3a94 Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Thu, 17 Jul 2025 02:27:36 -0400 Subject: [PATCH] Colors! --- src/components/forms/invoice-form.tsx | 128 +++++++++++++++++++++----- src/styles/globals.css | 86 ++++++----------- 2 files changed, 135 insertions(+), 79 deletions(-) diff --git a/src/components/forms/invoice-form.tsx b/src/components/forms/invoice-form.tsx index 4b5c1ac..911fff5 100644 --- a/src/components/forms/invoice-form.tsx +++ b/src/components/forms/invoice-form.tsx @@ -231,8 +231,9 @@ function InvoiceFormSkeleton() { ); } -export function InvoiceForm({ invoiceId }: InvoiceFormProps) { +function InvoiceForm({ invoiceId }: InvoiceFormProps) { const router = useRouter(); + const utils = api.useUtils(); const [formData, setFormData] = useState({ invoiceNumber: `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Date.now().toString().slice(-6)}`, @@ -418,6 +419,8 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { const createInvoice = api.invoices.create.useMutation({ onSuccess: () => { toast.success("Invoice created successfully"); + // Invalidate related queries to refresh cache + void utils.invoices.getAll.invalidate(); router.push("/dashboard/invoices"); }, onError: (error) => { @@ -428,44 +431,116 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { const updateInvoice = api.invoices.update.useMutation({ onSuccess: () => { toast.success("Invoice updated successfully"); + // Invalidate related queries to refresh cache + void utils.invoices.getAll.invalidate(); + if (invoiceId) { + void utils.invoices.getById.invalidate({ id: invoiceId }); + } router.push("/dashboard/invoices"); }, onError: (error) => { + console.error("Update invoice error:", error); toast.error(error.message || "Failed to update invoice"); }, }); + const updateStatus = api.invoices.updateStatus.useMutation({ + onSuccess: () => { + toast.success("Status updated successfully"); + // Invalidate related queries to refresh cache + void utils.invoices.getAll.invalidate(); + if (invoiceId) { + void utils.invoices.getById.invalidate({ id: invoiceId }); + } + router.push("/dashboard/invoices"); + }, + onError: (error) => { + console.error("Update status error:", error); + toast.error(error.message || "Failed to update status"); + }, + }); + + // Check if only status has changed compared to existing invoice + const hasOnlyStatusChanged = React.useMemo(() => { + if (!existingInvoice || !invoiceId) return false; + + return ( + formData.invoiceNumber === existingInvoice.invoiceNumber && + formData.businessId === (existingInvoice.businessId ?? "") && + formData.clientId === existingInvoice.clientId && + formData.issueDate.getTime() === + new Date(existingInvoice.issueDate).getTime() && + formData.dueDate.getTime() === + new Date(existingInvoice.dueDate).getTime() && + formData.status !== existingInvoice.status && + formData.notes === (existingInvoice.notes ?? "") && + formData.taxRate === existingInvoice.taxRate && + JSON.stringify( + formData.items.map((item) => ({ + date: item.date.getTime(), + description: item.description, + hours: item.hours, + rate: item.rate, + })), + ) === + JSON.stringify( + (existingInvoice.items ?? []).map((item) => ({ + date: new Date(item.date).getTime(), + description: item.description, + hours: item.hours, + rate: item.rate, + })), + ) + ); + }, [formData, existingInvoice, invoiceId]); + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { - const invoiceData = { - invoiceNumber: formData.invoiceNumber, - businessId: formData.businessId || undefined, - clientId: formData.clientId, - issueDate: formData.issueDate, - dueDate: formData.dueDate, - status: formData.status, - notes: formData.notes, - taxRate: formData.taxRate, - - items: formData.items.map((item) => ({ - date: item.date, - description: item.description, - hours: item.hours, - rate: item.rate, - amount: item.hours * item.rate, - })), - }; - - if (invoiceId) { - await updateInvoice.mutateAsync({ id: invoiceId, ...invoiceData }); + if (invoiceId && hasOnlyStatusChanged) { + // Use dedicated status update mutation for status-only changes + console.log("Using status-only update:", { + id: invoiceId, + status: formData.status, + }); + await updateStatus.mutateAsync({ + id: invoiceId, + status: formData.status, + }); } else { - await createInvoice.mutateAsync(invoiceData); + // Use full update mutation for all other changes + const invoiceData = { + invoiceNumber: formData.invoiceNumber, + businessId: formData.businessId || undefined, + clientId: formData.clientId, + issueDate: formData.issueDate, + dueDate: formData.dueDate, + status: formData.status, + notes: formData.notes, + taxRate: formData.taxRate, + + items: formData.items.map((item) => ({ + date: item.date, + description: item.description, + hours: item.hours, + rate: item.rate, + amount: item.hours * item.rate, + })), + }; + + console.log("Submitting invoice data:", invoiceData); + + if (invoiceId) { + await updateInvoice.mutateAsync({ id: invoiceId, ...invoiceData }); + } else { + await createInvoice.mutateAsync(invoiceData); + } } } catch (error) { console.error("Error saving invoice:", error); + toast.error("Failed to save invoice. Check console for details."); } finally { setLoading(false); } @@ -567,6 +642,11 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { ))} + {invoiceId && hasOnlyStatusChanged && ( +
+ Only status will be updated +
+ )} @@ -839,3 +919,5 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { ); } + +export { InvoiceForm }; diff --git a/src/styles/globals.css b/src/styles/globals.css index 4433331..4a0ac9a 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -107,19 +107,19 @@ @media (prefers-color-scheme: dark) { :root { - --background: oklch(0 0 0); + --background: oklch(0.1 0.05 180); --foreground: oklch(0.985 0 0); - --card: oklch(0.25 0.08 170); + --card: oklch(0.15 0.03 180); --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.25 0.08 170); + --popover: oklch(0.15 0.03 180); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.7 0.15 165); --primary-foreground: oklch(0.08 0.015 165); - --secondary: oklch(0.3 0.05 170); + --secondary: oklch(0.25 0.02 180); --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.3 0.05 170); + --muted: oklch(0.25 0.02 180); --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.3 0.05 170); + --accent: oklch(0.25 0.02 180); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --destructive-foreground: oklch(0.985 0 0); @@ -131,11 +131,11 @@ --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0.02 160); + --sidebar: oklch(0.12 0.04 180); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.696 0.17 162.48); --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0.015 160); + --sidebar-accent: oklch(0.18 0.03 180); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); @@ -199,31 +199,7 @@ @media (prefers-color-scheme: dark) { body { - background: - radial-gradient( - circle at 20% 80%, - oklch(0.25 0.15 165 / 0.6) 0%, - transparent 50% - ), - radial-gradient( - circle at 80% 20%, - oklch(0.3 0.12 185 / 0.4) 0%, - transparent 50% - ), - radial-gradient( - circle at 40% 40%, - oklch(0.35 0.1 205 / 0.3) 0%, - transparent 50% - ), - linear-gradient( - 135deg, - oklch(0.15 0.12 165) 0%, - oklch(0.18 0.1 175) 25%, - oklch(0.16 0.14 185) 50%, - oklch(0.15 0.12 195) 75%, - oklch(0.18 0.1 205) 100% - ) - fixed; + background: hsl(180 50% 8%); } } @@ -427,8 +403,8 @@ .bg-gradient-auth { background: linear-gradient( 135deg, - oklch(0.88 0.12 165) 0%, - oklch(0.92 0.08 185) 100% + oklch(0.667 0.192 164.206) 0%, + oklch(0.731 0.182 183.061) 100% ); } @@ -436,8 +412,8 @@ .bg-gradient-auth { background: linear-gradient( 135deg, - oklch(0.15 0.12 165) 0%, - oklch(0.18 0.1 185) 100% + oklch(0.15 0.12 180) 0%, + oklch(0.18 0.1 180) 100% ); } } @@ -740,15 +716,15 @@ } .bg-hero-gradient { - @apply bg-gradient-to-br from-emerald-600 via-teal-700 to-blue-800 dark:from-emerald-600/95 dark:via-teal-700/95 dark:to-blue-800/95; + @apply bg-gradient-to-br from-emerald-600 via-teal-700 to-blue-800 dark:from-teal-700 dark:via-teal-800 dark:to-teal-900; } .bg-page-gradient { - @apply bg-gradient-to-br from-white via-emerald-50/50 to-teal-50/30 dark:from-slate-900 dark:via-teal-900/8 dark:to-blue-900/8; + @apply bg-gradient-to-br from-white via-emerald-50/50 to-teal-50/30 dark:from-teal-950 dark:via-teal-900 dark:to-teal-800; } .bg-features-gradient { - @apply bg-gradient-to-br from-white via-emerald-50/30 to-teal-50/50 dark:from-slate-900/95 dark:via-teal-900/12 dark:to-blue-900/12; + @apply bg-gradient-to-br from-white via-emerald-50/30 to-teal-50/50 dark:from-teal-950 dark:via-teal-900 dark:to-teal-800; } /* Card Utility Classes */ @@ -774,45 +750,43 @@ /* Modern Dark Theme Styling */ @media (prefers-color-scheme: dark) { - /* Page background - rich dark base */ + /* Page background - rich dark teal base */ .floating-orbs { - background-color: hsl( - 206 12% 8% - ) !important; /* Rich dark blue-green background */ + background-color: hsl(180 50% 8%) !important; /* Dark teal background */ } - /* All cards - warm neutral with subtle transparency */ + /* All cards - dark teal with subtle transparency */ [data-slot="card"] { - background-color: hsl( - 206 10% 13% / 0.9 - ) !important; /* Blue-green dark cards */ - border-color: hsl(206 10% 20%) !important; /* Subtle borders */ + background-color: hsl(180 30% 10%) !important; /* Dark teal cards */ + border-color: hsl(180 25% 15%) !important; /* Subtle teal borders */ } /* Secondary cards - slightly lighter for hierarchy */ [data-slot="card"].card-secondary, .card-secondary { background-color: hsl( - 206 9% 16% / 0.85 - ) !important; /* Lighter secondary */ - border-color: hsl(206 9% 24%) !important; /* Softer borders */ + 180 25% 12% + ) !important; /* Lighter teal secondary */ + border-color: hsl(180 20% 20%) !important; /* Softer teal borders */ } /* Navigation elements - cohesive with cards */ .nav-sticky, aside.bg-background\/60, header .bg-background\/60 { - background-color: hsl(210 10% 12% / 0.95) !important; /* Navigation bg */ - border-color: hsl(210 10% 20%) !important; /* Consistent borders */ + background-color: hsl( + 180 40% 9% / 0.95 + ) !important; /* Teal navigation bg */ + border-color: hsl(180 30% 18%) !important; /* Consistent teal borders */ } /* Invoice line item mobile styling */ .dark .bg-gray-200\/30 { - background-color: hsl(210 8% 18% / 0.4) !important; + background-color: hsl(180 20% 15% / 0.4) !important; } .dark .border-gray-400\/60 { - border-color: hsl(210 8% 25% / 0.6) !important; + border-color: hsl(180 15% 22% / 0.6) !important; } }