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 [view, setView] = React.useState<"month" | "week">("month");
|
||||
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) => {
|
||||
return items
|
||||
.map((item, index) => ({ item, index }))
|
||||
@@ -69,18 +78,9 @@ export function InvoiceCalendarView({
|
||||
setDate(newDate);
|
||||
// Optionally update viewDate to match selection if desired, but user wants them decoupled during nav
|
||||
// setViewDate(newDate);
|
||||
const dateItems = getItemsForDate(newDate);
|
||||
setSelectedDateItems(dateItems);
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
// refresh selected items when main items change
|
||||
React.useEffect(() => {
|
||||
if (date && dialogOpen) {
|
||||
setSelectedDateItems(getItemsForDate(date));
|
||||
}
|
||||
}, [items, date, dialogOpen, getItemsForDate]);
|
||||
|
||||
const handleAddNewItem = () => {
|
||||
if (date) {
|
||||
onAddItem(date);
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
||||
status: existingInvoice.status as "draft" | "sent" | "paid",
|
||||
notes: existingInvoice.notes ?? "",
|
||||
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 }],
|
||||
});
|
||||
setInitialized(true);
|
||||
@@ -251,7 +251,10 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
||||
<div className="page-enter space-y-6 pb-32">
|
||||
<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>}
|
||||
<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>
|
||||
|
||||
<Tabs value={activeTab} className="w-full" onValueChange={setActiveTab}>
|
||||
|
||||
Reference in New Issue
Block a user