{new Date(data.senastUppdaterad).toLocaleString('sv-SE')}
@@ -428,6 +442,17 @@ function BalanceHero({
+ {saldo.lastSyncedAt && (
+
+ Senast synkad{' '}
+
+ {new Date(saldo.lastSyncedAt).toLocaleString('sv-SE')}
+
+ . Skatteverket uppdaterar saldot periodvis — datumet ovan ändras
+ inte varje gång du synkroniserar.
+
+ )}
+
{data.informationstext.length > 0 && (
diff --git a/app/(dashboard)/supplier-invoices/new/page.tsx b/app/(dashboard)/supplier-invoices/new/page.tsx
index 62432c6b..cd48a6b8 100644
--- a/app/(dashboard)/supplier-invoices/new/page.tsx
+++ b/app/(dashboard)/supplier-invoices/new/page.tsx
@@ -168,6 +168,7 @@ export default function NewSupplierInvoicePage() {
const watchedSupplierId = watch('supplier_id')
const watchedCurrency = watch('currency')
const watchedPaidPrivately = watch('paid_with_private_funds')
+ const watchedReverseCharge = watch('reverse_charge')
const isEF = entityType === 'enskild_firma'
@@ -401,7 +402,11 @@ export default function NewSupplierInvoicePage() {
})
const subtotal = itemTotals.reduce((sum, t) => sum + t.lineTotal, 0)
const totalVat = itemTotals.reduce((sum, t) => sum + t.vatAmount, 0)
- const total = Math.round((subtotal + totalVat) * 100) / 100
+ // Reverse charge: supplier never invoices VAT, so it doesn't roll into the
+ // payable total. The VAT is still accounted for via 2614 / 2645 in
+ // bookkeeping — the line stays in the breakdown for transparency.
+ const payableVat = watchedReverseCharge ? 0 : totalVat
+ const total = Math.round((subtotal + payableVat) * 100) / 100
// Show the AI-suggested supplier card when we have an inbox item, the AI
// surfaced a supplier name, and we couldn't match it to an existing record.
@@ -1251,7 +1256,9 @@ export default function NewSupplierInvoicePage() {
{formatCurrency(subtotal, watchedCurrency)}
+
)
}
diff --git a/components/extensions/general/InvoiceInboxWorkspace.tsx b/components/extensions/general/InvoiceInboxWorkspace.tsx
index 0c67c878..63d18279 100644
--- a/components/extensions/general/InvoiceInboxWorkspace.tsx
+++ b/components/extensions/general/InvoiceInboxWorkspace.tsx
@@ -7,7 +7,6 @@ import { Checkbox } from '@/components/ui/checkbox'
import { Input } from '@/components/ui/input'
import { Skeleton } from '@/components/ui/skeleton'
import { useToast } from '@/components/ui/use-toast'
-import { ToastAction } from '@/components/ui/toast'
import {
Inbox,
Upload,
@@ -27,17 +26,9 @@ import {
X,
} from 'lucide-react'
import Link from 'next/link'
-import { useRouter } from 'next/navigation'
import { cn, formatCurrency } from '@/lib/utils'
import type { WorkspaceComponentProps } from '@/lib/extensions/workspace-registry'
import type { InvoiceExtractionResult } from '@/types'
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogDescription,
-} from '@/components/ui/dialog'
import BookDirectlyDialog from '@/components/extensions/general/BookDirectlyDialog'
type AccountingMethod = 'accrual' | 'cash'
@@ -98,15 +89,51 @@ function pickSupplierName(item: InboxItem): string | null {
}
// ── Skeleton ─────────────────────────────────────────────────
+// Mirrors the live layout (top bar + 3-pane card) so the transition from
+// the route-level loading.tsx to data-loaded content has no visible reflow.
+// Keep in sync with app/(dashboard)/e/[sector]/[slug]/loading.tsx.
function WorkspaceSkeleton() {
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
@@ -116,16 +143,11 @@ function WorkspaceSkeleton() {
export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
const { toast } = useToast()
- const router = useRouter()
const fileInputRef = useRef(null)
const [items, setItems] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [selectedId, setSelectedId] = useState(null)
- // Phone-only master-detail toggle. On screens ('list')
// List filter + search (client-side over the already-fetched items list).
const [filter, setFilter] = useState<'all' | 'needs_action' | 'done' | 'error'>('all')
const [searchTerm, setSearchTerm] = useState('')
@@ -150,7 +172,6 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
const [isDeleting, setIsDeleting] = useState(false)
const [isRotating, setIsRotating] = useState(false)
const [isDragging, setIsDragging] = useState(false)
- const [attachOpen, setAttachOpen] = useState(false)
const [bookDirectOpen, setBookDirectOpen] = 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
@@ -272,7 +293,10 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
setSelected(null)
setDocUrl(null)
setDocMime(null)
- setMobileView('detail')
+ // Intentionally no auto-scroll: in the vertical-stack layout (below xl)
+ // scrolling the preview into view pushes the list off-screen, and the
+ // user has no obvious way back to pick another item. The row-highlight
+ // + the preview content update are enough feedback that the tap took.
try {
const res = await fetch(`/api/extensions/ext/invoice-inbox/items/${id}`)
@@ -523,7 +547,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
return (
{ e.preventDefault(); if (!isDragging) setIsDragging(true) }}
onDragLeave={(e) => {
// only clear when leaving the workspace itself, not children
@@ -531,7 +555,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
}}
onDrop={handleDrop}
>
-
+
{/* Top bar */}
@@ -611,16 +635,14 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
- {/* Three-pane body. On phone (
- {/* List */}
-