* feat(transactions): concept shell for scene 10 (header, toolbar, footer)
- Header: title + Importera SplitButton (import guide / manual entry,
last-used mode persisted via ui_state.create_mode.transactions),
replacing the three-button row
- Toolbar in concept order: [Att bokföra/Alla seg with count chip]
[search] [Välj flera as quiet toggle] ... [source ContextPicker far
right merging the old in-list source dropdown (bank/Skatteverket)]
- Bank sync status + sync-now + Bankavstämning move to the concept's
footer status line together with the honest visible-row counter
("n att hantera")
- skv-reconnect banner and all dialogs/actions untouched
Row cards -> concept dry-table conversion follows separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): concept row language for the inbox cards
Rows read like the scene 10 table: date as the first tabular column,
description on one line, amount right (+income in sage), auto-detected
invoice matches as beige suggestion chips instead of primary pills,
tighter hairline rows at 13px. Every action unchanged: Bokför pill,
match/assistant/overflow menu, batch checkboxes, exit animations,
extraction status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): de-bloat rows and chrome to match the concept
- Rows carry only the essentials: date, description, amount, Bokför and
one overflow menu. The inline match and ask-assistant icon buttons
fold into the overflow menu (all actions still one click away)
- The "n nya banktransaktioner sen ditt senaste besök" banner is removed;
the footer sync line covers it
- The Skatteverket reconnect banner becomes the one-ochre-sentence
AttnLine with an inline action (convention 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(ui): extract dry-table primitives into components/ui/dry-table
The concept table styles (TH/TD/VTH/VTD classes, quiet links, RowFoldout)
move out of JournalEntryList so the transaction inbox can render the exact
same table language.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): Synka bank nu in the Importera menu
Extracts the per-connection sync/reconnect logic into a shared useBankSync()
hook (BankSyncNowButton keeps using it) and adds the concept's first menu row
to the Importera split button: sync all active connections, with the
last-synced age as the row description. Hidden without a connection or the
bank_sync capability; the footer button stays the gated conversion surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): per-account source picker with balances
The context chip becomes the concept's account chooser: always visible in
inbox mode, one row per enabled cash account (PSD2 balance annotation),
an Övriga bucket for bank rows without a registered account, and a
Skattekonto row annotated with the cached saldo. The trigger reads
'Alla källor · <sum>' where the sum covers SEK ledgers + skattekonto.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): concept dry-table for the inbox
The inbox list becomes the exact Bokforing table: borderless dry-table with
hover-revealed checkboxes and chevrons, one-line rows (date, description,
amount, quiet primary pill + overflow + foldout), and a RowFoldout expansion
carrying the row detail and the full action set as quiet links. Skattekonto
rows render as chip-marked table rows with inline actions. The 'Valj flera'
toggle and the floating batch bar are replaced by the concept bulkbar that
pops in above the table once a row is selected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(i18n): singular form for bulkbar selection count
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): suggestion-first Bokfor flow (concept scene 10)
Bokfor on a row with a suggestion now goes straight to the compact
kontering confirm (QuickReview) showing the proposed verifikat; the full
template picker becomes the fallback and the 'Byt mall' path. Rows carry
the concept's 'Forslag: <mall>' chip (with D/K in the tooltip and the
foldout meta). The picker dialog is de-bloated: alternate paths (manuellt,
matcha, ignorera) collapse into quiet links. Modal scroll containers
(DialogContent, SlideOverBody) keep a visible scrollbar thumb: with the
app-wide auto-hide, a long dialog read as cut off at the fold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): drop suggestion UI and foldout action links
Founder direction: no booking suggestions in the list for now (logic comes
later), and the foldout duplicated the overflow menu: the three dots are
the clearer surface. The foldout keeps row detail only (FX conversion,
original bank name, 1930/1630 hint, extraction status) and rows without
any detail no longer expand. The Skattekonto picker row drops its saldo
annotation; the Alla kallor sum covers bank ledgers only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): dry-table for the Alla view + pill chips app-wide
The history list joins the inbox's table language: Datum, Beskrivning,
Belopp, Status columns with Bokford as muted text + Visa verifikat quiet
link (normal state) and Ej bokford as the beige exception chip with an
inline Bokfor pill; the business/private tabs become the house seg and the
KALLA header row becomes the standard chip-picker. Skattekonto rows get
the same treatment. SEK conversion moves to a tooltip (one-line rows).
Badge itself becomes the concept chip: pill radius (99px), 11.5px, quiet
padding, replacing the boxy rounded-md look the founder flagged on the
Underlag saknas badge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): audit round: viewer-disabled split options, touch fallback
Regression-audit fixes: SplitButton options support disabled+disabledTitle
so viewer-gated create paths render inert with the viewer tooltip instead
of silently no-opping (wired on Ny transaktion); the history list gets its
Kopplad till faktura indicator back; dead transactionsWithMatches memo
removed; and coarse-pointer devices now always show hover-revealed
controls (checkboxes, chevrons, quiet row actions) since touch has no
hover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
import { X } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const Dialog = DialogPrimitive.Root
|
|
|
|
const DialogTrigger = DialogPrimitive.Trigger
|
|
|
|
const DialogPortal = DialogPrimitive.Portal
|
|
|
|
const DialogClose = DialogPrimitive.Close
|
|
|
|
const DialogOverlay = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
>(({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Overlay
|
|
ref={ref}
|
|
className={cn(
|
|
"fixed inset-0 z-50 bg-black/50 dark:bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
|
|
const DialogContent = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
>(({ className, children, ...props }, ref) => (
|
|
<DialogPortal>
|
|
<DialogOverlay />
|
|
<DialogPrimitive.Content
|
|
ref={ref}
|
|
className={cn(
|
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-[var(--shadow-md)] duration-200 max-h-[calc(100dvh-2rem)] overflow-y-auto scrollbar-visible data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
<X className="h-4 w-4" />
|
|
<span className="sr-only">Close</span>
|
|
</DialogPrimitive.Close>
|
|
</DialogPrimitive.Content>
|
|
</DialogPortal>
|
|
))
|
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
|
|
const DialogHeader = ({
|
|
className,
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
DialogHeader.displayName = "DialogHeader"
|
|
|
|
const DialogFooter = ({
|
|
className,
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2 sm:gap-0",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
DialogFooter.displayName = "DialogFooter"
|
|
|
|
const DialogTitle = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
>(({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Title
|
|
ref={ref}
|
|
className={cn(
|
|
"text-lg font-semibold leading-none tracking-tight",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
|
|
const DialogDescription = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
>(({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Description
|
|
ref={ref}
|
|
className={cn("text-sm text-muted-foreground", className)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
|
|
|
export {
|
|
Dialog,
|
|
DialogPortal,
|
|
DialogOverlay,
|
|
DialogClose,
|
|
DialogTrigger,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogFooter,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
}
|