Add scheduled invoice delivery

This commit is contained in:
2026-08-17 13:54:45 -04:00
parent 29589c1f32
commit 67ab6b78bd
26 changed files with 2099 additions and 835 deletions
@@ -23,15 +23,10 @@ import {
import { toast } from "sonner";
import { invoiceLabel } from "~/lib/time-entry-display";
import type { RouterOutputs } from "~/trpc/react";
import { toLocalDateTimeInputValue } from "@beenvoice/domain/time-zone";
type TimeEntry = RouterOutputs["timeEntries"]["getById"];
function toDatetimeLocalValue(value: Date | string) {
const start = new Date(value);
start.setMinutes(start.getMinutes() - start.getTimezoneOffset());
return start.toISOString().slice(0, 16);
}
export type TimeEntryEditDialogProps = {
entryId: string | null;
open: boolean;
@@ -56,9 +51,11 @@ function TimeEntryEditForm({
const [clientId, setClientId] = useState(entry.clientId ?? "");
const [invoiceId, setInvoiceId] = useState(entry.invoiceId ?? "");
const [rate, setRate] = useState(entry.rate ?? 0);
const [startedAt, setStartedAt] = useState(() => toDatetimeLocalValue(entry.startedAt));
const [startedAt, setStartedAt] = useState(() =>
toLocalDateTimeInputValue(new Date(entry.startedAt)),
);
const [endedAt, setEndedAt] = useState(() =>
entry.endedAt ? toDatetimeLocalValue(entry.endedAt) : "",
entry.endedAt ? toLocalDateTimeInputValue(new Date(entry.endedAt)) : "",
);
const { data: billableInvoices } = api.invoices.getBillable.useQuery(
@@ -70,7 +67,8 @@ function TimeEntryEditForm({
if (!startedAt || !endedAt) return null;
const start = new Date(startedAt);
const end = new Date(endedAt);
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null;
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()))
return null;
return Math.max(0, (end.getTime() - start.getTime()) / 3_600_000);
}, [endedAt, startedAt]);
@@ -228,7 +226,11 @@ function TimeEntryEditForm({
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="button" onClick={handleSave} disabled={updateEntry.isPending}>
<Button
type="button"
onClick={handleSave}
disabled={updateEntry.isPending}
>
Save
</Button>
</div>
@@ -246,7 +248,9 @@ export function TimeEntryEditDialog({
{ id: entryId ?? "" },
{ enabled: Boolean(entryId) && open },
);
const { data: clients = [] } = api.clients.getAll.useQuery(undefined, { enabled: open });
const { data: clients = [] } = api.clients.getAll.useQuery(undefined, {
enabled: open,
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>