mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-02-05 00:06:36 -05:00
refactor: Remove env.example, optimize invoice calendar item selection with derived state, and enhance invoice form's default hourly rate initialization and save button loading state.
This commit is contained in:
24
env.example
24
env.example
@@ -1,24 +0,0 @@
|
|||||||
# Base application env (example)
|
|
||||||
NODE_ENV=production
|
|
||||||
PORT=3000
|
|
||||||
HOSTNAME=0.0.0.0
|
|
||||||
|
|
||||||
# NextAuth
|
|
||||||
AUTH_SECRET=__GENERATE__
|
|
||||||
|
|
||||||
# Database (Postgres)
|
|
||||||
POSTGRES_USER=beenvoice
|
|
||||||
POSTGRES_PASSWORD=__GENERATE__
|
|
||||||
POSTGRES_DB=beenvoice
|
|
||||||
DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
|
||||||
# Disable SSL for Docker local Postgres; set to false or remove for managed Postgres
|
|
||||||
DB_DISABLE_SSL=true
|
|
||||||
|
|
||||||
# Email (Resend). Replace with real keys in production
|
|
||||||
RESEND_API_KEY=replace-or-remove
|
|
||||||
RESEND_DOMAIN=
|
|
||||||
|
|
||||||
# Build tweaks
|
|
||||||
SKIP_ENV_VALIDATION=1
|
|
||||||
|
|
||||||
|
|
||||||
@@ -52,9 +52,18 @@ export function InvoiceCalendarView({
|
|||||||
const [viewDate, setViewDate] = React.useState<Date>(new Date()); // Controls the view (month/week)
|
const [viewDate, setViewDate] = React.useState<Date>(new Date()); // Controls the view (month/week)
|
||||||
const [view, setView] = React.useState<"month" | "week">("month");
|
const [view, setView] = React.useState<"month" | "week">("month");
|
||||||
const [dialogOpen, setDialogOpen] = React.useState(false);
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
||||||
const [selectedDateItems, setSelectedDateItems] = React.useState<{ item: InvoiceItem; index: number }[]>([]);
|
// Derived state for selected date items - solves cursor jumping
|
||||||
|
const selectedDateItems = React.useMemo(() => {
|
||||||
|
if (!date) return [];
|
||||||
|
return items
|
||||||
|
.map((item, index) => ({ item, index }))
|
||||||
|
.filter((wrapper) => {
|
||||||
|
const itemDate = new Date(wrapper.item.date);
|
||||||
|
return isSameDay(itemDate, date);
|
||||||
|
});
|
||||||
|
}, [items, date]);
|
||||||
|
|
||||||
// Function to get items for the selected date
|
// Helper to get items for any date (for calendar view)
|
||||||
const getItemsForDate = React.useCallback((targetDate: Date) => {
|
const getItemsForDate = React.useCallback((targetDate: Date) => {
|
||||||
return items
|
return items
|
||||||
.map((item, index) => ({ item, index }))
|
.map((item, index) => ({ item, index }))
|
||||||
@@ -69,18 +78,9 @@ export function InvoiceCalendarView({
|
|||||||
setDate(newDate);
|
setDate(newDate);
|
||||||
// Optionally update viewDate to match selection if desired, but user wants them decoupled during nav
|
// Optionally update viewDate to match selection if desired, but user wants them decoupled during nav
|
||||||
// setViewDate(newDate);
|
// setViewDate(newDate);
|
||||||
const dateItems = getItemsForDate(newDate);
|
|
||||||
setSelectedDateItems(dateItems);
|
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// refresh selected items when main items change
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (date && dialogOpen) {
|
|
||||||
setSelectedDateItems(getItemsForDate(date));
|
|
||||||
}
|
|
||||||
}, [items, date, dialogOpen, getItemsForDate]);
|
|
||||||
|
|
||||||
const handleAddNewItem = () => {
|
const handleAddNewItem = () => {
|
||||||
if (date) {
|
if (date) {
|
||||||
onAddItem(date);
|
onAddItem(date);
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
status: existingInvoice.status as "draft" | "sent" | "paid",
|
status: existingInvoice.status as "draft" | "sent" | "paid",
|
||||||
notes: existingInvoice.notes ?? "",
|
notes: existingInvoice.notes ?? "",
|
||||||
taxRate: existingInvoice.taxRate,
|
taxRate: existingInvoice.taxRate,
|
||||||
defaultHourlyRate: null,
|
defaultHourlyRate: existingInvoice.client?.defaultHourlyRate ?? null,
|
||||||
items: mappedItems.length > 0 ? mappedItems : [{ id: crypto.randomUUID(), date: new Date(), description: "", hours: 1, rate: 0, amount: 0 }],
|
items: mappedItems.length > 0 ? mappedItems : [{ id: crypto.randomUUID(), date: new Date(), description: "", hours: 1, rate: 0, amount: 0 }],
|
||||||
});
|
});
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
@@ -251,7 +251,10 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
<div className="page-enter space-y-6 pb-32">
|
<div className="page-enter space-y-6 pb-32">
|
||||||
<PageHeader title={invoiceId !== "new" ? "Edit Invoice" : "Create Invoice"} description="Manage your invoice" variant="gradient">
|
<PageHeader title={invoiceId !== "new" ? "Edit Invoice" : "Create Invoice"} description="Manage your invoice" variant="gradient">
|
||||||
{invoiceId !== "new" && <Button variant="secondary" onClick={handleDelete} className="text-destructive">Delete</Button>}
|
{invoiceId !== "new" && <Button variant="secondary" onClick={handleDelete} className="text-destructive">Delete</Button>}
|
||||||
<Button onClick={handleSubmit} variant="secondary"><Save className="mr-2 h-4 w-4" /> Save</Button>
|
<Button onClick={handleSubmit} variant="secondary" disabled={loading}>
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
{loading ? "Saving..." : "Save"}
|
||||||
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<Tabs value={activeTab} className="w-full" onValueChange={setActiveTab}>
|
<Tabs value={activeTab} className="w-full" onValueChange={setActiveTab}>
|
||||||
|
|||||||
Reference in New Issue
Block a user