mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-15 10:34:43 -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 router = useRouter();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
invoiceNumber: `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Date.now().toString().slice(-6)}`,
|
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({
|
const createInvoice = api.invoices.create.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Invoice created successfully");
|
toast.success("Invoice created successfully");
|
||||||
|
// Invalidate related queries to refresh cache
|
||||||
|
void utils.invoices.getAll.invalidate();
|
||||||
router.push("/dashboard/invoices");
|
router.push("/dashboard/invoices");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -428,44 +431,116 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
const updateInvoice = api.invoices.update.useMutation({
|
const updateInvoice = api.invoices.update.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Invoice updated successfully");
|
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");
|
router.push("/dashboard/invoices");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
console.error("Update invoice error:", error);
|
||||||
toast.error(error.message || "Failed to update invoice");
|
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) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const invoiceData = {
|
if (invoiceId && hasOnlyStatusChanged) {
|
||||||
invoiceNumber: formData.invoiceNumber,
|
// Use dedicated status update mutation for status-only changes
|
||||||
businessId: formData.businessId || undefined,
|
console.log("Using status-only update:", {
|
||||||
clientId: formData.clientId,
|
id: invoiceId,
|
||||||
issueDate: formData.issueDate,
|
status: formData.status,
|
||||||
dueDate: formData.dueDate,
|
});
|
||||||
status: formData.status,
|
await updateStatus.mutateAsync({
|
||||||
notes: formData.notes,
|
id: invoiceId,
|
||||||
taxRate: formData.taxRate,
|
status: formData.status,
|
||||||
|
});
|
||||||
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 });
|
|
||||||
} else {
|
} 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) {
|
} catch (error) {
|
||||||
console.error("Error saving invoice:", error);
|
console.error("Error saving invoice:", error);
|
||||||
|
toast.error("Failed to save invoice. Check console for details.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -567,6 +642,11 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
{invoiceId && hasOnlyStatusChanged && (
|
||||||
|
<div className="mt-1 text-xs text-blue-600 dark:text-blue-400">
|
||||||
|
Only status will be updated
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -839,3 +919,5 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { InvoiceForm };
|
||||||
|
|||||||
@@ -107,19 +107,19 @@
|
|||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--background: oklch(0 0 0);
|
--background: oklch(0.1 0.05 180);
|
||||||
--foreground: oklch(0.985 0 0);
|
--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);
|
--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);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.7 0.15 165);
|
--primary: oklch(0.7 0.15 165);
|
||||||
--primary-foreground: oklch(0.08 0.015 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);
|
--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);
|
--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);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--destructive-foreground: oklch(0.985 0 0);
|
--destructive-foreground: oklch(0.985 0 0);
|
||||||
@@ -131,11 +131,11 @@
|
|||||||
--chart-3: oklch(0.769 0.188 70.08);
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
--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-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-primary: oklch(0.696 0.17 162.48);
|
--sidebar-primary: oklch(0.696 0.17 162.48);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--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-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
@@ -199,31 +199,7 @@
|
|||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background:
|
background: hsl(180 50% 8%);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,8 +403,8 @@
|
|||||||
.bg-gradient-auth {
|
.bg-gradient-auth {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
oklch(0.88 0.12 165) 0%,
|
oklch(0.667 0.192 164.206) 0%,
|
||||||
oklch(0.92 0.08 185) 100%
|
oklch(0.731 0.182 183.061) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,8 +412,8 @@
|
|||||||
.bg-gradient-auth {
|
.bg-gradient-auth {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
oklch(0.15 0.12 165) 0%,
|
oklch(0.15 0.12 180) 0%,
|
||||||
oklch(0.18 0.1 185) 100%
|
oklch(0.18 0.1 180) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -740,15 +716,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bg-hero-gradient {
|
.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 {
|
.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 {
|
.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 */
|
/* Card Utility Classes */
|
||||||
@@ -774,45 +750,43 @@
|
|||||||
|
|
||||||
/* Modern Dark Theme Styling */
|
/* Modern Dark Theme Styling */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
/* Page background - rich dark base */
|
/* Page background - rich dark teal base */
|
||||||
.floating-orbs {
|
.floating-orbs {
|
||||||
background-color: hsl(
|
background-color: hsl(180 50% 8%) !important; /* Dark teal background */
|
||||||
206 12% 8%
|
|
||||||
) !important; /* Rich dark blue-green background */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All cards - warm neutral with subtle transparency */
|
/* All cards - dark teal with subtle transparency */
|
||||||
[data-slot="card"] {
|
[data-slot="card"] {
|
||||||
background-color: hsl(
|
background-color: hsl(180 30% 10%) !important; /* Dark teal cards */
|
||||||
206 10% 13% / 0.9
|
border-color: hsl(180 25% 15%) !important; /* Subtle teal borders */
|
||||||
) !important; /* Blue-green dark cards */
|
|
||||||
border-color: hsl(206 10% 20%) !important; /* Subtle borders */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Secondary cards - slightly lighter for hierarchy */
|
/* Secondary cards - slightly lighter for hierarchy */
|
||||||
[data-slot="card"].card-secondary,
|
[data-slot="card"].card-secondary,
|
||||||
.card-secondary {
|
.card-secondary {
|
||||||
background-color: hsl(
|
background-color: hsl(
|
||||||
206 9% 16% / 0.85
|
180 25% 12%
|
||||||
) !important; /* Lighter secondary */
|
) !important; /* Lighter teal secondary */
|
||||||
border-color: hsl(206 9% 24%) !important; /* Softer borders */
|
border-color: hsl(180 20% 20%) !important; /* Softer teal borders */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation elements - cohesive with cards */
|
/* Navigation elements - cohesive with cards */
|
||||||
.nav-sticky,
|
.nav-sticky,
|
||||||
aside.bg-background\/60,
|
aside.bg-background\/60,
|
||||||
header .bg-background\/60 {
|
header .bg-background\/60 {
|
||||||
background-color: hsl(210 10% 12% / 0.95) !important; /* Navigation bg */
|
background-color: hsl(
|
||||||
border-color: hsl(210 10% 20%) !important; /* Consistent borders */
|
180 40% 9% / 0.95
|
||||||
|
) !important; /* Teal navigation bg */
|
||||||
|
border-color: hsl(180 30% 18%) !important; /* Consistent teal borders */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Invoice line item mobile styling */
|
/* Invoice line item mobile styling */
|
||||||
.dark .bg-gray-200\/30 {
|
.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 {
|
.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