close the last three navigate-only menu items into links
This commit is contained in:
@@ -2,7 +2,7 @@ import type { Column } from './components';
|
||||
import type { ConfirmState } from './ConfirmDialog';
|
||||
import type { Address, Customer } from './shared';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import { Filter, MoreHorizontal, Plus, Trash2, Users, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { EmptyState, ErrorState, Field, Pagination, SearchBox, SectionShell, SimpleTable } from './components';
|
||||
import { ConfirmDialog } from './ConfirmDialog';
|
||||
import { formatDate, formatMoney } from './format';
|
||||
import { PAGE_SIZE } from './shared';
|
||||
import { invoicesCustomerPath, PAGE_SIZE } from './shared';
|
||||
import { useListFilters } from './useInvoicesSection';
|
||||
import { useCurrency, useCustomerStats, useResourceList, useResourceMutations } from './useInvoiceShelfData';
|
||||
|
||||
@@ -30,7 +30,6 @@ type Props = { onEdit: (id: number | 'new') => void };
|
||||
export const CustomersListView = ({ onEdit }: Props) => {
|
||||
const { filters, patch, select, setPage, clearFilters } = useListFilters();
|
||||
const currency = useCurrency();
|
||||
const navigate = useNavigate();
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [selection, setSelection] = useState<Set<number>>(new Set());
|
||||
const [confirm, setConfirm] = useState<ConfirmState>(null);
|
||||
@@ -120,11 +119,14 @@ export const CustomersListView = ({ onEdit }: Props) => {
|
||||
<DropdownMenuItem onSelect={() => onEdit(row.id)}>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => select(row.id)}>View</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={() => navigate(`/invoices/invoices?customer=${row.id}`)}>
|
||||
Their invoices
|
||||
{/* Both go somewhere and change nothing, so they are anchors wearing a menu item — `asChild` hands
|
||||
Radix's item semantics to the `<Link>` rather than nesting one inside the other. Middle-click
|
||||
opens the filtered list in a tab, which is the point of the filter living in the URL. */}
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to={invoicesCustomerPath('invoices', row.id)}>Their invoices</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => navigate(`/invoices/payments?customer=${row.id}`)}>
|
||||
Their payments
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to={invoicesCustomerPath('payments', row.id)}>Their payments</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ConfirmState } from './ConfirmDialog';
|
||||
import type { SendTarget } from './SendDocumentDialog';
|
||||
import type { Invoice } from './shared';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Copy, FileText, Filter, MoreHorizontal, Plus, Send, Trash2, Wallet } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -50,9 +51,9 @@ import {
|
||||
// `allow_edit` is honoured rather than ignored: once an invoice has payments against it, upstream locks
|
||||
// editing, and letting a user open a form whose save will be rejected is worse than hiding the button.
|
||||
|
||||
type Props = { onEdit: (id: number | 'new') => void; onRecordPayment: (invoiceId: number) => void };
|
||||
type Props = { onEdit: (id: number | 'new') => void; recordPaymentHref: (invoiceId: number) => string };
|
||||
|
||||
export const InvoicesListView = ({ onEdit, onRecordPayment }: Props) => {
|
||||
export const InvoicesListView = ({ onEdit, recordPaymentHref }: Props) => {
|
||||
const { filters, patch, select, setPage, clearFilters } = useListFilters();
|
||||
const currency = useCurrency();
|
||||
const { customers } = useLookups();
|
||||
@@ -189,10 +190,15 @@ export const InvoicesListView = ({ onEdit, onRecordPayment }: Props) => {
|
||||
Resend invoice
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{/* Opening the payment editor is an address (`?edit=new&pay=<id>`), not a side effect — the editor
|
||||
prefills itself from those params — so it is an anchor, not a callback up to the parent and
|
||||
back down through `navigate`. */}
|
||||
{(row.status === 'SENT' || row.status === 'VIEWED') && (
|
||||
<DropdownMenuItem onSelect={() => onRecordPayment(row.id)}>
|
||||
<Wallet className="mr-2 h-3.5 w-3.5" />
|
||||
Record payment
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to={recordPaymentHref(row.id)}>
|
||||
<Wallet className="mr-2 h-3.5 w-3.5" />
|
||||
Record payment
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{row.status === 'DRAFT' && (
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PaymentsListView } from './PaymentsListView';
|
||||
import { RecurringListView } from './RecurringListView';
|
||||
import { ReportsView } from './ReportsView';
|
||||
import { CustomerEditor, ExpenseEditor, ItemEditor, PaymentEditor } from './RecordEditors';
|
||||
import { invoicesSectionPath } from './shared';
|
||||
import { invoicesNewPaymentPath, invoicesSectionPath } from './shared';
|
||||
import { useInvoiceShelfHealth } from './useInvoiceShelfData';
|
||||
import { useInvoicesSection } from './useInvoicesSection';
|
||||
|
||||
@@ -100,12 +100,7 @@ export const InvoicesView = () => {
|
||||
|
||||
switch (section) {
|
||||
case 'invoices':
|
||||
return (
|
||||
<InvoicesListView
|
||||
onEdit={openEditor}
|
||||
onRecordPayment={(invoiceId) => navigate(`/invoices/payments?edit=new&pay=${invoiceId}`)}
|
||||
/>
|
||||
);
|
||||
return <InvoicesListView onEdit={openEditor} recordPaymentHref={invoicesNewPaymentPath} />;
|
||||
|
||||
case 'estimates':
|
||||
return (
|
||||
|
||||
@@ -42,6 +42,18 @@ export const invoicesSectionPath = (id: InvoicesSectionId) => `/invoices/${id}`;
|
||||
export const invoicesRecordPath = (section: InvoicesSectionId, id: number) =>
|
||||
`${invoicesSectionPath(section)}?selected=${id}`;
|
||||
|
||||
/** A list section filtered to one customer — `useListFilters` reads `customer`. */
|
||||
export const invoicesCustomerPath = (section: InvoicesSectionId, customerId: number) =>
|
||||
`${invoicesSectionPath(section)}?customer=${customerId}`;
|
||||
|
||||
/**
|
||||
* The payment editor, opened blank against an invoice — `/invoices/payments?edit=new&pay=12`.
|
||||
* `InvoicesView` reads both params (`edit` → which editor, `pay` → which invoice it prefills from), so
|
||||
* "record a payment for this invoice" is a whole address and not a callback.
|
||||
*/
|
||||
export const invoicesNewPaymentPath = (invoiceId: number) =>
|
||||
`${invoicesSectionPath('payments')}?edit=new&pay=${invoiceId}`;
|
||||
|
||||
/** The sidecar resource slug behind each list section. `dashboard` and `reports` have none. */
|
||||
export const SECTION_RESOURCE = {
|
||||
invoices: 'invoices',
|
||||
|
||||
Reference in New Issue
Block a user