refactor(design): lock the border-radius ladder, one radius per role (#1607)
Seven radii were in circulation (4/5/6/8/12/16px + pill) with no rule for which went where; one toolbar row on /transactions mixed four shape languages. This locks a 4-tier ladder (design.md convention 16): - pill: interactive toolbar controls (buttons, chips, pickers, segmented controls, toolbar search, count nubs) - rounded-xl (12px): overlay tier: page panel, dialogs, slide-overs - rounded-lg (8px): cards, form fields, popover/menu content, boxes - rounded-sm (4px): nested leaves (menu items, checkboxes, kbd/code nubs) Changes: - New SegmentedControl primitive (pill-in-pill tablist, h-8) replaces the hand-rolled bg-muted/70 tablist copied across 11 files - New ToolbarSearch primitive (pill, h-8) adopted on 9 page toolbars; dialog/picker searches keep the rounded-lg Input - dialog.tsx 8px -> 12px, matching SettingsModal/slide-over/CommandPalette - ContextPicker chips at the shared h-8 toolbar height - ~300 rounded-md / bare rounded call sites remapped by role; auth icon tiles and the mobile nav sheet come down from 16px to 12px - rounded-md, bare rounded, rounded-2xl and rounded-[Npx] are dead vocabulary, enforced by a new off-ladder-radius check in check:guards Verified: lint 0 errors, 14422 unit tests pass, check:guards green, tsc clean on all changed files, sandbox screenshots of transactions/ bookkeeping/granskning toolbars and the Ny verifikation dialog. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
18c20e68e6
commit
9686b54b41
@@ -329,7 +329,7 @@ export function LedgerGraph({ deep, companyName }: { deep: DeepLedgerContext; co
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRunKey((k) => k + 1)}
|
||||
className="inline-flex shrink-0 items-center gap-2 rounded-md border px-3 py-1.5 text-xs transition-colors"
|
||||
className="inline-flex shrink-0 items-center gap-2 rounded-full border px-3 py-1.5 text-xs transition-colors"
|
||||
style={{ borderColor: HAIR_STRONG, color: MUTED }}
|
||||
>
|
||||
<RotateCw className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -978,7 +978,7 @@ function MessageBubble({
|
||||
{isUser ? (
|
||||
message.text || (streamingTail ? <Cursor /> : '')
|
||||
) : message.text ? (
|
||||
<div className="prose prose-sm max-w-none text-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 prose-headings:font-display prose-headings:font-normal prose-headings:tracking-tight prose-h2:text-base prose-h2:mt-3 prose-h2:mb-2 prose-h3:text-sm prose-h3:mt-3 prose-h3:mb-1 prose-p:my-2 prose-p:leading-6 prose-strong:font-semibold prose-strong:text-foreground prose-ul:my-2 prose-li:my-0.5 prose-blockquote:border-l-2 prose-blockquote:border-foreground/30 prose-blockquote:not-italic prose-blockquote:text-muted-foreground prose-blockquote:pl-3 prose-blockquote:my-2 prose-code:bg-secondary prose-code:rounded prose-code:px-1 prose-code:py-0.5 prose-code:text-xs prose-code:before:content-none prose-code:after:content-none prose-a:text-foreground prose-a:underline prose-a:underline-offset-2 prose-pre:bg-secondary prose-pre:text-foreground prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-pre:my-2 prose-pre:p-3 prose-pre:text-xs prose-pre:leading-relaxed prose-pre:overflow-x-auto [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-foreground [&_pre_code]:text-xs prose-table:my-2 prose-table:text-xs prose-table:border-collapse [&_table]:w-full [&_th]:border-b [&_th]:border-border [&_th]:py-1.5 [&_th]:px-2 [&_th]:text-left [&_th]:font-medium [&_th]:text-muted-foreground [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-[10px] [&_td]:border-b [&_td]:border-border [&_td]:py-1.5 [&_td]:px-2 [&_td]:align-top [&_tbody_tr:last-child_td]:border-b-0">
|
||||
<div className="prose prose-sm max-w-none text-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 prose-headings:font-display prose-headings:font-normal prose-headings:tracking-tight prose-h2:text-base prose-h2:mt-3 prose-h2:mb-2 prose-h3:text-sm prose-h3:mt-3 prose-h3:mb-1 prose-p:my-2 prose-p:leading-6 prose-strong:font-semibold prose-strong:text-foreground prose-ul:my-2 prose-li:my-0.5 prose-blockquote:border-l-2 prose-blockquote:border-foreground/30 prose-blockquote:not-italic prose-blockquote:text-muted-foreground prose-blockquote:pl-3 prose-blockquote:my-2 prose-code:bg-secondary prose-code:rounded-sm prose-code:px-1 prose-code:py-0.5 prose-code:text-xs prose-code:before:content-none prose-code:after:content-none prose-a:text-foreground prose-a:underline prose-a:underline-offset-2 prose-pre:bg-secondary prose-pre:text-foreground prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-pre:my-2 prose-pre:p-3 prose-pre:text-xs prose-pre:leading-relaxed prose-pre:overflow-x-auto [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-foreground [&_pre_code]:text-xs prose-table:my-2 prose-table:text-xs prose-table:border-collapse [&_table]:w-full [&_th]:border-b [&_th]:border-border [&_th]:py-1.5 [&_th]:px-2 [&_th]:text-left [&_th]:font-medium [&_th]:text-muted-foreground [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-[10px] [&_td]:border-b [&_td]:border-border [&_td]:py-1.5 [&_td]:px-2 [&_td]:align-top [&_tbody_tr:last-child_td]:border-b-0">
|
||||
{markdownLoaded ? (
|
||||
<MarkdownMessage text={message.text} />
|
||||
) : (
|
||||
@@ -1124,7 +1124,7 @@ function MessageActions({
|
||||
}
|
||||
|
||||
const btn =
|
||||
'inline-flex items-center gap-1.5 rounded-md px-1.5 py-1 text-[11px] text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors'
|
||||
'inline-flex items-center gap-1.5 rounded-sm px-1.5 py-1 text-[11px] text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors'
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 opacity-0 focus-within:opacity-100 group-hover/msg:opacity-100 transition-opacity">
|
||||
|
||||
@@ -80,14 +80,14 @@ export default function AgentSessionList({ activeConversationId, onSelect }: Pro
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Sök konversationer…"
|
||||
className="w-full rounded-md border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
{query.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuery('')}
|
||||
aria-label="Rensa sökning"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -135,7 +135,7 @@ export default function AgentSessionList({ activeConversationId, onSelect }: Pro
|
||||
placeholder="Namnge konversationen…"
|
||||
maxLength={200}
|
||||
aria-label="Nytt namn på konversationen"
|
||||
className="w-full rounded-md border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -617,7 +617,7 @@ export default function AgentSheet({
|
||||
>
|
||||
<button
|
||||
onClick={() => setView('chat')}
|
||||
className="h-9 w-9 -ml-1 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 -ml-1 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Tillbaka"
|
||||
title="Tillbaka"
|
||||
>
|
||||
@@ -626,7 +626,7 @@ export default function AgentSheet({
|
||||
<h2 className="font-display text-lg tracking-tight truncate">Konversationer</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="ml-auto h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Stäng"
|
||||
title="Avsluta sessionen"
|
||||
>
|
||||
@@ -646,7 +646,7 @@ export default function AgentSheet({
|
||||
{!isSandbox && (
|
||||
<button
|
||||
onClick={() => setView('list')}
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Tidigare konversationer"
|
||||
title="Tidigare konversationer"
|
||||
>
|
||||
@@ -671,7 +671,7 @@ export default function AgentSheet({
|
||||
{!isSandbox && (
|
||||
<button
|
||||
onClick={toggleFloating}
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label={floating ? 'Docka mot högerkanten' : 'Frigör panelen'}
|
||||
title={
|
||||
floating
|
||||
@@ -693,7 +693,7 @@ export default function AgentSheet({
|
||||
{!isSandbox && !floating && (
|
||||
<button
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label={expanded ? 'Förminska' : 'Förstora'}
|
||||
title={expanded ? 'Förminska' : 'Förstora'}
|
||||
>
|
||||
@@ -706,7 +706,7 @@ export default function AgentSheet({
|
||||
{activeConversationId && !isSandbox && (
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="h-9 inline-flex items-center gap-2 rounded-md px-2 text-xs font-medium text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 inline-flex items-center gap-2 rounded-sm px-2 text-xs font-medium text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Rensa: börja en ny konversation"
|
||||
title="Rensa: börja en ny konversation"
|
||||
>
|
||||
@@ -716,7 +716,7 @@ export default function AgentSheet({
|
||||
)}
|
||||
<button
|
||||
onClick={handleCollapse}
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Minimera"
|
||||
title="Minimera: behåll sessionen"
|
||||
>
|
||||
@@ -724,7 +724,7 @@ export default function AgentSheet({
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
|
||||
aria-label="Stäng"
|
||||
title="Avsluta sessionen"
|
||||
>
|
||||
|
||||
@@ -352,7 +352,7 @@ export default function ApprovalCard({
|
||||
</div>
|
||||
|
||||
{preview != null && typeof preview === 'object' && (
|
||||
<div className="rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="rounded-lg border border-border bg-muted/30 px-3 py-2">
|
||||
<OperationPreview
|
||||
op={{
|
||||
operation_type: previewOperationType,
|
||||
@@ -374,7 +374,7 @@ export default function ApprovalCard({
|
||||
value={confirmText}
|
||||
onChange={(e) => setConfirmText(e.target.value)}
|
||||
disabled={isBusy}
|
||||
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
autoComplete="off"
|
||||
aria-label="Bekräfta med ordet godkänn"
|
||||
/>
|
||||
@@ -384,7 +384,7 @@ export default function ApprovalCard({
|
||||
{errorMessage && <p className="text-xs text-destructive">{errorMessage}</p>}
|
||||
|
||||
{showRejectForm ? (
|
||||
<div className="space-y-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="space-y-2 rounded-lg border border-border bg-muted/30 px-3 py-2">
|
||||
<p className="text-xs font-medium">Vad är fel?</p>
|
||||
<Select
|
||||
value={rejectCategory}
|
||||
@@ -448,7 +448,7 @@ export default function ApprovalCard({
|
||||
</div>
|
||||
</div>
|
||||
) : accountsToActivate ? (
|
||||
<div className="space-y-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="space-y-2 rounded-lg border border-border bg-muted/30 px-3 py-2">
|
||||
<p className="text-xs leading-5">
|
||||
Bokningen använder konton som inte är aktiva i din kontoplan:{' '}
|
||||
<strong className="tabular-nums">{accountsToActivate.join(', ')}</strong>. Aktivera dem för att godkänna bokningen.
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function ChatConversationView({
|
||||
visible so a back button would be redundant. */}
|
||||
<Link
|
||||
href="/chat"
|
||||
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors -ml-1"
|
||||
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors -ml-1"
|
||||
aria-label="Tillbaka till konversationer"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
onClick={toggleCollapsed}
|
||||
aria-label="Visa konversationer"
|
||||
title="Visa konversationer"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
>
|
||||
<PanelLeftOpen className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -91,7 +91,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
onClick={() => openAgentSheet({ intentId: 'general.help' })}
|
||||
aria-label="Ny konversation"
|
||||
title="Ny konversation"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors text-lg"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors text-lg"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
@@ -123,7 +123,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
onClick={toggleCollapsed}
|
||||
aria-label="Dölj konversationer"
|
||||
title="Dölj konversationer"
|
||||
className="hidden md:inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
className="hidden md:inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
>
|
||||
<PanelLeftClose className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -141,14 +141,14 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Sök…"
|
||||
className="w-full rounded-md border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background pl-8 pr-7 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
{query.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuery('')}
|
||||
aria-label="Rensa sökning"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -191,7 +191,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
placeholder="Namnge konversationen…"
|
||||
maxLength={200}
|
||||
aria-label="Nytt namn på konversationen"
|
||||
className="w-full rounded-md border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
className="w-full rounded-lg border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -230,7 +230,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
}}
|
||||
title="Byt namn"
|
||||
aria-label="Byt namn på konversation"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
>
|
||||
<Pencil className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -243,7 +243,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
title={c.pinned ? 'Avfäst' : 'Fäst'}
|
||||
aria-label={c.pinned ? 'Avfäst konversation' : 'Fäst konversation'}
|
||||
className={cn(
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded transition-colors',
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded-sm transition-colors',
|
||||
c.pinned
|
||||
? 'text-foreground'
|
||||
: 'text-muted-foreground/50 hover:text-foreground hover:bg-secondary',
|
||||
@@ -263,7 +263,7 @@ export default function ChatSidebar({ initialConversations }: Props) {
|
||||
}}
|
||||
title="Arkivera"
|
||||
aria-label="Arkivera konversation"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-sm text-muted-foreground/50 hover:text-foreground hover:bg-secondary transition-colors"
|
||||
>
|
||||
<Archive className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
@@ -301,7 +301,7 @@ export default function AccountCombobox({ value, accounts, onChange, onCommit, o
|
||||
<div
|
||||
ref={listRef}
|
||||
className={cn(
|
||||
'absolute z-50 top-full left-0 mt-1 max-h-[300px] overflow-y-auto rounded-md border border-input bg-card shadow-md',
|
||||
'absolute z-50 top-full left-0 mt-1 max-h-[300px] overflow-y-auto rounded-lg border border-input bg-card shadow-md',
|
||||
listWidthClass,
|
||||
)}
|
||||
>
|
||||
@@ -348,7 +348,7 @@ export default function AccountCombobox({ value, accounts, onChange, onCommit, o
|
||||
{isOpen && !disabled && search.trim() && flatList.length === 0 && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute z-50 top-full left-0 mt-1 rounded-md border border-input bg-card shadow-md p-3',
|
||||
'absolute z-50 top-full left-0 mt-1 rounded-lg border border-input bg-card shadow-md p-3',
|
||||
listWidthClass,
|
||||
)}
|
||||
>
|
||||
@@ -367,7 +367,7 @@ export default function AccountCombobox({ value, accounts, onChange, onCommit, o
|
||||
{onCreateAccount && (
|
||||
<button
|
||||
type="button"
|
||||
className="mt-2 flex w-full items-center gap-2 rounded-md border border-input bg-card px-2 py-1.5 text-left text-sm hover:bg-muted/50"
|
||||
className="mt-2 flex w-full items-center gap-2 rounded-sm border border-input bg-card px-2 py-1.5 text-left text-sm hover:bg-muted/50"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
setIsOpen(false)
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function AccrualPeriodControl({
|
||||
const showK2Hint = shouldShowK2AccrualHint({ amount, currency, exchangeRate })
|
||||
|
||||
return (
|
||||
<div className="rounded-md border bg-muted/30 p-3 space-y-3">
|
||||
<div className="rounded-lg border bg-muted/30 p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{t('panel_title')}
|
||||
|
||||
@@ -114,7 +114,7 @@ export function ActivateAccountsDialog({
|
||||
)}
|
||||
|
||||
{!loading && knownRows.length > 0 && (
|
||||
<ul className="divide-y divide-border rounded-md border">
|
||||
<ul className="divide-y divide-border rounded-lg border">
|
||||
{knownRows.map((r) => (
|
||||
<li key={r.account_number} className="flex items-baseline gap-3 px-3 py-2">
|
||||
<span className="font-mono text-foreground w-14 shrink-0">{r.account_number}</span>
|
||||
@@ -130,7 +130,7 @@ export function ActivateAccountsDialog({
|
||||
)}
|
||||
|
||||
{!loading && unknownRows.length > 0 && (
|
||||
<div className="rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-attn">
|
||||
<div className="rounded-lg border border-border bg-muted/30 px-3 py-2 text-xs text-attn">
|
||||
<p className="font-medium">Finns inte i BAS-katalogen:</p>
|
||||
<p className="mt-1 font-mono">{unknownRows.map((r) => r.account_number).join(', ')}</p>
|
||||
<p className="mt-1 text-attn/80">
|
||||
|
||||
@@ -71,7 +71,7 @@ export function AttachDocumentPreview({ data, params }: AttachDocumentPreviewPro
|
||||
</div>
|
||||
|
||||
{willOverwrite && existingIsAccounting && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium">Ersätter räkenskapsinformation</p>
|
||||
@@ -85,7 +85,7 @@ export function AttachDocumentPreview({ data, params }: AttachDocumentPreviewPro
|
||||
</div>
|
||||
)}
|
||||
{willOverwrite && !existingIsAccounting && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-2 text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-2 text-xs text-muted-foreground">
|
||||
Ersätter befintligt dokument{existingDocName ? `: ${existingDocName}` : ''}.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Fragment, useState, useEffect, useCallback, useMemo, useRef } from 'rea
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
@@ -18,7 +19,6 @@ import { AddAccountDialog } from './AddAccountDialog'
|
||||
import { EditAccountDialog } from './EditAccountDialog'
|
||||
import { PruneAccountsDialog } from './PruneAccountsDialog'
|
||||
import {
|
||||
Search,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
Pencil,
|
||||
@@ -466,46 +466,20 @@ export default function ChartOfAccountsManager() {
|
||||
|
||||
{/* Toolbar: seg + search; the K2 filter rides far right in catalog view */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={view === 'my-accounts'}
|
||||
onClick={() => switchView('my-accounts')}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
view === 'my-accounts'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{t('tab_my_accounts')}
|
||||
<span className="font-normal text-muted-foreground tabular-nums">{accounts.length}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={view === 'bas-catalog'}
|
||||
onClick={() => switchView('bas-catalog')}
|
||||
className={cn(
|
||||
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
view === 'bas-catalog'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{t('tab_bas_catalog')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative min-w-[190px] max-w-xs flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={view}
|
||||
onChange={switchView}
|
||||
options={[
|
||||
{ value: 'my-accounts', label: t('tab_my_accounts'), count: accounts.length },
|
||||
{ value: 'bas-catalog', label: t('tab_bas_catalog') },
|
||||
]}
|
||||
/>
|
||||
<ToolbarSearch
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
containerClassName="min-w-[190px] max-w-xs flex-1"
|
||||
/>
|
||||
{view === 'my-accounts' && (
|
||||
<label className="ml-auto flex items-center gap-2 text-sm">
|
||||
<Switch
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function CorrectionChain({ currentEntryId, chain }: Props) {
|
||||
href={`/bookkeeping/${entry.id}`}
|
||||
className="block"
|
||||
>
|
||||
<div className={`relative pl-7 py-2 rounded-md transition-colors hover:bg-muted/50 ${isCurrent ? 'bg-muted/30' : ''} ${isCancelled ? 'opacity-60' : ''}`}>
|
||||
<div className={`relative pl-7 py-2 rounded-sm transition-colors hover:bg-muted/50 ${isCurrent ? 'bg-muted/30' : ''} ${isCancelled ? 'opacity-60' : ''}`}>
|
||||
{/* Timeline dot */}
|
||||
<div className={`absolute left-0.5 top-[18px] h-3 w-3 rounded-full border-2 border-background ${isCancelled ? 'bg-muted-foreground' : role.color}`} />
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ export default function DocumentUploadZone({
|
||||
{files.map((file, index) => (
|
||||
<div
|
||||
key={file.uploadKey}
|
||||
className="flex min-w-0 items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50"
|
||||
className="flex min-w-0 items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50"
|
||||
>
|
||||
{isImageType(file.file.type) ? (
|
||||
<ImageIcon className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
|
||||
@@ -140,7 +140,7 @@ export default function DocumentViewerPane({
|
||||
<div className="min-h-0 flex-1 overflow-auto bg-background">
|
||||
{loadingMeta ? (
|
||||
<div className="p-3">
|
||||
<Skeleton className="h-full min-h-[40vh] w-full rounded-md" />
|
||||
<Skeleton className="h-full min-h-[40vh] w-full" />
|
||||
</div>
|
||||
) : showAsPdf ? (
|
||||
<object
|
||||
|
||||
@@ -538,7 +538,7 @@ export function EditAccountDialog({ open, onOpenChange, account, onSaved }: Edit
|
||||
</div>
|
||||
|
||||
{account.is_system_account && (
|
||||
<p className="text-xs text-muted-foreground bg-muted rounded p-2">
|
||||
<p className="text-xs text-muted-foreground bg-muted rounded-sm p-2">
|
||||
Detta är ett systemkonto och kan inte tas bort.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -216,7 +216,7 @@ export default function InboxDocumentPicker({ open, onClose, journalEntryId, onL
|
||||
onClick={() => void handlePick(it)}
|
||||
disabled={!!linkingId}
|
||||
className={cn(
|
||||
'flex-1 min-w-0 text-left flex items-center gap-3 py-3 px-2 -ml-2 rounded transition-colors hover:bg-secondary/60',
|
||||
'flex-1 min-w-0 text-left flex items-center gap-3 py-3 px-2 -ml-2 rounded-sm transition-colors hover:bg-secondary/60',
|
||||
linkingId && !isLinking && 'opacity-50',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -307,7 +307,7 @@ export default function JournalEntryAttachments({
|
||||
const isReplacing = replacingDocId === doc.id
|
||||
return (
|
||||
<div key={doc.id}>
|
||||
<div className="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50">
|
||||
<div className="flex items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50">
|
||||
{isPreviewable(doc.mime_type) ? (
|
||||
<button
|
||||
onClick={() => handlePreviewToggle(doc)}
|
||||
|
||||
@@ -1792,7 +1792,7 @@ export default function JournalEntryForm({
|
||||
</Button>
|
||||
{dimPopoverRow === index && (
|
||||
<div
|
||||
className="absolute right-0 top-full z-50 mt-1 w-64 rounded-md border bg-card p-3 shadow-md"
|
||||
className="absolute right-0 top-full z-50 mt-1 w-64 rounded-lg border bg-card p-3 shadow-md"
|
||||
onKeyDown={(e) => {
|
||||
// The comboboxes preventDefault their own Escape
|
||||
// (closing their dropdown): only an unhandled
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from '@/components/ui/data-list'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
@@ -990,51 +992,24 @@ export default function JournalEntryList({ pristineSlot }: { pristineSlot?: Reac
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{/* Verifikat vs Utkast. Drafts live in their own view with a count badge so
|
||||
they don't sink to the last page of the committed list. */}
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={listMode === 'committed'}
|
||||
onClick={() => switchMode('committed')}
|
||||
className={cn(
|
||||
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
listMode === 'committed'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{t('mode_vouchers')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={listMode === 'drafts'}
|
||||
onClick={() => switchMode('drafts')}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
listMode === 'drafts'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{t('mode_drafts')}
|
||||
{draftCount > 0 && (
|
||||
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
|
||||
{draftCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={listMode}
|
||||
onChange={switchMode}
|
||||
options={[
|
||||
{ value: 'committed', label: t('mode_vouchers') },
|
||||
{ value: 'drafts', label: t('mode_drafts'), count: draftCount },
|
||||
]}
|
||||
/>
|
||||
<div className="relative flex-1 sm:flex-none sm:w-[280px]">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
|
||||
<Input
|
||||
<ToolbarSearch
|
||||
type="text"
|
||||
inputMode="search"
|
||||
placeholder={t('search_placeholder')}
|
||||
aria-label={t('search_placeholder')}
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
className="h-8 pl-8 pr-7 text-xs"
|
||||
containerClassName="min-w-0 max-w-none"
|
||||
className="pr-7"
|
||||
/>
|
||||
{searchInput && (
|
||||
<button
|
||||
@@ -1506,7 +1481,7 @@ export default function JournalEntryList({ pristineSlot }: { pristineSlot?: Reac
|
||||
e.stopPropagation()
|
||||
setPreviewEntryId(entry.id)
|
||||
}}
|
||||
className="inline-flex items-center gap-0.5 rounded text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
className="inline-flex items-center gap-0.5 rounded-sm text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
>
|
||||
<Paperclip className="h-3.5 w-3.5" />
|
||||
<span className="text-xs tabular-nums">{attachmentCounts[entry.id]}</span>
|
||||
@@ -1553,7 +1528,7 @@ export default function JournalEntryList({ pristineSlot }: { pristineSlot?: Reac
|
||||
// p-2 grows the tap target to 30px without
|
||||
// changing row height (the row is ~40px from
|
||||
// the description cell).
|
||||
'inline-flex items-center rounded p-2 text-muted-foreground transition-opacity duration-150 hover:text-foreground',
|
||||
'inline-flex items-center rounded-sm p-2 text-muted-foreground transition-opacity duration-150 hover:text-foreground',
|
||||
// Quiet at rest on desktop, but the table has no
|
||||
// mobile card to fall back on, so touch keeps the
|
||||
// icon visible.
|
||||
|
||||
@@ -202,7 +202,7 @@ export default function TemplateBookDialog({ open, onOpenChange, onCreated }: Pr
|
||||
key={tpl.id}
|
||||
type="button"
|
||||
onClick={() => setSelected(tpl)}
|
||||
className="flex w-full items-center gap-2.5 rounded-md px-2.5 py-2.5 text-left transition-colors hover:bg-secondary/60"
|
||||
className="flex w-full items-center gap-2.5 rounded-sm px-2.5 py-2.5 text-left transition-colors hover:bg-secondary/60"
|
||||
>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-[13px] text-foreground">
|
||||
|
||||
@@ -313,7 +313,7 @@ export function CreateAssetDialog({ open, onOpenChange, onCreated }: CreateAsset
|
||||
</p>
|
||||
</div>
|
||||
{isK3 && (
|
||||
<div className="space-y-3 rounded-md border border-border bg-muted/20 p-4">
|
||||
<div className="space-y-3 rounded-lg border border-border bg-muted/20 p-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
||||
@@ -340,7 +340,7 @@ export function CreateAssetDialog({ open, onOpenChange, onCreated }: CreateAsset
|
||||
{componentRows.map((row, idx) => (
|
||||
<div
|
||||
key={row.id}
|
||||
className="grid grid-cols-12 items-end gap-2 rounded-md border border-border bg-background p-2"
|
||||
className="grid grid-cols-12 items-end gap-2 rounded-lg border border-border bg-background p-2"
|
||||
>
|
||||
<div className="col-span-12 sm:col-span-4 space-y-1">
|
||||
<Label
|
||||
@@ -464,14 +464,14 @@ export function CreateAssetDialog({ open, onOpenChange, onCreated }: CreateAsset
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<strong className="text-foreground">Tips:</strong> Anskaffningen måste redan vara
|
||||
bokförd (debet på 1xxx-kontot mot t.ex. 1930/2440): registret bokför inte
|
||||
själva köpet. Det här registret styr enbart de planenliga avskrivningarna under
|
||||
bokslutet.
|
||||
</div>
|
||||
{error && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
|
||||
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -191,7 +191,7 @@ export function EditAssetDialog({ asset, open, onOpenChange, onSaved }: EditAsse
|
||||
</div>
|
||||
|
||||
{basisLocked && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<Lock className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<span>
|
||||
Anskaffningsdatum, anskaffningsvärde och kategori är låsta eftersom avskrivningar
|
||||
@@ -216,7 +216,7 @@ export function EditAssetDialog({ asset, open, onOpenChange, onSaved }: EditAsse
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
|
||||
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -302,7 +302,7 @@ function ManualEntryEditor({
|
||||
onRemove: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-md border border-border p-3 space-y-3">
|
||||
<div className="rounded-lg border border-border p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium">
|
||||
{entry.kind === 'audit_fee' && 'Revisions-/bokslutsarvode'}
|
||||
@@ -329,7 +329,7 @@ function ManualEntryEditor({
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">Konto</Label>
|
||||
<select
|
||||
className="border border-border rounded-md h-8 text-sm px-2 w-full bg-background"
|
||||
className="border border-border rounded-lg h-8 text-sm px-2 w-full bg-background"
|
||||
value={entry.liabilityAccount}
|
||||
onChange={(e) =>
|
||||
onChange({ liabilityAccount: e.target.value as '2991' | '2992' })
|
||||
|
||||
@@ -584,7 +584,7 @@ function TaxDepreciationCard({
|
||||
</div>
|
||||
|
||||
{dirty && !preview && (
|
||||
<p className="rounded-md border border-border bg-muted/20 p-3 text-sm text-muted-foreground">
|
||||
<p className="rounded-lg border border-border bg-muted/20 p-3 text-sm text-muted-foreground">
|
||||
Inställningarna är ändrade. Beräkna förslaget för att se rätt gränsbelopp innan du
|
||||
sparar.
|
||||
</p>
|
||||
@@ -592,7 +592,7 @@ function TaxDepreciationCard({
|
||||
|
||||
{result && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-3 rounded-md border border-border bg-muted/20 p-4 text-sm sm:grid-cols-3">
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-3 rounded-lg border border-border bg-muted/20 p-4 text-sm sm:grid-cols-3">
|
||||
<TaxValue label="Ingående värde" value={result.openingTaxValue} />
|
||||
<TaxValue label="Årets anskaffningar" value={result.additions} />
|
||||
<TaxValue label="Årets avyttringar" value={-result.disposals} />
|
||||
@@ -639,7 +639,7 @@ function TaxDepreciationCard({
|
||||
</div>
|
||||
)}
|
||||
{dirty && method === 'rakenskapsenlig' && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-border p-3">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border p-3">
|
||||
<Checkbox
|
||||
id="tax-book-conformity"
|
||||
checked={bookConformityConfirmed}
|
||||
|
||||
@@ -213,7 +213,7 @@ export function EfDeclarationSection({
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">Egenavgifter: kategori</Label>
|
||||
<select
|
||||
className="border border-border rounded-md h-9 text-sm px-2 w-full bg-background"
|
||||
className="border border-border rounded-lg h-9 text-sm px-2 w-full bg-background"
|
||||
value={overrides.category}
|
||||
onChange={(e) => update('category', e.target.value as EgenavgiftCategory)}
|
||||
>
|
||||
|
||||
@@ -97,7 +97,7 @@ export function ContextPicker({
|
||||
aria-haspopup="listbox"
|
||||
aria-label={ariaLabel}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 whitespace-nowrap rounded-full border border-border px-3 py-[5px] text-[13px]',
|
||||
'inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-full border border-border px-3 text-[13px]',
|
||||
'text-foreground transition-colors duration-150',
|
||||
disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
@@ -130,7 +130,7 @@ export function ContextPicker({
|
||||
setOpen(false)
|
||||
}}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-[13px] leading-snug transition-colors',
|
||||
'flex w-full items-center gap-2 rounded-sm px-2.5 py-2 text-left text-[13px] leading-snug transition-colors',
|
||||
item.disabled
|
||||
? 'text-muted-foreground/40 cursor-not-allowed'
|
||||
: item.id === value
|
||||
|
||||
@@ -195,7 +195,7 @@ export function ReportDateRange({
|
||||
type="button"
|
||||
onClick={() => handlePreset(p)}
|
||||
className={cn(
|
||||
'px-3 py-1.5 text-xs rounded-md border transition-colors duration-150',
|
||||
'px-3 py-1.5 text-xs rounded-full border transition-colors duration-150',
|
||||
active
|
||||
? 'bg-secondary border-border text-foreground'
|
||||
: 'bg-transparent border-border text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
||||
|
||||
@@ -275,7 +275,7 @@ export default function AttGoraSection({
|
||||
)}
|
||||
>
|
||||
<div className="overflow-hidden pb-1">
|
||||
<div className="flex items-center gap-3 rounded bg-secondary/40 px-3 py-2">
|
||||
<div className="flex items-center gap-3 rounded-sm bg-secondary/40 px-3 py-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm truncate">
|
||||
{match.transaction_description}
|
||||
@@ -302,7 +302,7 @@ export default function AttGoraSection({
|
||||
href={`/transactions?highlight=${match.transaction_id}`}
|
||||
aria-label={t('suggested_view')}
|
||||
title={t('suggested_view')}
|
||||
className="shrink-0 h-10 w-10 inline-flex items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
className="shrink-0 h-10 w-10 inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Link>
|
||||
|
||||
@@ -152,7 +152,7 @@ export default function CompanySwitcher() {
|
||||
onClick={() => handleSwitch(c.id)}
|
||||
disabled={isPending}
|
||||
className={cn(
|
||||
'flex items-center gap-2 w-full px-2.5 py-2 text-left text-[13px] leading-snug transition-colors rounded-md md:whitespace-nowrap',
|
||||
'flex items-center gap-2 w-full px-2.5 py-2 text-left text-[13px] leading-snug transition-colors rounded-sm md:whitespace-nowrap',
|
||||
c.id === company?.id
|
||||
? 'text-foreground bg-muted/40'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/40',
|
||||
@@ -184,7 +184,7 @@ export default function CompanySwitcher() {
|
||||
<Link
|
||||
href="/select-company?choose=1"
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex items-center gap-2 px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground hover:bg-muted/40 rounded-md transition-colors md:whitespace-nowrap"
|
||||
className="flex items-center gap-2 px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground hover:bg-muted/40 rounded-sm transition-colors md:whitespace-nowrap"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
{t('add_company')}
|
||||
|
||||
@@ -738,7 +738,7 @@ export default function DashboardNav({ companyName: _companyName, entityType, pa
|
||||
alt=""
|
||||
width={26}
|
||||
height={26}
|
||||
className="h-[26px] w-[26px] rounded-md"
|
||||
className="h-[26px] w-[26px] rounded-lg"
|
||||
/>
|
||||
</Link>
|
||||
<button
|
||||
@@ -1010,7 +1010,7 @@ export default function DashboardNav({ companyName: _companyName, entityType, pa
|
||||
{/* Bottom sheet */}
|
||||
<div
|
||||
className={cn(
|
||||
"md:hidden fixed inset-x-0 bottom-0 z-50 bg-card rounded-t-2xl border-t border-border/40 overflow-y-auto overscroll-contain",
|
||||
"md:hidden fixed inset-x-0 bottom-0 z-50 bg-card rounded-t-xl border-t border-border/40 overflow-y-auto overscroll-contain",
|
||||
isClosing
|
||||
? "animate-out slide-out-to-bottom duration-200"
|
||||
: "animate-in slide-in-from-bottom duration-300"
|
||||
@@ -1020,7 +1020,7 @@ export default function DashboardNav({ companyName: _companyName, entityType, pa
|
||||
aria-label={tNav('navigation_menu')}
|
||||
>
|
||||
{/* Drag handle */}
|
||||
<div className="flex justify-center pt-3 pb-1 sticky top-0 bg-card rounded-t-2xl">
|
||||
<div className="flex justify-center pt-3 pb-1 sticky top-0 bg-card rounded-t-xl">
|
||||
<div className="w-8 h-1 rounded-full bg-muted-foreground/25" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ export function SandboxBanner() {
|
||||
</span>
|
||||
<button
|
||||
onClick={handleCreateAccount}
|
||||
className="shrink-0 rounded-md bg-foreground/10 px-3 py-0.5 text-xs font-semibold hover:bg-foreground/15 transition-colors"
|
||||
className="shrink-0 rounded-full bg-foreground/10 px-3 py-0.5 text-xs font-semibold hover:bg-foreground/15 transition-colors"
|
||||
>
|
||||
Skapa konto
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDismissed(true)}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 hover:bg-foreground/10 transition-colors"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-sm p-1 hover:bg-foreground/10 transition-colors"
|
||||
aria-label="Stäng"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -63,7 +63,7 @@ function CompanyMark({ name }: { name: string }) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-[4px] bg-secondary text-[9px] font-semibold uppercase leading-none text-foreground"
|
||||
className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-sm bg-secondary text-[9px] font-semibold uppercase leading-none text-foreground"
|
||||
>
|
||||
{name.trim().charAt(0) || '?'}
|
||||
</span>
|
||||
@@ -193,7 +193,7 @@ export default function UserMenu({
|
||||
)
|
||||
|
||||
const menuRow =
|
||||
'flex w-full items-center gap-2.5 rounded-md px-2.5 py-2 text-left text-[13px] ' +
|
||||
'flex w-full items-center gap-2.5 rounded-sm px-2.5 py-2 text-left text-[13px] ' +
|
||||
'text-muted-foreground hover:text-foreground hover:bg-secondary/60 ' +
|
||||
'transition-colors duration-150 cursor-pointer'
|
||||
|
||||
@@ -293,7 +293,7 @@ export default function UserMenu({
|
||||
role="option"
|
||||
aria-selected={c.id === company?.id}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-[13px] leading-snug transition-colors',
|
||||
'flex w-full items-center gap-2 rounded-sm px-2.5 py-2 text-left text-[13px] leading-snug transition-colors',
|
||||
c.id === company?.id
|
||||
? 'bg-secondary/60 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
||||
|
||||
@@ -81,7 +81,7 @@ export function DeadlineGroupCard({ deadlines, onEdit, onRequestToggle }: Deadli
|
||||
key={deadline.id}
|
||||
onClick={() => onEdit?.(deadline)}
|
||||
className={cn(
|
||||
'group flex items-center gap-3 rounded-md py-1.5 -mx-2 px-2 transition-colors duration-150',
|
||||
'group flex items-center gap-3 rounded-sm py-1.5 -mx-2 px-2 transition-colors duration-150',
|
||||
onEdit && 'cursor-pointer hover:bg-secondary/35',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Deadline, DeadlineType } from '@/types'
|
||||
import { DeadlineRow, deadlineDateLabel } from './DeadlineRow'
|
||||
import { DeadlineGroupCard, isSkattekontoDeadline } from './DeadlineGroupCard'
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { isDeadlineOverdue, parseDate, startOfDay } from '@/lib/calendar/utils'
|
||||
|
||||
type TypeSegment = 'all' | 'tax' | 'invoicing' | 'own'
|
||||
@@ -136,31 +137,16 @@ export function DeadlineList({
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Toolbar: the type seg (concept: Alla / Skatt / Fakturering / Egna) */}
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
{(
|
||||
[
|
||||
{ key: 'all', label: t('seg_all') },
|
||||
{ key: 'tax', label: t('seg_tax') },
|
||||
{ key: 'invoicing', label: t('seg_invoicing') },
|
||||
{ key: 'own', label: t('seg_own') },
|
||||
] as const
|
||||
).map(({ key, label }) => (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={segment === key}
|
||||
onClick={() => setSegment(key)}
|
||||
className={`rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
|
||||
segment === key
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={segment}
|
||||
onChange={setSegment}
|
||||
options={[
|
||||
{ value: 'all', label: t('seg_all') },
|
||||
{ value: 'tax', label: t('seg_tax') },
|
||||
{ value: 'invoicing', label: t('seg_invoicing') },
|
||||
{ value: 'own', label: t('seg_own') },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="gap-8 lg:grid lg:grid-cols-[minmax(0,1fr)_240px] lg:items-start">
|
||||
{/* The thread */}
|
||||
|
||||
@@ -130,7 +130,7 @@ export function DeadlineRow({ deadline, onEdit, onRequestToggle }: DeadlineRowPr
|
||||
<div
|
||||
onClick={() => onEdit?.(deadline)}
|
||||
className={cn(
|
||||
'group flex items-start gap-3 rounded-md py-3 -mx-2 px-2 transition-colors duration-150',
|
||||
'group flex items-start gap-3 rounded-sm py-3 -mx-2 px-2 transition-colors duration-150',
|
||||
onEdit && 'cursor-pointer hover:bg-secondary/35',
|
||||
completed && 'opacity-60',
|
||||
)}
|
||||
|
||||
@@ -265,7 +265,7 @@ export default function DimensionCombobox({
|
||||
{isOpen && !disabled && (
|
||||
<div
|
||||
ref={listRef}
|
||||
className="absolute z-50 top-full left-0 mt-1 min-w-[16rem] w-[max(100%,20rem)] max-h-[300px] overflow-y-auto rounded-md border border-input bg-card shadow-md"
|
||||
className="absolute z-50 top-full left-0 mt-1 min-w-[16rem] w-[max(100%,20rem)] max-h-[300px] overflow-y-auto rounded-lg border border-input bg-card shadow-md"
|
||||
>
|
||||
{loadState === 'loading' && (
|
||||
<div className="flex items-center gap-2 px-2 py-2 text-sm text-muted-foreground">
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
@@ -368,40 +370,30 @@ export default function DimensionsManager() {
|
||||
muted value count, search, and the actions far right. */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
{dimensions.map((dim) => (
|
||||
<button
|
||||
key={dim.id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeDimId === dim.id}
|
||||
onClick={() => {
|
||||
setActiveDimId(dim.id)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
activeDimId === dim.id
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{dim.name}
|
||||
{dim.values.length > 0 && (
|
||||
<span className="text-muted-foreground tabular-nums">{dim.values.length}</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="relative min-w-[180px] max-w-xs flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={activeDimId ?? ''}
|
||||
onChange={(id) => {
|
||||
setActiveDimId(id)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
options={dimensions.map((dim) => ({
|
||||
value: dim.id,
|
||||
label: (
|
||||
<>
|
||||
{dim.name}
|
||||
{dim.values.length > 0 && (
|
||||
<span className="text-muted-foreground tabular-nums">{dim.values.length}</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
<ToolbarSearch
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
containerClassName="min-w-[180px] max-w-xs flex-1"
|
||||
/>
|
||||
<div className="ml-auto flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -72,7 +72,7 @@ export function DocsLayout({ currentPath, children }: DocsLayoutProps) {
|
||||
<Link
|
||||
href={link.href}
|
||||
className={cn(
|
||||
'block text-sm px-3 py-1.5 rounded-md transition-colors',
|
||||
'block text-sm px-3 py-1.5 rounded-sm transition-colors',
|
||||
isActive
|
||||
? 'bg-secondary text-foreground font-medium'
|
||||
: 'text-foreground/75 hover:bg-secondary/60 hover:text-foreground',
|
||||
|
||||
@@ -1113,7 +1113,7 @@ function OptionsStep({
|
||||
{/* Years whose provider export failed: must be visible before
|
||||
the user proceeds, otherwise an IB/UB gap slips through. */}
|
||||
{failedYears.length > 0 && (
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
|
||||
<div className="rounded-lg border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600" />
|
||||
<div className="space-y-1">
|
||||
@@ -1263,7 +1263,7 @@ function OptionsStep({
|
||||
</div>
|
||||
|
||||
{options.importSIEData && yearsToReplace.length > 0 && (
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
|
||||
<div className="rounded-lg border border-amber-500/30 bg-amber-50/50 p-3 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<RefreshCw className="mt-0.5 h-4 w-4 shrink-0 text-amber-600" />
|
||||
<div className="space-y-1">
|
||||
@@ -1435,7 +1435,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
|
||||
<div className="border-t border-border px-4 pb-4 pt-3 space-y-3">
|
||||
{/* Errors: shown prominently */}
|
||||
{result.errors.length > 0 && (
|
||||
<div className="rounded-md border border-destructive/20 bg-destructive/5 p-3">
|
||||
<div className="rounded-lg border border-destructive/20 bg-destructive/5 p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-destructive" />
|
||||
<div className="space-y-1.5">
|
||||
@@ -1452,7 +1452,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
|
||||
|
||||
{/* Opening balance adjustment */}
|
||||
{d?.openingBalance && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<BookOpen className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div>
|
||||
@@ -1493,7 +1493,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
|
||||
|
||||
{/* Skipped vouchers breakdown */}
|
||||
{d?.skippedVouchers && d.skippedVouchers.total > 0 && (
|
||||
<div className="rounded-md border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
|
||||
<div className="rounded-lg border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
|
||||
<div>
|
||||
@@ -1537,7 +1537,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
|
||||
|
||||
{/* Migration adjustment info */}
|
||||
{d?.migrationAdjustment?.created && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div>
|
||||
@@ -1554,7 +1554,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
|
||||
|
||||
{/* Untransferred prior-year results — omföring av årets resultat saknas */}
|
||||
{d?.untransferredResults && d.untransferredResults.length > 0 && (
|
||||
<div className="rounded-md border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
|
||||
<div className="rounded-lg border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
|
||||
<div>
|
||||
@@ -1591,7 +1591,7 @@ function FiscalYearResult({ result, index }: { result: ImportResult; index: numb
|
||||
)
|
||||
if (remainingWarnings.length === 0) return null
|
||||
return (
|
||||
<div className="rounded-md border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
|
||||
<div className="rounded-lg border border-amber-200 bg-amber-50/50 p-3 dark:border-amber-900/30 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
|
||||
<div className="space-y-1.5">
|
||||
|
||||
@@ -763,7 +763,7 @@ export default function BookDirectlyDialog({ open, onOpenChange, item, docUrl =
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-56 overflow-y-auto rounded-md border">
|
||||
<div className="max-h-56 overflow-y-auto rounded-lg border">
|
||||
{isLoadingTransactions ? (
|
||||
<div className="flex items-center justify-center py-8 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> Laddar…
|
||||
|
||||
@@ -252,7 +252,7 @@ export default function BulkBookInboxDialog({ open, onOpenChange, items, onSucce
|
||||
</Select>
|
||||
|
||||
{vatTreatment === 'reverse_charge' && (
|
||||
<div className="rounded-md border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
|
||||
<strong className="font-medium text-foreground">Kontrollera säljaren.</strong>{' '}
|
||||
Omvänd skattskyldighet gäller bara köp från en <strong className="font-medium text-foreground">utländsk
|
||||
säljare utan svenskt momsregistreringsnummer</strong>: t.ex. EU-tjänster, EU-varor,
|
||||
@@ -272,7 +272,7 @@ export default function BulkBookInboxDialog({ open, onOpenChange, items, onSucce
|
||||
)}
|
||||
|
||||
{isMixedCurrency && (
|
||||
<div className="rounded-md border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
|
||||
<div className="rounded-lg border border-border bg-secondary/40 p-3 text-xs text-muted-foreground">
|
||||
<p className="font-medium text-foreground">{t('mixed_currency_totals_label')}</p>
|
||||
<ul className="mt-2 space-y-1">
|
||||
{underlagTotals.map(({ currency, total }) => (
|
||||
|
||||
@@ -1793,7 +1793,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
/>
|
||||
)}
|
||||
{isDragging && (
|
||||
<div className="absolute inset-0 bg-primary/5 border-2 border-dashed border-primary rounded-md m-4 flex items-center justify-center pointer-events-none">
|
||||
<div className="absolute inset-0 bg-primary/5 border-2 border-dashed border-primary rounded-lg m-4 flex items-center justify-center pointer-events-none">
|
||||
<p className="text-sm font-medium text-primary">Släpp filen för att ladda upp</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -2235,7 +2235,7 @@ export function DocumentPreview({
|
||||
<div className="h-full w-full p-4 flex items-start justify-center overflow-hidden">
|
||||
{docMime?.startsWith('image/') ? (
|
||||
// Image: frame hugs the image, capped at the parent's visible box.
|
||||
<div className="max-h-full max-w-3xl bg-background rounded-md border overflow-hidden flex">
|
||||
<div className="max-h-full max-w-3xl bg-background rounded-lg border overflow-hidden flex">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={docUrl}
|
||||
@@ -2248,7 +2248,7 @@ export function DocumentPreview({
|
||||
// with no tokens = opaque origin, no scripts, no forms, no popups.
|
||||
// bg-white because mail HTML assumes a white canvas and would render
|
||||
// transparent (unreadable in dark mode) otherwise.
|
||||
<div className="h-full w-full max-w-3xl bg-background rounded-md border overflow-hidden">
|
||||
<div className="h-full w-full max-w-3xl bg-background rounded-lg border overflow-hidden">
|
||||
<iframe
|
||||
src={docUrl}
|
||||
sandbox=""
|
||||
@@ -2258,7 +2258,7 @@ export function DocumentPreview({
|
||||
</div>
|
||||
) : (
|
||||
// PDF: iframe needs explicit height, frame fills the available pane.
|
||||
<div className="h-full w-full max-w-3xl bg-background rounded-md border overflow-hidden">
|
||||
<div className="h-full w-full max-w-3xl bg-background rounded-lg border overflow-hidden">
|
||||
<iframe src={docUrl} className="w-full h-full border-0" title="Underlag" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -281,7 +281,7 @@ export function WhatsAppLinkPanel() {
|
||||
{minted ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="rounded-md border border-border bg-muted/50 px-3 py-1.5 font-mono text-base tracking-wider">
|
||||
<span className="rounded-sm border border-border bg-muted/50 px-3 py-1.5 font-mono text-base tracking-wider">
|
||||
{minted.code}
|
||||
</span>
|
||||
{minted.waLink ? (
|
||||
|
||||
@@ -242,7 +242,7 @@ export default function MockDataImportDialog<T>({
|
||||
|
||||
{/* ── Error ─────────────────────────────────────── */}
|
||||
{error && (
|
||||
<div className="flex items-start gap-2 p-3 rounded-md bg-destructive/5 text-destructive text-sm">
|
||||
<div className="flex items-start gap-2 p-3 rounded-lg bg-destructive/5 text-destructive text-sm">
|
||||
<AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
@@ -378,7 +378,7 @@ export default function MockDataImportDialog<T>({
|
||||
<span>{fileName}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 p-3 rounded-md bg-success/10 text-success text-sm">
|
||||
<div className="flex items-center gap-2 p-3 rounded-lg bg-success/10 text-success text-sm">
|
||||
<Check className="h-4 w-4 shrink-0" />
|
||||
<span>Giltig JSON: {jsonSummary}</span>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,7 @@ export default function ArticlesEditStep({
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
@@ -244,7 +244,7 @@ export default function ArticlesEditStep({
|
||||
{row.duplicate_match ? (
|
||||
<span
|
||||
className={cn(
|
||||
'text-[11px] font-medium px-1.5 py-0.5 rounded',
|
||||
'text-[11px] font-medium px-1.5 py-0.5 rounded-full',
|
||||
updateDuplicates
|
||||
? 'bg-muted text-warning'
|
||||
: 'bg-muted text-muted-foreground',
|
||||
@@ -254,7 +254,7 @@ export default function ArticlesEditStep({
|
||||
{updateDuplicates ? 'Uppdateras' : 'Hoppas över'}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded bg-success/15 text-success">
|
||||
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded-full bg-success/15 text-success">
|
||||
Ny
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -186,7 +186,7 @@ export default function BankFileColumnMappingStep({
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Header row toggle */}
|
||||
<div className="flex items-center justify-between rounded-md border p-4">
|
||||
<div className="flex items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="has-header">Har filen rubrikrad?</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -382,7 +382,7 @@ export default function BankFileColumnMappingStep({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border max-h-64 overflow-x-auto overflow-y-auto">
|
||||
<div className="rounded-lg border max-h-64 overflow-x-auto overflow-y-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
@@ -163,7 +163,7 @@ export default function BankFilePreviewStep({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border max-h-96 overflow-x-auto overflow-y-auto">
|
||||
<div className="rounded-lg border max-h-96 overflow-x-auto overflow-y-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function BankFileResultStep({
|
||||
</CardHeader>
|
||||
{!isSuccess && result.first_error && (
|
||||
<CardContent>
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm">
|
||||
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm">
|
||||
<p className="font-medium text-destructive">Databasfel</p>
|
||||
<p className="mt-1 font-mono text-xs text-muted-foreground break-all">
|
||||
{result.first_error.message}
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function CustomersEditStep({
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
@@ -183,7 +183,7 @@ export default function CustomersEditStep({
|
||||
{row.duplicate_match ? (
|
||||
<span
|
||||
className={cn(
|
||||
'text-[11px] font-medium px-1.5 py-0.5 rounded',
|
||||
'text-[11px] font-medium px-1.5 py-0.5 rounded-full',
|
||||
updateDuplicates
|
||||
? 'bg-muted text-warning'
|
||||
: 'bg-muted text-muted-foreground',
|
||||
@@ -193,7 +193,7 @@ export default function CustomersEditStep({
|
||||
{updateDuplicates ? 'Uppdateras' : 'Hoppas över'}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded bg-success/15 text-success">
|
||||
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded-full bg-success/15 text-success">
|
||||
Ny
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -195,7 +195,7 @@ export default function OpeningBalanceColumnMappingStep({
|
||||
{previewRows.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-muted-foreground">Förhandsgranskning (5 första raderna)</Label>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
|
||||
@@ -239,7 +239,7 @@ export default function OpeningBalanceRowEditor({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
@@ -284,7 +284,7 @@ export default function OpeningBalanceRowEditor({
|
||||
{activeAutocomplete === row.id && autocompleteResults.length > 0 && (
|
||||
<div
|
||||
ref={autocompleteRef}
|
||||
className="absolute z-50 top-full left-3 mt-1 w-72 max-h-48 overflow-y-auto rounded-md border bg-popover shadow-md"
|
||||
className="absolute z-50 top-full left-3 mt-1 w-72 max-h-48 overflow-y-auto rounded-lg border bg-popover shadow-md"
|
||||
>
|
||||
{autocompleteResults.map((item) => (
|
||||
<button
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function RegisterColumnMappingStep<K extends string>({
|
||||
{previewRows.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-muted-foreground">Förhandsgranskning (5 första raderna)</Label>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
|
||||
@@ -109,7 +109,7 @@ export default function SuppliersEditStep({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
@@ -173,7 +173,7 @@ export default function SuppliersEditStep({
|
||||
{row.duplicate_match ? (
|
||||
<span
|
||||
className={cn(
|
||||
'text-[11px] font-medium px-1.5 py-0.5 rounded',
|
||||
'text-[11px] font-medium px-1.5 py-0.5 rounded-full',
|
||||
updateDuplicates
|
||||
? 'bg-muted text-warning'
|
||||
: 'bg-muted text-muted-foreground',
|
||||
@@ -183,7 +183,7 @@ export default function SuppliersEditStep({
|
||||
{updateDuplicates ? 'Uppdateras' : 'Hoppas över'}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded bg-success/15 text-success">
|
||||
<span className="text-[11px] font-medium px-1.5 py-0.5 rounded-full bg-success/15 text-success">
|
||||
Ny
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -345,7 +345,7 @@ export default function TransactionMatchPicker({
|
||||
{/* Underlag reference: what we're matching against, so a currency or
|
||||
amount mismatch with a candidate is obvious at a glance. */}
|
||||
{(total != null || supplier) && (
|
||||
<div className="rounded-md border bg-muted/30 px-3 py-2 text-xs flex items-center gap-x-3 gap-y-1 flex-wrap">
|
||||
<div className="rounded-lg border bg-muted/30 px-3 py-2 text-xs flex items-center gap-x-3 gap-y-1 flex-wrap">
|
||||
<span className="text-muted-foreground shrink-0">Underlag</span>
|
||||
{supplier && <span className="font-medium truncate">{supplier}</span>}
|
||||
{total != null && (
|
||||
@@ -420,7 +420,7 @@ export default function TransactionMatchPicker({
|
||||
onClick={() => void handlePick(c.id)}
|
||||
disabled={!!matchingId}
|
||||
className={cn(
|
||||
'w-full text-left flex items-center gap-3 py-3 hover:bg-muted/50 transition-colors px-2 -mx-2 rounded',
|
||||
'w-full text-left flex items-center gap-3 py-3 hover:bg-muted/50 transition-colors px-2 -mx-2 rounded-sm',
|
||||
matchingId && !isMatching && 'opacity-50',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -441,7 +441,7 @@ export default function RotRutPayoutDialog({
|
||||
)}
|
||||
|
||||
{visibleCandidates.length > MAX_CASES_PER_FILE && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-attn" />
|
||||
<span>{t('rot_rut_max_cases_help', { count: MAX_CASES_PER_FILE })}</span>
|
||||
</div>
|
||||
|
||||
@@ -166,7 +166,7 @@ export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDi
|
||||
<p className="font-medium text-foreground/80 mb-0.5">
|
||||
{t('settings_formula_label')}
|
||||
</p>
|
||||
<p className="font-mono text-[11px] bg-muted/50 rounded px-2 py-1">
|
||||
<p className="font-mono text-[11px] bg-muted/50 rounded-sm px-2 py-1">
|
||||
{t(`def_${def.id}_formula`)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -189,7 +189,7 @@ export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDi
|
||||
setAccountOverride(def.id, e.target.value)
|
||||
}
|
||||
placeholder={def.defaultAccounts.join(', ')}
|
||||
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs font-mono tabular-nums placeholder:text-muted-foreground/50"
|
||||
className="w-full rounded-lg border border-input bg-background px-2.5 py-1.5 text-xs font-mono tabular-nums placeholder:text-muted-foreground/50"
|
||||
/>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground/70">
|
||||
{t('settings_account_hint', { example: def.defaultAccounts.slice(0, 3).join(', ') })}
|
||||
|
||||
@@ -504,7 +504,7 @@ function LogoMark({ src, name, mono = false }: { src: string; name: string; mono
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-6 w-6 items-center justify-center overflow-hidden rounded-md border border-border',
|
||||
'flex h-6 w-6 items-center justify-center overflow-hidden rounded-sm border border-border',
|
||||
!mono && 'bg-white',
|
||||
)}
|
||||
title={name}
|
||||
|
||||
@@ -66,7 +66,7 @@ export function DeclarationRutaRow({
|
||||
aria-expanded={expanded}
|
||||
aria-controls={panelId}
|
||||
aria-label={expanded ? `Dölj konton för ${code}` : `Visa konton för ${code}`}
|
||||
className="mr-1 inline-flex h-6 w-6 items-center justify-center rounded align-middle hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
className="mr-1 inline-flex h-6 w-6 items-center justify-center rounded-sm align-middle hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setExpanded((v) => !v)
|
||||
@@ -79,7 +79,7 @@ export function DeclarationRutaRow({
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<span className="font-mono text-xs bg-muted px-1 rounded mr-2">{code}</span>
|
||||
<span className="font-mono text-xs bg-muted px-1 rounded-sm mr-2">{code}</span>
|
||||
{label}
|
||||
{hasAccounts && (
|
||||
<span className="text-xs text-muted-foreground ml-2">
|
||||
|
||||
@@ -87,7 +87,7 @@ export function ReportLibrary({
|
||||
type="button"
|
||||
onClick={() => setQuery('')}
|
||||
aria-label={t('search_clear')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-sm p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
@@ -97,7 +97,7 @@ export function ReportRowExpansion({
|
||||
aria-expanded={expanded}
|
||||
aria-controls={`expansion-${rowId}`}
|
||||
aria-label={expanded ? 'Dölj verifikat' : 'Visa verifikat'}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
@@ -142,7 +142,7 @@ export function useReportRowExpansion(fetcher: ReportSourceFetcher, rowId: strin
|
||||
aria-expanded={expanded}
|
||||
aria-controls={`expansion-${rowId}`}
|
||||
aria-label={expanded ? 'Dölj verifikat' : 'Visa verifikat'}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors"
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { AlertCircle, Check, ChevronDown, ChevronRight, ExternalLink, FileCode, FileDown, FileText, Percent } from 'lucide-react'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { EmptyState } from '@/components/ui/empty-state'
|
||||
import { FyPicker } from '@/components/common/FyPicker'
|
||||
import { mostRecentEndedVatPeriod } from '@/lib/vat/period-defaults'
|
||||
@@ -209,36 +210,14 @@ export function TrialBalanceView({ periodId, onNavigateToAccount }: { periodId:
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>Saldobalans</CardTitle>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={viewMode === 'simplified'}
|
||||
onClick={() => setViewMode('simplified')}
|
||||
className={cn(
|
||||
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
viewMode === 'simplified'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
Förenklad
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={viewMode === 'detailed'}
|
||||
onClick={() => setViewMode('detailed')}
|
||||
className={cn(
|
||||
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
viewMode === 'detailed'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
Detaljerad
|
||||
</button>
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={viewMode}
|
||||
onChange={setViewMode}
|
||||
options={[
|
||||
{ value: 'simplified', label: 'Förenklad' },
|
||||
{ value: 'detailed', label: 'Detaljerad' },
|
||||
]}
|
||||
/>
|
||||
{data.isBalanced ? (
|
||||
<span className="text-sm text-muted-foreground">Balanserad</span>
|
||||
) : (
|
||||
@@ -2374,7 +2353,7 @@ function VatRutaRow({
|
||||
<expansion.Toggle />
|
||||
</span>
|
||||
)}
|
||||
<span className="font-mono text-xs bg-muted px-1 rounded mr-2">{ruta}</span>
|
||||
<span className="font-mono text-xs bg-muted px-1 rounded-sm mr-2">{ruta}</span>
|
||||
{label}
|
||||
</td>
|
||||
<td className="py-2 text-right tabular-nums">{noVat ? `${formatAmount(baseAmount)} kr` : `${formatAmount(amount)} kr`}</td>
|
||||
@@ -3490,8 +3469,8 @@ export function DimensionPnlView({ periodId, dateRange }: { periodId: string; da
|
||||
onClick={() => setDimNo(String(d.sie_dim_no))}
|
||||
className={
|
||||
active
|
||||
? 'px-3 py-1.5 text-xs rounded-md border transition-colors duration-150 bg-secondary border-border text-foreground'
|
||||
: 'px-3 py-1.5 text-xs rounded-md border transition-colors duration-150 bg-transparent border-border text-muted-foreground hover:bg-secondary/60 hover:text-foreground'
|
||||
? 'px-3 py-1.5 text-xs rounded-full border transition-colors duration-150 bg-secondary border-border text-foreground'
|
||||
: 'px-3 py-1.5 text-xs rounded-full border transition-colors duration-150 bg-transparent border-border text-muted-foreground hover:bg-secondary/60 hover:text-foreground'
|
||||
}
|
||||
>
|
||||
{d.name}
|
||||
|
||||
@@ -841,7 +841,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
<ShieldAlert className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<p>
|
||||
{t('disabled_before')}
|
||||
<code className="mx-1 rounded bg-muted px-1 py-0.5 text-xs">SKATTEVERKET_ENABLED</code>
|
||||
<code className="mx-1 rounded-sm bg-muted px-1 py-0.5 text-xs">SKATTEVERKET_ENABLED</code>
|
||||
{t('disabled_after')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -923,7 +923,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
type="button"
|
||||
onClick={handleDisconnect}
|
||||
disabled={actionLoading === 'disconnect' || connecting}
|
||||
className="inline-flex items-center gap-1 rounded border border-border px-1.5 py-0.5 text-[11px] font-normal text-muted-foreground transition-colors hover:border-destructive/50 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="inline-flex items-center gap-1 rounded-full border border-border px-1.5 py-0.5 text-[11px] font-normal text-muted-foreground transition-colors hover:border-destructive/50 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-50"
|
||||
title={t('disconnect_title')}
|
||||
>
|
||||
{actionLoading === 'disconnect' ? (
|
||||
@@ -945,7 +945,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
receipt a later correction has replaced) still gets the card, just
|
||||
without a number: better than showing another declaration's. */}
|
||||
{isSigned && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-4">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-success" />
|
||||
<div className="space-y-1">
|
||||
@@ -979,7 +979,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
refresh token or has burned through its 10-refresh budget. The
|
||||
only fix is a fresh BankID round-trip. */}
|
||||
{(status?.expired === true || status?.canRefresh === false) && !readOnly && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<p className="text-sm font-medium">{t('expired_banner_title')}</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{t('expired_banner_description')}
|
||||
@@ -996,7 +996,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
after some users had already connected, so their stored token
|
||||
grants moms/skattekonto but not AGI. */}
|
||||
{missingAgdScope && !readOnly && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<p className="text-sm font-medium">
|
||||
{t('missing_scope_title')}
|
||||
</p>
|
||||
@@ -1046,7 +1046,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
treatment so the user understands they must fix errors before
|
||||
BankID signing is even possible. */}
|
||||
{submission?.signeringslank && awaitingSigning && !draftIsStale && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<p className="text-sm font-medium">
|
||||
<InfoTooltip variant="help" content={t('granskningsunderlag_gloss')}>
|
||||
{t('draft_locked_title')}
|
||||
@@ -1072,7 +1072,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
it would file the old amounts. The "Lås upp" button below releases
|
||||
the SKV lock; the user then re-submits the freshly generated XML. */}
|
||||
{awaitingSigning && draftIsStale && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<p className="text-sm font-medium">{t('stale_draft_title')}</p>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
{t('stale_draft_description', {
|
||||
@@ -1096,7 +1096,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
link would be permanently unreachable even though the extension
|
||||
persisted it. */}
|
||||
{submission?.signeringslank && underlagRejected && (
|
||||
<div className="rounded-md border border-destructive/40 bg-destructive/5 p-3">
|
||||
<div className="rounded-lg border border-destructive/40 bg-destructive/5 p-3">
|
||||
<p className="text-sm font-medium text-destructive">
|
||||
{t('incorrect_data_title')}
|
||||
</p>
|
||||
@@ -1115,7 +1115,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
)}
|
||||
|
||||
{kontroller.length > 0 && (
|
||||
<div className="space-y-1 rounded-md border bg-muted/30 p-3">
|
||||
<div className="space-y-1 rounded-lg border bg-muted/30 p-3">
|
||||
{kontroller.map((k, i) => (
|
||||
<div
|
||||
key={i}
|
||||
@@ -1144,7 +1144,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
status?.expired === true ||
|
||||
status?.canRefresh === false
|
||||
return (
|
||||
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
|
||||
<div className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive">
|
||||
<AlertCircle className="mr-1 inline h-3.5 w-3.5" />
|
||||
{error}
|
||||
{sessionExpired && !readOnly && (
|
||||
@@ -1159,7 +1159,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
)
|
||||
})()}
|
||||
{success && !error && (
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3 text-sm">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3 text-sm">
|
||||
<CheckCircle2 className="mr-1 inline h-3.5 w-3.5 text-success" />
|
||||
{success}
|
||||
</div>
|
||||
@@ -1204,7 +1204,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
</div>
|
||||
|
||||
{chain && (
|
||||
<ol className="space-y-1.5 rounded-md border border-border bg-muted/30 p-3">
|
||||
<ol className="space-y-1.5 rounded-lg border border-border bg-muted/30 p-3">
|
||||
{CHAIN_STEPS.map(step => {
|
||||
const state = chainStepState(step)
|
||||
return (
|
||||
|
||||
@@ -182,7 +182,7 @@ export function EmployeeBenefitsPanel({ employeeId, canWrite }: { employeeId: st
|
||||
)}
|
||||
|
||||
{adding && (
|
||||
<div className="space-y-4 rounded-md border bg-muted/30 p-4">
|
||||
<div className="space-y-4 rounded-lg border bg-muted/30 p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="benefit_type">{t('benefits_type')}</Label>
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function EmployeeTaxCard({
|
||||
checked={sido}
|
||||
onChange={(e) => setSido(e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="rounded border-border"
|
||||
className="rounded-sm border-border"
|
||||
/>
|
||||
<InfoTooltip content={t('tax_sidoinkomst_tooltip')}>
|
||||
{t('tax_sidoinkomst_label')}
|
||||
@@ -185,7 +185,7 @@ export default function EmployeeTaxCard({
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : tableNumber ? (
|
||||
<div className="flex items-baseline gap-2 rounded-md border border-input px-3 py-2">
|
||||
<div className="flex items-baseline gap-2 rounded-lg border border-input px-3 py-2">
|
||||
<span className="font-sans text-xl tabular-nums">{tableNumber}</span>
|
||||
{(municipality || rate != null) && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -195,7 +195,7 @@ export default function EmployeeTaxCard({
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="rounded-md border border-dashed border-input px-3 py-2 text-sm text-muted-foreground">
|
||||
<p className="rounded-lg border border-dashed border-input px-3 py-2 text-sm text-muted-foreground">
|
||||
{t('tax_table_pick_municipality')}
|
||||
</p>
|
||||
)}
|
||||
@@ -249,7 +249,7 @@ export default function EmployeeTaxCard({
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="rounded-md border border-dashed border-input px-3 py-3 text-sm text-muted-foreground">
|
||||
<p className="rounded-lg border border-dashed border-input px-3 py-3 text-sm text-muted-foreground">
|
||||
{sido
|
||||
? t('tax_no_table_sidoinkomst')
|
||||
: t('tax_no_table_f_skatt')}
|
||||
|
||||
@@ -165,7 +165,7 @@ export default function MunicipalityCombobox({
|
||||
{isOpen && filtered.length > 0 && (
|
||||
<div
|
||||
ref={listRef}
|
||||
className="absolute z-50 top-full left-0 mt-1 w-full max-h-[300px] overflow-y-auto rounded-md border border-input bg-card shadow-md"
|
||||
className="absolute z-50 top-full left-0 mt-1 w-full max-h-[300px] overflow-y-auto rounded-lg border border-input bg-card shadow-md"
|
||||
>
|
||||
{filtered.map((k, i) => {
|
||||
const isHighlighted = i === highlightedIndex
|
||||
|
||||
@@ -154,14 +154,14 @@ export function PaymentFilePanel({
|
||||
file downloads fine and only fails at upload, on the pay date.
|
||||
Say the delivery precondition up front instead. */}
|
||||
{format === 'pain001' && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
|
||||
<AlertTriangle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span className="text-muted-foreground">{t('pain001_agreement_warning')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{format === 'bg_lb' && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
|
||||
<AlertTriangle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span className="text-muted-foreground">
|
||||
{t('sunset_warning')}{' '}
|
||||
@@ -197,7 +197,7 @@ export function PaymentFilePanel({
|
||||
</button>
|
||||
|
||||
{showInstructions && (
|
||||
<div className="space-y-2 rounded-md border bg-muted/30 p-3 text-xs">
|
||||
<div className="space-y-2 rounded-lg border bg-muted/30 p-3 text-xs">
|
||||
{sortedBanks.map((bank) => (
|
||||
<div key={bank}>
|
||||
<strong className="text-foreground">
|
||||
@@ -215,7 +215,7 @@ export function PaymentFilePanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-start gap-2 rounded-md border border-dashed bg-muted/20 p-3 text-xs text-muted-foreground">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-dashed bg-muted/20 p-3 text-xs text-muted-foreground">
|
||||
<Info className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span>{t('open_payments_note')}</span>
|
||||
</div>
|
||||
|
||||
@@ -353,7 +353,7 @@ export function SalaryCalendar({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-md border bg-card">
|
||||
<div className="rounded-lg border bg-card">
|
||||
{/* Header */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -727,7 +727,7 @@ function BulkWorkedDialog({
|
||||
</div>
|
||||
|
||||
{conflicts.length > 0 && (
|
||||
<div className="space-y-1 rounded-md border border-border bg-muted/30 p-2 text-xs">
|
||||
<div className="space-y-1 rounded-lg border border-border bg-muted/30 p-2 text-xs">
|
||||
<div className="font-medium text-attn">
|
||||
{t('conflicts_worked', { count: conflicts.length })}
|
||||
</div>
|
||||
@@ -738,7 +738,7 @@ function BulkWorkedDialog({
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
|
||||
<div className="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -880,7 +880,7 @@ function BulkAbsenceDialog({
|
||||
</div>
|
||||
|
||||
{conflicts.length > 0 && (
|
||||
<div className="space-y-1 rounded-md border border-border bg-muted/30 p-2 text-xs">
|
||||
<div className="space-y-1 rounded-lg border border-border bg-muted/30 p-2 text-xs">
|
||||
<div className="font-medium text-attn">
|
||||
{t('conflicts_absence', { count: conflicts.length })}
|
||||
</div>
|
||||
@@ -891,7 +891,7 @@ function BulkAbsenceDialog({
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
|
||||
<div className="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -995,7 +995,7 @@ function DayInspectorDialog({
|
||||
|
||||
<div className="space-y-2">
|
||||
{isHourly && worked && (
|
||||
<div className="flex items-center justify-between rounded-md border bg-secondary/50 px-3 py-2">
|
||||
<div className="flex items-center justify-between rounded-lg border bg-secondary/50 px-3 py-2">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
@@ -1020,7 +1020,7 @@ function DayInspectorDialog({
|
||||
const meta = TYPE_META[a.absence_type]
|
||||
const Icon = meta.icon
|
||||
return (
|
||||
<div key={a.id} className={cn('flex items-center justify-between rounded-md border px-3 py-2', meta.pillClass)}>
|
||||
<div key={a.id} className={cn('flex items-center justify-between rounded-lg border px-3 py-2', meta.pillClass)}>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Icon className="h-4 w-4" />
|
||||
<div>
|
||||
@@ -1049,7 +1049,7 @@ function DayInspectorDialog({
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
|
||||
<div className="rounded-lg bg-destructive/10 p-2 text-xs text-destructive">{error}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ export function RunHeader({ run, canWrite, actionLoading, employeeCount, onDelet
|
||||
<DialogDescription className="space-y-3 pt-2 text-left">
|
||||
<span className="block">{t('correct_dialog_body', { period: periodLabel })}</span>
|
||||
{run.agi_generated_at && (
|
||||
<span className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
|
||||
<span className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
|
||||
<AlertTriangle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span>{t('correct_dialog_agi_warning')}</span>
|
||||
</span>
|
||||
|
||||
@@ -389,7 +389,7 @@ export function AgentMemoryPanel() {
|
||||
onClick={() => patch(row.id, { is_pinned: !row.is_pinned })}
|
||||
disabled={isBusy}
|
||||
className={cn(
|
||||
'mt-0.5 shrink-0 rounded-md p-1.5 transition-colors duration-150',
|
||||
'mt-0.5 shrink-0 rounded-sm p-1.5 transition-colors duration-150',
|
||||
row.is_pinned
|
||||
? 'bg-secondary text-foreground'
|
||||
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
||||
|
||||
@@ -50,7 +50,7 @@ const PROSE =
|
||||
'prose-h1:text-lg prose-h2:text-base prose-h3:text-sm prose-p:my-2 prose-p:leading-6 ' +
|
||||
'prose-strong:font-semibold prose-strong:text-foreground prose-ul:my-2 prose-li:my-0.5 ' +
|
||||
'prose-a:text-foreground prose-a:underline prose-a:underline-offset-2 ' +
|
||||
'prose-code:bg-secondary prose-code:rounded prose-code:px-1 prose-code:py-0.5 prose-code:text-xs ' +
|
||||
'prose-code:bg-secondary prose-code:rounded-sm prose-code:px-1 prose-code:py-0.5 prose-code:text-xs ' +
|
||||
'prose-code:before:content-none prose-code:after:content-none ' +
|
||||
'prose-table:my-2 prose-table:text-xs [&_table]:w-full ' +
|
||||
'[&_th]:border-b [&_th]:border-border [&_th]:py-1.5 [&_th]:px-2 [&_th]:text-left [&_th]:font-medium ' +
|
||||
|
||||
@@ -192,7 +192,7 @@ function CopyBlock({ text, copyAriaLabel }: { text: string; copyAriaLabel: strin
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<pre className="select-all rounded-md bg-muted p-4 pr-12 text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all">
|
||||
<pre className="select-all rounded-lg bg-muted p-4 pr-12 text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all">
|
||||
{text}
|
||||
</pre>
|
||||
<Button
|
||||
@@ -240,7 +240,7 @@ function ScopeCard({
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'flex min-h-[68px] cursor-pointer flex-col gap-1 rounded-md border p-2 transition-colors',
|
||||
'flex min-h-[68px] cursor-pointer flex-col gap-1 rounded-lg border p-2 transition-colors',
|
||||
checked
|
||||
? 'border-border bg-secondary'
|
||||
: 'border-border hover:bg-secondary/60'
|
||||
@@ -580,7 +580,7 @@ export function ApiKeysPanel() {
|
||||
|
||||
{/* Create key dialog */}
|
||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||
<DialogContent className="max-w-[calc(100vw-2rem)] rounded-2xl p-4 sm:max-w-3xl sm:p-6">
|
||||
<DialogContent className="max-w-[calc(100vw-2rem)] rounded-xl p-4 sm:max-w-3xl sm:p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('create_dialog_title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -600,7 +600,7 @@ export function ApiKeysPanel() {
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t('mode_label')}</Label>
|
||||
<div className="inline-flex rounded-md border p-0.5" role="radiogroup" aria-label={t('mode_label')}>
|
||||
<div className="inline-flex rounded-full border p-0.5" role="radiogroup" aria-label={t('mode_label')}>
|
||||
{(['live', 'test'] as const).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
@@ -609,7 +609,7 @@ export function ApiKeysPanel() {
|
||||
aria-checked={newKeyMode === m}
|
||||
onClick={() => setNewKeyMode(m)}
|
||||
className={cn(
|
||||
'rounded-[5px] px-3 py-1.5 text-xs transition-colors',
|
||||
'rounded-full px-3 py-1.5 text-xs transition-colors',
|
||||
newKeyMode === m
|
||||
? 'bg-secondary text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
@@ -683,7 +683,7 @@ export function ApiKeysPanel() {
|
||||
{hasSodConflict && (
|
||||
<div
|
||||
role="alert"
|
||||
className="flex items-start gap-2 rounded-md border border-border bg-muted/30 p-3 text-xs text-foreground"
|
||||
className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-foreground"
|
||||
>
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-warning" />
|
||||
<p className="leading-snug">{t('sod_warning')}</p>
|
||||
|
||||
@@ -277,7 +277,7 @@ export function BackupDownloadForm() {
|
||||
id="backup-period"
|
||||
value={selectedPeriodId}
|
||||
onChange={(e) => setSelectedPeriodId(e.target.value)}
|
||||
className="flex h-10 w-full max-w-xs rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
className="flex h-10 w-full max-w-xs rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
disabled={periods === null || periods.length === 0}
|
||||
>
|
||||
{/* The confirmed-empty option only after a confirmed empty
|
||||
@@ -308,7 +308,7 @@ export function BackupDownloadForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-border bg-muted/30 p-3 text-sm">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-3 text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
{isLoadingEstimate ? (
|
||||
|
||||
@@ -155,7 +155,7 @@ export function BankNameCombobox({ defaultValue = '', value: controlledValue, on
|
||||
ref={listRef}
|
||||
id="bank-name-listbox"
|
||||
role="listbox"
|
||||
className="absolute z-50 mt-1 max-h-56 w-full overflow-auto rounded-md border border-border bg-popover shadow-md"
|
||||
className="absolute z-50 mt-1 max-h-56 w-full overflow-auto rounded-lg border border-border bg-popover shadow-md"
|
||||
>
|
||||
{filtered.map((bank, i) => (
|
||||
<li
|
||||
@@ -176,7 +176,7 @@ export function BankNameCombobox({ defaultValue = '', value: controlledValue, on
|
||||
<img
|
||||
src={bank.logo}
|
||||
alt=""
|
||||
className="h-5 w-5 flex-shrink-0 rounded object-contain"
|
||||
className="h-5 w-5 flex-shrink-0 rounded-sm object-contain"
|
||||
/>
|
||||
) : (
|
||||
<svg
|
||||
|
||||
@@ -150,7 +150,7 @@ export function InvoiceEmailTextsSettings({ settings, onUpdate }: InvoiceEmailTe
|
||||
<p>
|
||||
{t('placeholders_help')}{' '}
|
||||
{INVOICE_EMAIL_PLACEHOLDER_KEYS.map((key) => (
|
||||
<code key={key} className="mr-1 rounded bg-muted px-1 text-xs">{`{${key}}`}</code>
|
||||
<code key={key} className="mr-1 rounded-sm bg-muted px-1 text-xs">{`{${key}}`}</code>
|
||||
))}
|
||||
</p>
|
||||
<p>{t('firstname_note')}</p>
|
||||
|
||||
@@ -177,7 +177,7 @@ export function OAuthClientsPanel() {
|
||||
<DialogDescription>
|
||||
{t.rich('register_dialog_description', {
|
||||
bold: (chunks) => <span className="font-medium">{chunks}</span>,
|
||||
code: (chunks) => <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">{chunks}</code>,
|
||||
code: (chunks) => <code className="rounded-sm bg-muted px-1 py-0.5 font-mono text-[11px]">{chunks}</code>,
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -242,7 +242,7 @@ export function SettingsSeg<T extends string>({
|
||||
aria-pressed={o.value === value}
|
||||
onClick={() => onChange(o.value)}
|
||||
className={cn(
|
||||
'rounded-md px-3 py-1 text-xs transition-colors duration-150 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
'rounded-sm px-3 py-1 text-xs transition-colors duration-150 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
o.value === value
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
|
||||
@@ -237,7 +237,7 @@ export function TemplateForm({
|
||||
{lines.map((line, i) => {
|
||||
const showRatioInput = showRatio && line.type === 'business'
|
||||
return (
|
||||
<div key={i} className="rounded-md border border-border p-2 space-y-1.5">
|
||||
<div key={i} className="rounded-lg border border-border p-2 space-y-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={line.account}
|
||||
|
||||
@@ -115,7 +115,7 @@ export function BankingSettingsContent() {
|
||||
setIsAccessDenied(false)
|
||||
setShowHbPoaHint(false)
|
||||
}}
|
||||
className="shrink-0 rounded-md p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
className="shrink-0 rounded-sm p-1 text-muted-foreground transition-colors duration-150 hover:text-foreground"
|
||||
aria-label={t('dismiss_aria')}
|
||||
>
|
||||
<span className="text-lg leading-none">×</span>
|
||||
|
||||
@@ -177,7 +177,7 @@ export function SkattekontoMatchDialog({
|
||||
)}
|
||||
|
||||
{!loading && candidates && candidates.length > 0 && (
|
||||
<div className="max-h-[420px] overflow-y-auto rounded-md border">
|
||||
<div className="max-h-[420px] overflow-y-auto rounded-lg border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
@@ -1724,7 +1724,7 @@ export default function NewSupplierInvoiceForm({
|
||||
{/* Eget utlägg-toggle. När den är på bokas verifikatet direkt mot
|
||||
skuld till ägare (2893/2018) istället för leverantörsskuld (2440),
|
||||
och fakturan får status "Betalad" direkt. */}
|
||||
<div className="flex items-start gap-3 p-3 rounded-md border bg-muted/30">
|
||||
<div className="flex items-start gap-3 p-3 rounded-lg border bg-muted/30">
|
||||
<Controller
|
||||
name="paid_with_private_funds"
|
||||
control={control}
|
||||
@@ -2302,17 +2302,17 @@ export default function NewSupplierInvoiceForm({
|
||||
<div className="mt-4 pt-4 border-t flex flex-wrap gap-2 text-xs">
|
||||
<span className="text-muted-foreground">{t('ai_totals_label')}</span>
|
||||
{extractedData.totals.subtotal != null && (
|
||||
<span className="px-2 py-1 rounded bg-muted tabular-nums">
|
||||
<span className="px-2 py-1 rounded-sm bg-muted tabular-nums">
|
||||
{t('ai_net', { amount: formatAmount(extractedData.totals.subtotal) })}
|
||||
</span>
|
||||
)}
|
||||
{extractedData.totals.vatAmount != null && (
|
||||
<span className="px-2 py-1 rounded bg-muted tabular-nums">
|
||||
<span className="px-2 py-1 rounded-sm bg-muted tabular-nums">
|
||||
{t('ai_vat', { amount: formatAmount(extractedData.totals.vatAmount) })}
|
||||
</span>
|
||||
)}
|
||||
{extractedData.totals.total != null && (
|
||||
<span className="px-2 py-1 rounded bg-muted tabular-nums">
|
||||
<span className="px-2 py-1 rounded-sm bg-muted tabular-nums">
|
||||
{t('ai_total', { amount: formatAmount(extractedData.totals.total) })}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -327,7 +327,7 @@ export default function PaymentFileDialog({
|
||||
|
||||
{/* The file downloads fine and only fails at the bank if the
|
||||
upload agreement is missing: say the precondition up front. */}
|
||||
<div className="flex items-start gap-2 rounded-md border border-border p-3 text-xs">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border p-3 text-xs">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<span className="text-muted-foreground">{t('pain001_agreement_warning')}</span>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function BankSyncSinceLastVisit() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center gap-2 rounded-md border border-border bg-secondary px-2.5 py-1 text-xs text-foreground">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-border bg-secondary px-2.5 py-1 text-xs text-foreground">
|
||||
<span>
|
||||
{count === 1
|
||||
? t('bank_sync_new_since_last_visit_one')
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function BankSyncStatusChip() {
|
||||
return (
|
||||
<Link
|
||||
href="/settings/banking"
|
||||
className="inline-flex items-center gap-1.5 rounded-md border border-destructive/40 bg-destructive/5 px-2.5 py-1 text-xs text-destructive transition-colors hover:bg-destructive/10"
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-destructive/40 bg-destructive/5 px-2.5 py-1 text-xs text-destructive transition-colors hover:bg-destructive/10"
|
||||
>
|
||||
<AlertTriangle className="h-3.5 w-3.5" />
|
||||
<span>
|
||||
@@ -128,7 +128,7 @@ export default function BankSyncStatusChip() {
|
||||
return (
|
||||
<Link
|
||||
href="/settings/banking"
|
||||
className="inline-flex items-center gap-1.5 rounded-md border border-border bg-muted/30 px-2.5 py-1 text-xs text-attn transition-colors hover:bg-muted/50"
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-attn transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<AlertTriangle className="h-3.5 w-3.5" />
|
||||
<span>
|
||||
@@ -143,7 +143,7 @@ export default function BankSyncStatusChip() {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex cursor-help items-center gap-1.5 rounded-md border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground">
|
||||
<span className="inline-flex cursor-help items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground">
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
<span>
|
||||
{t('bank_sync_auto_nightly')}
|
||||
|
||||
@@ -212,7 +212,7 @@ export default function DuplicateBookingDialog({
|
||||
{canIgnore && <> {t('dialog_duplicate_ignore_hint')}</>}
|
||||
</p>
|
||||
{candidate && (
|
||||
<div className="space-y-1 rounded-md border bg-muted/30 p-3">
|
||||
<div className="space-y-1 rounded-lg border bg-muted/30 p-3">
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
<span className="font-medium">
|
||||
{candidate.voucher_label
|
||||
|
||||
@@ -879,7 +879,7 @@ export default function InvoiceMatchDialog({
|
||||
{/* Side toggle: segmented control. Clicking either
|
||||
button picks that side; the amount stays the
|
||||
same. */}
|
||||
<div className="inline-flex rounded-md border bg-background overflow-hidden h-9">
|
||||
<div className="inline-flex rounded-lg border bg-background overflow-hidden h-9">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateEditLine(i, { side: 'debit' })}
|
||||
|
||||
@@ -159,7 +159,7 @@ export default function TransactionAttachDocumentDialog({
|
||||
disabled={isAttaching}
|
||||
/>
|
||||
{pickedDoc && (
|
||||
<div className="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50">
|
||||
<div className="flex items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50">
|
||||
<FileText className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="truncate flex-1">
|
||||
{pickedDoc.supplier_name ?? pickedDoc.file_name}
|
||||
|
||||
@@ -311,7 +311,7 @@ export default function TransactionBookingDialog({
|
||||
{pickedInboxDocs.map((doc) => (
|
||||
<div
|
||||
key={doc.document_id}
|
||||
className="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50"
|
||||
className="flex items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50"
|
||||
>
|
||||
<FileText className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="truncate flex-1">
|
||||
|
||||
@@ -6,6 +6,7 @@ import Link from 'next/link'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DataListEmpty } from '@/components/ui/data-list'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -148,25 +149,11 @@ export default function TransactionHistoryList({
|
||||
<div className="space-y-4">
|
||||
{/* Business/private seg; the source chip lives in the page toolbar. */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
{FILTERS.map(({ key, labelKey }) => (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={filter === key}
|
||||
onClick={() => setFilter(key)}
|
||||
className={cn(
|
||||
'rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
filter === key
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{t(labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={filter}
|
||||
onChange={setFilter}
|
||||
options={FILTERS.map(({ key, labelKey }) => ({ value: key, label: t(labelKey) }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function VatTreatmentSelect<T extends VatSelectValue>({
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
className={cn(
|
||||
'relative flex w-full cursor-default select-none items-start rounded-md py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50'
|
||||
'relative flex w-full cursor-default select-none items-start rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50'
|
||||
)}
|
||||
>
|
||||
<span className="absolute left-2 top-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
|
||||
@@ -12,7 +12,7 @@ const Checkbox = React.forwardRef<
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded border border-input transition-colors duration-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/35 focus-visible:border-primary/60 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground hover:border-primary/50",
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-input transition-colors duration-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/35 focus-visible:border-primary/60 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground hover:border-primary/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -37,7 +37,7 @@ const DialogContent = React.forwardRef<
|
||||
<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)] 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-98 data-[state=open]:zoom-in-98 sm:rounded-lg",
|
||||
"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)] 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-98 data-[state=open]:zoom-in-98 sm:rounded-xl",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -37,7 +37,7 @@ const DropdownMenuItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
@@ -53,7 +53,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-md py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function OpenInNewTab({
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className={cn(
|
||||
HOVER_REVEAL_CLASS,
|
||||
'relative inline-flex shrink-0 items-center rounded p-1 text-muted-foreground',
|
||||
'relative inline-flex shrink-0 items-center rounded-sm p-1 text-muted-foreground',
|
||||
// The icon stays 14px so the row keeps its density, but the pointer
|
||||
// target is padded out to the 40px the design rules require.
|
||||
'before:absolute before:left-1/2 before:top-1/2 before:h-10 before:w-10',
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface SegmentedControlOption<T extends string> {
|
||||
value: T
|
||||
/** Segment label. Pass a fragment for custom trailing content (e.g. a muted count). */
|
||||
label: React.ReactNode
|
||||
/** Renders the standard count chip after the label when > 0. */
|
||||
count?: number
|
||||
}
|
||||
|
||||
interface SegmentedControlProps<T extends string> {
|
||||
value: T
|
||||
onChange: (value: T) => void
|
||||
options: SegmentedControlOption<T>[]
|
||||
'aria-label'?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The toolbar segmented control (view/mode switcher). Pill-in-pill per the
|
||||
* radius ladder: interactive toolbar controls are pills, and pill nesting is
|
||||
* concentric by construction. One fixed height (h-8) shared with
|
||||
* ToolbarSearch and ContextPicker so a toolbar row reads as one line.
|
||||
*/
|
||||
export function SegmentedControl<T extends string>({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
className,
|
||||
...aria
|
||||
}: SegmentedControlProps<T>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex h-8 shrink-0 items-center gap-0.5 rounded-full bg-muted/70 p-[3px]',
|
||||
className,
|
||||
)}
|
||||
role="tablist"
|
||||
aria-label={aria['aria-label']}
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={value === opt.value}
|
||||
onClick={() => onChange(opt.value)}
|
||||
className={cn(
|
||||
'inline-flex h-full items-center gap-1.5 rounded-full px-3.5 text-[12.5px] transition-colors duration-150',
|
||||
value === opt.value
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{opt.label}
|
||||
{typeof opt.count === 'number' && opt.count > 0 && (
|
||||
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
|
||||
{opt.count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -117,7 +117,7 @@ const SelectItem = React.forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-md py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-secondary focus:text-secondary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -6,7 +6,7 @@ function Skeleton({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
className={cn("animate-pulse rounded-sm bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -168,7 +168,7 @@ export function SplitButton({
|
||||
runOption(option)
|
||||
}}
|
||||
className={cn(
|
||||
'flex w-full items-start gap-2.5 rounded-md px-2.5 py-2 text-left transition-colors',
|
||||
'flex w-full items-start gap-2.5 rounded-sm px-2.5 py-2 text-left transition-colors',
|
||||
option.disabled
|
||||
? 'cursor-not-allowed text-muted-foreground/40'
|
||||
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
||||
|
||||
@@ -13,7 +13,7 @@ const TabsList = React.forwardRef<
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground max-w-full overflow-x-auto scrollbar-hide",
|
||||
"inline-flex h-10 items-center justify-center rounded-full bg-muted p-1 text-muted-foreground max-w-full overflow-x-auto scrollbar-hide",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -28,7 +28,7 @@ const TabsTrigger = React.forwardRef<
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -77,7 +77,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-3 py-1.5 text-sm transition-colors',
|
||||
'flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-lg border border-input bg-transparent px-3 py-1.5 text-sm transition-colors',
|
||||
'focus-within:ring-1 focus-within:ring-ring',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
className
|
||||
@@ -87,7 +87,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
||||
{tags.map((tag, i) => (
|
||||
<span
|
||||
key={`${tag}-${i}`}
|
||||
className="inline-flex items-center gap-1 rounded-md bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground"
|
||||
className="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground"
|
||||
>
|
||||
{tag}
|
||||
{!disabled && (
|
||||
@@ -97,7 +97,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
||||
e.stopPropagation()
|
||||
removeTag(i)
|
||||
}}
|
||||
className="rounded-sm opacity-60 hover:opacity-100 focus:outline-none"
|
||||
className="rounded-full opacity-60 hover:opacity-100 focus:outline-none"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user