mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-13 01:24:44 -05:00
Colors!
This commit is contained in:
@@ -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) {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{invoiceId && hasOnlyStatusChanged && (
|
||||
<div className="mt-1 text-xs text-blue-600 dark:text-blue-400">
|
||||
Only status will be updated
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -839,3 +919,5 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { InvoiceForm };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user