diff --git a/app/(dashboard)/invoices/[id]/page.tsx b/app/(dashboard)/invoices/[id]/page.tsx index a66cd1b2..a66a49b7 100644 --- a/app/(dashboard)/invoices/[id]/page.tsx +++ b/app/(dashboard)/invoices/[id]/page.tsx @@ -536,12 +536,12 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
-

{isSelfBilled ? invoiceDisplayNumber(invoice as Invoice) : (invoice.invoice_number ?? '—')}

+

{isSelfBilled ? invoiceDisplayNumber(invoice as Invoice) : (invoice.invoice_number ?? '—')}

{isProforma && ( - {t('badge_proforma')} + {t('badge_proforma')} )} {isDeliveryNote && ( - {t('badge_delivery_note')} + {t('badge_delivery_note')} )} {isSelfBilled && ( {t('badge_self_billed')} @@ -1123,8 +1123,8 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st ) : ( <> - - {t('reminder_objection')} + + {t('reminder_objection')} )}
@@ -1190,9 +1190,9 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st {/* Converted from proforma */} {convertedFromInvoice && ( - + - + {t('converted_card_title')} @@ -1217,66 +1217,41 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st {t('actions_card_title')} + {/* Secondary actions only. The primary next-step for every status + (convert / finalize / send / mark-paid) lives in the header + action row next to the status badge — this card holds the + reversible/destructive alternatives so there is one obvious + next step, not two competing copies of it. */} {isProforma && ( - <> - - - + )} {!isProforma && invoice.status === 'draft' && ( isUnnumberedDraft ? ( - <> - {/* Unnumbered draft (saved via "Spara som utkast"): review - and create it, or remove it without a trace. */} - - - + ) : ( <> - {!isDeliveryNote && customerHasEmail ? ( + {/* When the customer has an email the header offers "Send via + email" as the primary; keep the manual-mark-sent path here + as the secondary alternative (it is not in the header). */} + {!isDeliveryNote && customerHasEmail && ( <> - -

- {t('send_manual_hint_no_email')} -

- )} - - - - - )} - {invoice.status === 'paid' && isRealInvoice && ( + {((invoice.status === 'sent' || invoice.status === 'overdue' || invoice.status === 'paid') && isRealInvoice) && (
-

+

{t('arrival_header', { number: invoice.arrival_number })}

@@ -605,29 +605,29 @@ export default function SupplierInvoiceDetailPage() {
{t('net_excl_vat')} - {formatAmount(invoice.subtotal)} {invoice.currency} + {formatAmount(invoice.subtotal)} {invoice.currency}
{t('vat_label')} - {formatAmount(invoice.vat_amount)} {invoice.currency} + {formatAmount(invoice.vat_amount)} {invoice.currency}
{rounding.applies && (
{t('ore_rounding')} - {formatAmount(rounding.roundingDelta)} {invoice.currency} + {formatAmount(rounding.roundingDelta)} {invoice.currency}
)} -
+
{t('total_label')} - {formatAmount(rounding.displayed)} {invoice.currency} + {formatAmount(rounding.displayed)} {invoice.currency}
{t('paid_label')} - {formatAmount(invoice.paid_amount)} {invoice.currency} + {formatAmount(invoice.paid_amount)} {invoice.currency}
{t('remaining_label')} - {formatAmount(invoice.remaining_amount)} {invoice.currency} + {formatAmount(invoice.remaining_amount)} {invoice.currency}
@@ -692,11 +692,11 @@ export default function SupplierInvoiceDetailPage() { {item.quantity} {item.unit} - {formatAmount(item.unit_price)} + {formatAmount(item.unit_price)} {Math.round(item.vat_rate * 100)}% - {formatAmount(item.line_total)} - {formatAmount(item.vat_amount)} + {formatAmount(item.line_total)} + {formatAmount(item.vat_amount)} ))} @@ -721,11 +721,11 @@ export default function SupplierInvoiceDetailPage() { )}
{item.quantity} {item.unit} × {formatAmount(item.unit_price)} - {formatAmount(item.line_total)} kr + {formatAmount(item.line_total)} kr
· {t('vat_inline', { rate: Math.round(item.vat_rate * 100) })} - {t('vat_amount_inline', { amount: formatAmount(item.vat_amount) })} + {t('vat_amount_inline', { amount: formatAmount(item.vat_amount) })}
))} @@ -755,7 +755,7 @@ export default function SupplierInvoiceDetailPage() { {payments.map((p) => ( {formatDate(p.payment_date)} - {formatAmount(p.amount)} {p.currency} + {formatAmount(p.amount)} {p.currency} {p.journal_entry_id ? ( diff --git a/app/(dashboard)/supplier-invoices/new/page.tsx b/app/(dashboard)/supplier-invoices/new/page.tsx index 8f556c42..300d954f 100644 --- a/app/(dashboard)/supplier-invoices/new/page.tsx +++ b/app/(dashboard)/supplier-invoices/new/page.tsx @@ -1246,7 +1246,7 @@ export default function NewSupplierInvoicePage() {
-

{t('page_title')}

+

{t('page_title')}

@@ -1745,17 +1745,17 @@ export default function NewSupplierInvoicePage() {
{t('ai_totals_label')} {extractedData.totals.subtotal != null && ( - + {t('ai_net', { amount: formatAmount(extractedData.totals.subtotal) })} )} {extractedData.totals.vatAmount != null && ( - + {t('ai_vat', { amount: formatAmount(extractedData.totals.vatAmount) })} )} {extractedData.totals.total != null && ( - + {t('ai_total', { amount: formatAmount(extractedData.totals.total) })} )} @@ -1766,17 +1766,17 @@ export default function NewSupplierInvoicePage() {
{t('net_excl_vat')} - {formatCurrency(subtotal, watchedCurrency)} + {formatCurrency(subtotal, watchedCurrency)}
{watchedReverseCharge ? t('vat_reverse_charge') : t('vat_label_short')} - {formatCurrency(totalVat, watchedCurrency)} + {formatCurrency(totalVat, watchedCurrency)}
{t('total_label')} - {formatCurrency(total, watchedCurrency)} + {formatCurrency(total, watchedCurrency)}
{/* Öresavrundning — display-only rounding of the displayed total to whole kronor (SEK only). The registered amount and the booked diff --git a/app/(dashboard)/transactions/page.tsx b/app/(dashboard)/transactions/page.tsx index 13c58c01..415b4cbd 100644 --- a/app/(dashboard)/transactions/page.tsx +++ b/app/(dashboard)/transactions/page.tsx @@ -13,6 +13,7 @@ import { ToastAction } from '@/components/ui/toast' import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog' import { DataList, DataListHeader, DataListEmpty } from '@/components/ui/data-list' import { Input } from '@/components/ui/input' +import { Skeleton } from '@/components/ui/skeleton' import { DropdownMenu, DropdownMenuTrigger, @@ -1932,13 +1933,13 @@ export default function TransactionsPage() { {isLoading ? ( {[1, 2, 3].map((i) => ( -
-
+
+
-
-
+ +
-
+
))} @@ -2047,7 +2048,7 @@ export default function TransactionsPage() { {/* Batch mode floating action bar */} {isBatchMode && selectedIds.size > 0 && ( -
+
{batchProgress ? ( <> diff --git a/components/dashboard/AttGoraSection.tsx b/components/dashboard/AttGoraSection.tsx index 33e0d27b..a66ed540 100644 --- a/components/dashboard/AttGoraSection.tsx +++ b/components/dashboard/AttGoraSection.tsx @@ -205,7 +205,7 @@ export default function AttGoraSection({ {bokforRows && (
{t('band_bokfor')} -
+
{counts.book_transaction > 0 && ( {t('band_granska')} -
+
{counts.supplier_invoice_approval > 0 && ( {t('band_bevaka')} -
+
{counts.overdue_invoice > 0 && ( `erp_setup_fresh_start:${companyId}` @@ -115,8 +114,6 @@ export default function DashboardContent({ companyId, summary, worklist, suggest // header so the tile and the section never disagree. const todoCount = worklist.total + (summary.expiringBankConnections?.length ?? 0) - const slim = getBranding().navDensity === 'slim' - // Pick the single most-urgent next action so the launchpad surfaces one // unambiguous CTA. Order matches the friction we actually want to remove // first: stale → overdue → uncategorized → unpaid → all clear. @@ -202,8 +199,9 @@ export default function DashboardContent({ companyId, summary, worklist, suggest - ) : slim ? ( - /* Next best action — single hero card */ + ) : ( + /* Next best action — single hero card, shown for every agent-built + company regardless of nav density so there is always one clear CTA */
- ) : null} + )} {/* Key metrics — 4 compact cards */}
@@ -242,7 +240,7 @@ export default function DashboardContent({ companyId, summary, worklist, suggest

{t('result')}

= 0 ? 'text-success' : 'text-destructive' )}> {formatLargeNumber(summary.mtd.net)} @@ -261,7 +259,7 @@ export default function DashboardContent({ companyId, summary, worklist, suggest

{t('to_be_paid')}

-

+

{summary.unpaidInvoicesCount} {t('units') && {t('units')}}

@@ -276,7 +274,7 @@ export default function DashboardContent({ companyId, summary, worklist, suggest

{t('bank_balance')}

-

+

{formatLargeNumber(summary.bankBalance)} kr

@@ -301,7 +299,7 @@ export default function DashboardContent({ companyId, summary, worklist, suggest

{t('todo')}

{todoCount > 0 ? ( -

+

{todoCount} {t('units') && {t('units')}}

@@ -332,12 +330,12 @@ export default function DashboardContent({ companyId, summary, worklist, suggest

{t('revenue')}

-

+

{formatLargeNumber(summary.mtd.income)} kr

{t('this_month')}

-
+

{t('this_year_block')}

{formatCurrency(summary.ytd.income)}

@@ -347,12 +345,12 @@ export default function DashboardContent({ companyId, summary, worklist, suggest

{t('expenses')}

-

+

{formatLargeNumber(summary.mtd.expenses)} kr

{t('this_month')}

-
+

{t('this_year_block')}

{formatCurrency(summary.ytd.expenses)}

diff --git a/components/deadlines/TaxTodoWidget.tsx b/components/deadlines/TaxTodoWidget.tsx index 8efc0250..4e8a71ea 100644 --- a/components/deadlines/TaxTodoWidget.tsx +++ b/components/deadlines/TaxTodoWidget.tsx @@ -112,7 +112,7 @@ export function TaxTodoWidget({ deadlines, onStatusChange }: TaxTodoWidgetProps) } return ( - +
@@ -134,18 +134,11 @@ export function TaxTodoWidget({ deadlines, onStatusChange }: TaxTodoWidgetProps) const isUpdating = updatingId === deadline.id const reportLink = getReportLink(deadline) const isOverdue = deadline.status === 'overdue' - const isActionNeeded = deadline.status === 'action_needed' return (
diff --git a/components/deadlines/UpcomingDeadlinesWidget.tsx b/components/deadlines/UpcomingDeadlinesWidget.tsx index 58aaf9bd..369c2ea3 100644 --- a/components/deadlines/UpcomingDeadlinesWidget.tsx +++ b/components/deadlines/UpcomingDeadlinesWidget.tsx @@ -129,11 +129,7 @@ export function UpcomingDeadlinesWidget({ deadlines, maxItems = 5, onStatusChang
diff --git a/components/extensions/general/InvoiceInboxWorkspace.tsx b/components/extensions/general/InvoiceInboxWorkspace.tsx index aebca843..ddb613b2 100644 --- a/components/extensions/general/InvoiceInboxWorkspace.tsx +++ b/components/extensions/general/InvoiceInboxWorkspace.tsx @@ -6,6 +6,13 @@ import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { Skeleton } from '@/components/ui/skeleton' +import AiFilledIndicator from '@/components/ui/ai-filled-indicator' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' import { useToast } from '@/components/ui/use-toast' import { Inbox, @@ -24,6 +31,7 @@ import { Search, Circle, X, + ChevronDown, } from 'lucide-react' import Link from 'next/link' import { cn, formatCurrency } from '@/lib/utils' @@ -664,7 +672,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) { }} onDrop={handleDrop} > -
+
{/* Top bar */}
@@ -1084,10 +1092,10 @@ function InboxRow({ )} {isLinkedToTransaction && ( - + )} {isBooked && ( - + )}
@@ -1144,7 +1152,7 @@ export function DocumentPreview({
{docMime?.startsWith('image/') ? ( // Image: frame hugs the image, capped at the parent's visible box. -
+
{/* eslint-disable-next-line @next/next/no-img-element */} ) : ( // PDF: iframe needs explicit height — frame fills the available pane. -
+