feat(invoices): create customer & supplier invoices in modals, matching the verifikat pattern (#861)
Invoice and supplier-invoice creation now open as pop-up dialogs on their list pages instead of navigating to standalone form pages — the same UX as NewJournalEntryDialog (capped-height scroll, explicit-close-only so a half-typed invoice survives stray Escape/backdrop clicks). - InvoiceEditor gains a `bare` variant (page chrome stripped, inline actions replacing the fixed mobile bar, live document-type title kept) hosted by the new NewInvoiceDialog (sm:max-w-5xl). Draft editing pages unchanged. - The 2,057-line supplier form moves out of the route page into components/supplier-invoices/NewSupplierInvoiceForm.tsx with bare/inboxItemId/onCreated/onCancel props, hosted by NewSupplierInvoiceDialog (sm:max-w-4xl). - Modals are URL-driven (?new=1): header buttons, empty states, command palette, and the reports CTA all open the same dialog; browser back closes it. /invoices/new and /supplier-invoices/new survive as redirects (bookmarks, agent intents, /expenses/new alias, inbox deep links). - The invoice-inbox "Skapa leverantörsfaktura" action opens the modal in place and refreshes the inbox on success instead of navigating away. No new i18n keys; dialog titles reuse existing strings. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
11126d6d56
commit
86071334cb
@@ -42,6 +42,7 @@ import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
import type { WorkspaceComponentProps } from '@/lib/extensions/workspace-registry'
|
||||
import type { InvoiceExtractionResult } from '@/types'
|
||||
import BookDirectlyDialog from '@/components/extensions/general/BookDirectlyDialog'
|
||||
import NewSupplierInvoiceDialog from '@/components/supplier-invoices/NewSupplierInvoiceDialog'
|
||||
import BulkBookInboxDialog from '@/components/extensions/general/BulkBookInboxDialog'
|
||||
import TransactionMatchPicker from '@/components/inbox/TransactionMatchPicker'
|
||||
import { useAgentSheet } from '@/components/agent/AgentSheetProvider'
|
||||
@@ -240,6 +241,10 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
// Match-to-bank-transaction picker (opens when user clicks "Matcha mot
|
||||
// transaktion" on an unmatched inbox item).
|
||||
const [matchPickerOpen, setMatchPickerOpen] = useState(false)
|
||||
// "Skapa leverantörsfaktura" modal for the selected underlag — opens in
|
||||
// place (instead of navigating to a form page) so the user lands right back
|
||||
// here to pick the next document.
|
||||
const [createSupplierInvoiceOpen, setCreateSupplierInvoiceOpen] = useState(false)
|
||||
// Cash method users see "Bokför direkt" as the primary CTA; accrual users
|
||||
// see "Skapa leverantörsfaktura". Defaults to 'accrual' until we've read
|
||||
// the company settings so we don't flicker the CTA order on first paint.
|
||||
@@ -1002,6 +1007,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
accountingMethod={accountingMethod}
|
||||
onDelete={() => handleDelete(selected.id)}
|
||||
onBookDirect={() => setBookDirectOpen(true)}
|
||||
onCreateSupplierInvoice={() => setCreateSupplierInvoiceOpen(true)}
|
||||
onMatchTransaction={() => setMatchPickerOpen(true)}
|
||||
onUnmatchTransaction={async () => {
|
||||
const targetId = selected.id
|
||||
@@ -1073,6 +1079,19 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{selected && (
|
||||
<NewSupplierInvoiceDialog
|
||||
open={createSupplierInvoiceOpen}
|
||||
onOpenChange={setCreateSupplierInvoiceOpen}
|
||||
inboxItemId={selected.id}
|
||||
onCreated={async () => {
|
||||
// Stay in the inbox (the whole point of the modal): close, then
|
||||
// refresh the list + the selected item so it shows as converted.
|
||||
setCreateSupplierInvoiceOpen(false)
|
||||
await Promise.all([fetchItems(), handleSelect(selected.id)])
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<BulkBookInboxDialog
|
||||
open={bulkBookOpen}
|
||||
onOpenChange={setBulkBookOpen}
|
||||
@@ -1492,6 +1511,7 @@ function FieldsRail({
|
||||
accountingMethod,
|
||||
onDelete,
|
||||
onBookDirect,
|
||||
onCreateSupplierInvoice,
|
||||
onMatchTransaction,
|
||||
onUnmatchTransaction,
|
||||
onAskAssistant,
|
||||
@@ -1503,6 +1523,7 @@ function FieldsRail({
|
||||
accountingMethod: AccountingMethod
|
||||
onDelete: () => void
|
||||
onBookDirect: () => void
|
||||
onCreateSupplierInvoice: () => void
|
||||
onMatchTransaction: () => void
|
||||
onUnmatchTransaction: () => Promise<void>
|
||||
onAskAssistant?: (transactionId: string) => void
|
||||
@@ -1766,16 +1787,14 @@ function FieldsRail({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-64">
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
href={`/supplier-invoices/new?inbox_item_id=${item.id}`}
|
||||
className="flex flex-col items-start gap-1"
|
||||
>
|
||||
<span>Skapa leverantörsfaktura</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
För leverantörsskulder du vill följa (periodisering).
|
||||
</span>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={onCreateSupplierInvoice}
|
||||
className="flex flex-col items-start gap-1"
|
||||
>
|
||||
<span>Skapa leverantörsfaktura</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
För leverantörsskulder du vill följa (periodisering).
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onBookDirect}
|
||||
|
||||
Reference in New Issue
Block a user