wrap the document, record and send-mail editors in real forms

typing into a field and pressing enter did nothing in the document editor, the
customer/item/payment/expense dialogs and the send-document dialog — inputs sat
in a div, so the button was the only way to submit. now each wraps in a
<form onSubmit>, with every non-submit button (cancel, close, add/remove line,
"same as billing") marked type="button" so it doesn't submit instead of doing
its own thing.

the record editors share one dialog shell (EditorShell), so a single form
there covers customer, item, payment and expense at once — kept the dialog's
grid gap intact with display:contents rather than nesting a box.

left the list-view filter boxes and the reports date-range picker alone: none
of them has a submit action, so there's nothing for enter to do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 10:43:59 +00:00
co-authored by Claude Opus 5
parent 464acee34e
commit 06203f97b3
3 changed files with 85 additions and 65 deletions
+52 -45
View File
@@ -85,62 +85,69 @@ export const SendDocumentDialog = ({
const missingRecipient = !to.trim();
const submit = () => {
const submit = (ev?: React.FormEvent) => {
ev?.preventDefault();
action.mutate({ id: target.id, action: 'send', payload: { subject, body, from, to } }, { onSuccess: onClose });
};
return (
<Dialog open onOpenChange={(open) => !open && onClose()}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>
{target.resend ? 'Resend' : 'Send'} {kind} {target.number}
</DialogTitle>
<DialogDescription>
This emails {target.customerName} through InvoiceShelf. It cannot be undone.
</DialogDescription>
</DialogHeader>
{/* `contents` keeps the form invisible to DialogContent's grid layout — it still owns the gap between
header, body and footer, the form just carries Enter-to-submit across all three. */}
<form onSubmit={submit} className="contents">
<DialogHeader>
<DialogTitle>
{target.resend ? 'Resend' : 'Send'} {kind} {target.number}
</DialogTitle>
<DialogDescription>
This emails {target.customerName} through InvoiceShelf. It cannot be undone.
</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs">To</Label>
<Input value={to} onChange={(e) => setTo(e.target.value)} className="h-8 text-xs" type="email" />
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs">To</Label>
<Input value={to} onChange={(e) => setTo(e.target.value)} className="h-8 text-xs" type="email" />
</div>
<div className="space-y-1">
<Label className="text-xs">From</Label>
<Input value={from} onChange={(e) => setFrom(e.target.value)} className="h-8 text-xs" type="email" />
</div>
</div>
<div className="space-y-1">
<Label className="text-xs">From</Label>
<Input value={from} onChange={(e) => setFrom(e.target.value)} className="h-8 text-xs" type="email" />
<Label className="text-xs">Subject</Label>
<Input value={subject} onChange={(e) => setSubject(e.target.value)} className="h-8 text-xs" />
</div>
</div>
<div className="space-y-1">
<Label className="text-xs">Subject</Label>
<Input value={subject} onChange={(e) => setSubject(e.target.value)} className="h-8 text-xs" />
</div>
<div className="space-y-1">
<Label className="text-xs">Message</Label>
<Textarea value={body} onChange={(e) => setBody(e.target.value)} rows={7} className="text-xs" />
<p className="text-[10px] text-muted-foreground">
HTML. InvoiceShelf attaches the PDF itself — do not link it here.
</p>
</div>
{missingRecipient && (
<p className="text-[10px] text-warning">This customer has no email address on file. Enter one to send.</p>
)}
</div>
<DialogFooter>
<Button variant="ghost" size="sm" onClick={onClose} disabled={action.isPending}>
Cancel
</Button>
<Button size="sm" onClick={submit} disabled={action.isPending || missingRecipient}>
{action.isPending ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Send className="mr-1.5 h-3.5 w-3.5" />
<div className="space-y-1">
<Label className="text-xs">Message</Label>
<Textarea value={body} onChange={(e) => setBody(e.target.value)} rows={7} className="text-xs" />
<p className="text-[10px] text-muted-foreground">
HTML. InvoiceShelf attaches the PDF itself — do not link it here.
</p>
</div>
{missingRecipient && (
<p className="text-[10px] text-warning">
This customer has no email address on file. Enter one to send.
</p>
)}
Send to {to || '—'}
</Button>
</DialogFooter>
</div>
<DialogFooter>
<Button variant="ghost" size="sm" type="button" onClick={onClose} disabled={action.isPending}>
Cancel
</Button>
<Button type="submit" size="sm" disabled={action.isPending || missingRecipient}>
{action.isPending ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Send className="mr-1.5 h-3.5 w-3.5" />
)}
Send to {to || '—'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);