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
+16
-1
@@ -54,6 +54,7 @@ Decided during the 2026-07 concept work (dev_docs/ui_migration_plan.md); they ap
|
||||
13. **Overlays**: centered modal for create/confirm (template, assistant, confirmations, settings); right slide-over for reviewing an object (Granskning detail). Both with veil, Esc, and click-outside.
|
||||
14. **Manual base, AI as opt-in.** Base flows work without AI; AI entry points are clearly labeled discrete choices (e.g. "Skapa med assistenten") and no AI suggestion posts without Granskning.
|
||||
15. **Settings speak "Fönster"** (founder-chosen 2026-07-25, applies to every settings tab). Settings content is flat hairline rows, never boxed cards: `SettingsGroup` / `SettingsRow` / `SettingsSectionHeader` from `components/settings/SettingsRows.tsx`. Toggles are switches, not checkboxes. Saving is a sticky bar that fades in only when the form is dirty (`components/settings/SettingsFormWrapper.tsx`); no always-visible save button. The settings surface is a 920x680 modal (`components/settings/SettingsModal.tsx`); switching tabs inside it updates the URL shallowly via `history.replaceState` (`SettingsRail.tsx`), never `router.replace`, which would remount the modal.
|
||||
16. **One radius per role (the radius ladder, 2026-08-13).** Four tiers, tied to what a thing IS: pills for interactive toolbar controls (buttons, chips, pickers, segmented controls, toolbar search, count badges), `rounded-xl` (12px) for the overlay/panel tier (page panel, dialogs, slide-overs, command palette), `rounded-lg` (8px) for surfaces on the panel (cards, form fields, textareas, popover/dropdown/menu content, bordered boxes), `rounded-sm` (4px) for leaf elements nested inside 8px surfaces (menu items, checkboxes, kbd/code nubs, skeleton text lines). Toolbar controls share one height: `h-8`. `rounded-md`, bare `rounded`, `rounded-2xl` and arbitrary `rounded-[Npx]` are dead vocabulary, enforced by `check:guards` (off-ladder-radius). Nesting is concentric: pill-in-pill by construction, `rounded-sm` items inside `rounded-lg` content with ~4px inset.
|
||||
|
||||
## Design System Tokens
|
||||
|
||||
@@ -72,6 +73,17 @@ Decided during the 2026-07 concept work (dev_docs/ui_migration_plan.md); they ap
|
||||
|
||||
Compact metric cards (e.g. dashboard tiles, salary KPI row) use `p-4`. Detail cards use `p-6`. Never mix `p-5`.
|
||||
|
||||
**Radius ladder** (convention 16; enforced by `check:guards` off-ladder-radius). Radius communicates role, and every role has exactly one:
|
||||
|
||||
| Tier | Class | Use for |
|
||||
|---|---|---|
|
||||
| pill | `rounded-full` | Buttons, chips, badges, context pickers, segmented controls, toolbar search, count nubs, dots, avatars |
|
||||
| 12px | `rounded-xl` | Page panel, dialogs, slide-overs, command palette, hero surfaces |
|
||||
| 8px | `rounded-lg` | Cards, form inputs, textareas, popover/dropdown/menu content, bordered boxes, toasts |
|
||||
| 4px | `rounded-sm` | Menu items, checkboxes, kbd/code/account-number nubs, skeleton text lines, small nested leaves |
|
||||
|
||||
**Forbidden radii:** `rounded-md`, bare `rounded`, `rounded-2xl`+, `rounded-[Npx]`. A search field is a pill in a toolbar (`ToolbarSearch`) and `rounded-lg` in a form or dialog (`Input`): the row it sits in decides, and mixing shapes in one row is the bug the ladder exists to prevent.
|
||||
|
||||
**Layout (frame layout).**
|
||||
- The dashboard wrapper is `bg-frame` (`--frame: 40 18% 96%` light, `0 0% 5%` dark). `<main>` is the page panel: `bg-background rounded-xl border border-border`, 10px margin against the frame, own inner scroll (`md:h-[calc(100vh-20px)] md:overflow-y-auto`). Defined once as `MAIN_PANEL_CLASS` in `app/(dashboard)/layout.tsx`; never restyle per page.
|
||||
- The panel is the desktop scroll container: `position: sticky` binds to it automatically; never assume `window` scroll on dashboard pages. Scroll reset on navigation lives in `MainContainer`.
|
||||
@@ -91,6 +103,8 @@ Compact metric cards (e.g. dashboard tiles, salary KPI row) use `p-4`. Detail ca
|
||||
| No-data state | `components/ui/empty-state.tsx` `EmptyState` | Don't hand-roll `<div className="flex flex-col items-center py-12">…</div>`. Preset variants exist (`EmptyInvoices`, `EmptyCustomers`, `EmptyTransactions`, etc.). |
|
||||
| Loading placeholder | `components/ui/skeleton.tsx` `<Skeleton>` | Don't hand-roll `bg-muted rounded animate-pulse` divs. |
|
||||
| Inline help / formulas | `components/ui/info-tooltip.tsx` `InfoTooltip` | Hover-revealed; don't use always-visible info buttons. |
|
||||
| View/mode switcher in a toolbar | `components/ui/segmented-control.tsx` `SegmentedControl` | Pill-in-pill tablist at the shared `h-8` toolbar height; `options` take an optional `count` for the standard count chip. Never hand-roll the `bg-muted/70` tablist div. |
|
||||
| Search in a page toolbar | `components/ui/toolbar-search.tsx` `ToolbarSearch` | Pill search at `h-8`, matching the chips and pickers beside it. Searches inside dialogs/pickers keep the regular `Input`. |
|
||||
| Fiscal year picker | `components/common/FyPicker.tsx` | The chip-dropdown context picker of convention 8 (wraps `ContextPicker`). `FiscalYearSelector` is the legacy pre-frame control: don't add new uses. Never use a raw `<select>` for fiscal periods. |
|
||||
| Settings rows / save | `components/settings/SettingsRows.tsx`, `SettingsFormWrapper.tsx` | Fönster language (convention 15): flat hairline rows, dirty-only sticky save bar. Don't hand-roll settings cards or per-field save buttons. |
|
||||
|
||||
@@ -125,6 +139,7 @@ Never render raw `{x.invoice_date}` directly: always route through `formatDate()
|
||||
- `active:scale-[...]` on buttons. Buttons do not bounce.
|
||||
- `bg-gradient-to-*` on page or card backgrounds. Flat surfaces only.
|
||||
- `font-medium` on display elements (`font-display`, h1/h2/h3, CardTitle, PageHeader title). Hedvig is single-weight by design.
|
||||
- `rounded-xl` (12px) on cards. Cards are `rounded-lg` (8px). `rounded-xl` survives on the page panel (frame layout) and prominent hero-style surfaces only.
|
||||
- `rounded-xl` (12px) on cards. Cards are `rounded-lg` (8px). `rounded-xl` is the overlay/panel tier: page panel, dialogs, slide-overs, hero surfaces.
|
||||
- Off-ladder radii: `rounded-md`, bare `rounded`, `rounded-2xl`, `rounded-[Npx]`. See the radius ladder above; `check:guards` fails on any of them in `app/` or `components/`.
|
||||
- Overriding the Button pill radius per call site (`rounded-lg`, `rounded-md` on a `<Button>`). Buttons are pills app-wide; the radius lives in `components/ui/button.tsx` alone.
|
||||
- Opacity-suffixed border classes (`border-border/30`, `border-border/60`) on cards and primary surfaces. Use full-opacity `border-border`: the new border token is calibrated for that.
|
||||
|
||||
@@ -978,3 +978,6 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-08-14] PR #1608 round-2 compliance finding (VMB class-3/4 VAT hole): account_override now books GROSS with no auto-VAT line unless the caller stated explicit VAT intent (vat_treatment or vat_amount). Deliberate divergence from v1 REST, which keeps the category-default standard_25 on overrides: MCP callers are agents, and a forgotten flag must under-deduct (lawful), never fabricate an ingående-moms deduction on a margin-scheme account. The review's class-appropriateness suggestion (block e.g. expense onto 27xx) was noted but not implemented: the approval gate is the guard, and a class matrix would block legitimate balance-sheet bookings v1 REST supports today.
|
||||
[2026-08-14] Skatteverket connections are per (user, company): the stacked UNIQUE(user_id) + UNIQUE(company_id) pair is replaced by UNIQUE(user_id, company_id), every token read/write is company-scoped, and /skattekonto/saldo returns 401 NOT_CONNECTED when the active company has no row. A multi-company operator connects each company separately; legacy NULL-company rows are ignored by scoped reads and just require a reconnect.
|
||||
[2026-08-14] Pristine empty-state gates key on all-years emptiness (a count probe), never on the scoped list result: a default fiscal-year selection is a scope, not a filter, and it made the bookkeeping start card unreachable on brand-new companies.
|
||||
[2026-08-13] Radius ladder locked (convention 16): 4 tiers by role (pill toolbar controls / rounded-xl overlays / rounded-lg surfaces / rounded-sm leaves); rounded-md, bare rounded, rounded-2xl and rounded-[Npx] retired app-wide, hard-failed by check:guards off-ladder-radius. Before: 7 radii in circulation with no rule; one toolbar row on /transactions mixed 4 shape languages.
|
||||
[2026-08-13] Toolbar shape language: pills won over "inputs stay rectangles". Search in a page toolbar is a pill (ToolbarSearch, h-8) matching chips/pickers/buttons beside it; the same search inside a dialog or form keeps rounded-lg Input. Rationale: convention 8 already made pickers pill chips and convention 3 made buttons pills, so the rectangle search/segmented were the odd ones out; one row = one shape reads as trust.
|
||||
[2026-08-13] Dialogs promoted 8px -> 12px (rounded-xl): SettingsModal, slide-over and CommandPalette were already 12px, so dialog.tsx was the overlay-tier outlier, not the rule.
|
||||
|
||||
@@ -371,7 +371,7 @@ export function LoginClient({ initialMethod }: { initialMethod: LoginMethod | nu
|
||||
<div className="min-h-dvh flex flex-col items-center justify-center bg-frame p-4">
|
||||
<div className="w-full max-w-sm animate-slide-up space-y-8">
|
||||
<div className="flex justify-center">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<Mail className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -432,7 +432,7 @@ export function LoginClient({ initialMethod }: { initialMethod: LoginMethod | nu
|
||||
<div className="w-full max-w-sm animate-slide-up">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<KeyRound className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -179,7 +179,7 @@ function MfaEnrollContent() {
|
||||
<div className="w-full max-w-sm animate-slide-up">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<ShieldCheck className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,7 +233,7 @@ function MfaEnrollContent() {
|
||||
<div className="w-full max-w-sm animate-slide-up">
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<ShieldCheck className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,7 +258,7 @@ function MfaEnrollContent() {
|
||||
Kan du inte skanna? Ange denna nyckel manuellt:
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 rounded-md border bg-muted/50 px-3 py-2 text-xs font-mono text-center break-all select-all">
|
||||
<code className="flex-1 rounded-sm border bg-muted/50 px-3 py-2 text-xs font-mono text-center break-all select-all">
|
||||
{secret}
|
||||
</code>
|
||||
<Button
|
||||
|
||||
@@ -174,7 +174,7 @@ function MfaVerifyContent() {
|
||||
<div className="w-full max-w-sm animate-slide-up">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<ShieldCheck className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -387,7 +387,7 @@ function RegisterPageContent() {
|
||||
<div className="min-h-dvh flex flex-col items-center justify-center bg-frame p-4">
|
||||
<div className="w-full max-w-sm animate-slide-up space-y-8">
|
||||
<div className="flex justify-center">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<Mail className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -440,7 +440,7 @@ function RegisterPageContent() {
|
||||
<div className="min-h-dvh flex flex-col items-center justify-center bg-frame p-4">
|
||||
<div className="w-full max-w-sm animate-slide-up space-y-8">
|
||||
<div className="flex justify-center">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<Mail className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -212,7 +212,7 @@ function ResetPasswordInner() {
|
||||
<div className="w-full max-w-sm animate-slide-up">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="h-14 w-14 rounded-2xl bg-primary/8 flex items-center justify-center">
|
||||
<div className="h-14 w-14 rounded-xl bg-primary/8 flex items-center justify-center">
|
||||
<KeyRound className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,13 +7,13 @@ import { useSearchParams, useRouter, usePathname } from 'next/navigation'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { getErrorMessage, type ErrorLocale } from '@/lib/errors/get-error-message'
|
||||
import { Plus, Search, Package, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
||||
import { Plus, Package, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
||||
import { ActivateAccountsDialog } from '@/components/bookkeeping/ActivateAccountsDialog'
|
||||
import {
|
||||
useSubmitWithAccountActivation,
|
||||
@@ -315,18 +315,15 @@ function ArticlesPageInner() {
|
||||
|
||||
{/* Toolbar: search, plus the currency scope far right (convention 8) */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<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={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value)
|
||||
setVisibleCount(INITIAL_VISIBLE_ROWS)
|
||||
}}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<ToolbarSearch
|
||||
containerClassName="min-w-[190px]"
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value)
|
||||
setVisibleCount(INITIAL_VISIBLE_ROWS)
|
||||
}}
|
||||
/>
|
||||
{/* A single-currency register needs no scope: the chip would be a
|
||||
control with one meaningful position. */}
|
||||
{availableCurrencies.length > 1 && (
|
||||
|
||||
@@ -325,7 +325,7 @@ export default function DisposeAssetPage({ params }: { params: Promise<{ id: str
|
||||
<SelectContent>{VAT_TREATMENTS.map((value) => <SelectItem key={value} value={value}>{t(`vat_${value}`)}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<div className="rounded-md bg-secondary/40 p-3 text-xs">
|
||||
<div className="rounded-lg bg-secondary/40 p-3 text-xs">
|
||||
<SummaryRow label={t('gross')} value={formatCurrency(proceedsNumber)} />
|
||||
<SummaryRow label={t('vat')} value={formatCurrency(vatAmount)} />
|
||||
<SummaryRow label={t('net')} value={formatCurrency(netProceeds)} strong />
|
||||
@@ -351,20 +351,20 @@ export default function DisposeAssetPage({ params }: { params: Promise<{ id: str
|
||||
</div>
|
||||
)}
|
||||
{jamkningAssessment && (
|
||||
<div className="rounded-md border border-border bg-secondary/40 p-3 text-sm">
|
||||
<div className="rounded-lg border border-border bg-secondary/40 p-3 text-sm">
|
||||
<SummaryRow label={t('adjustment_direction')} value={t(`direction_${jamkningAssessment.direction}`)} />
|
||||
<SummaryRow label={t('adjustment_amount')} value={formatCurrency(jamkningAssessment.amount)} strong />
|
||||
{jamkningAssessment.capped && <p className="mt-2 text-xs text-muted-foreground">{t('adjustment_capped')}</p>}
|
||||
</div>
|
||||
)}
|
||||
{disposalType === 'business_transfer' && (
|
||||
<div className="flex items-center gap-3 rounded-md border border-border p-3">
|
||||
<div className="flex items-center gap-3 rounded-lg border border-border p-3">
|
||||
<Switch id="businessTransfer" checked={businessTransferConfirmed} onCheckedChange={setBusinessTransferConfirmed} />
|
||||
<Label htmlFor="businessTransfer" className="cursor-pointer">{t('business_transfer_confirm')}</Label>
|
||||
</div>
|
||||
)}
|
||||
{disposalType === 'business_transfer' && transferNeedsDocument && (
|
||||
<div className="flex items-center gap-3 rounded-md border border-border p-3">
|
||||
<div className="flex items-center gap-3 rounded-lg border border-border p-3">
|
||||
<Switch id="adjustmentDocument" checked={adjustmentDocumentConfirmed} onCheckedChange={setAdjustmentDocumentConfirmed} />
|
||||
<Label htmlFor="adjustmentDocument" className="cursor-pointer">{t('adjustment_document_confirm')}</Label>
|
||||
</div>
|
||||
|
||||
@@ -842,7 +842,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRetagLine(line as unknown as RetagLine)}
|
||||
className="p-1 rounded text-muted-foreground/50 hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
className="p-1 rounded-sm text-muted-foreground/50 hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
aria-label="Ändra dimensioner"
|
||||
title="Ändra dimensioner (påverkar endast internredovisningen)"
|
||||
>
|
||||
@@ -932,7 +932,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRetagLine(line as unknown as RetagLine)}
|
||||
className="p-1 rounded text-muted-foreground/50 hover:text-foreground transition-colors"
|
||||
className="p-1 rounded-sm text-muted-foreground/50 hover:text-foreground transition-colors"
|
||||
aria-label="Ändra dimensioner"
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
@@ -996,7 +996,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
<li key={`${ref.type}-${ref.id}`}>
|
||||
<Link
|
||||
href={ref.type === 'invoice' ? `/invoices/${ref.id}` : `/supplier-invoices/${ref.id}`}
|
||||
className="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-muted/50 hover:bg-secondary/60 transition-colors"
|
||||
className="flex items-center gap-2 text-sm py-1.5 px-2 rounded-sm bg-muted/50 hover:bg-secondary/60 transition-colors"
|
||||
>
|
||||
<FileText className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="truncate">
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { EmptyState } from '@/components/ui/empty-state'
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS, RowFoldout } from '@/components/ui/dry-table'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
||||
@@ -211,35 +212,15 @@ export default function AccrualSchedulesPage() {
|
||||
) : null}
|
||||
|
||||
{/* Toolbar: status seg (concept scene 33) */}
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
{(
|
||||
[
|
||||
{ key: 'active', label: 'Aktiva', count: activeCount },
|
||||
{ key: 'completed', label: 'Avslutade', count: null },
|
||||
{ key: 'all', label: 'Alla', count: null },
|
||||
] as const
|
||||
).map(({ key, label, count }) => (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={statusFilter === key}
|
||||
onClick={() => setStatusFilter(key)}
|
||||
className={`inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
|
||||
statusFilter === key
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
{count !== null && count > 0 && (
|
||||
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={statusFilter}
|
||||
onChange={setStatusFilter}
|
||||
options={[
|
||||
{ value: 'active', label: 'Aktiva', count: activeCount ?? undefined },
|
||||
{ value: 'completed', label: 'Avslutade' },
|
||||
{ value: 'all', label: 'Alla' },
|
||||
]}
|
||||
/>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
|
||||
@@ -522,7 +522,7 @@ function VacationStep({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{proposal ? (
|
||||
<div className="flex items-start justify-between gap-4 rounded-md border border-border p-4">
|
||||
<div className="flex items-start justify-between gap-4 rounded-lg border border-border p-4">
|
||||
<div className="flex-1 space-y-2">
|
||||
<p className="text-sm font-medium">{proposal.label}</p>
|
||||
<p className="text-xs text-muted-foreground">{proposal.description}</p>
|
||||
@@ -590,7 +590,7 @@ function AuditStep({
|
||||
</Label>
|
||||
</div>
|
||||
{state.enabled && (
|
||||
<div className="grid grid-cols-2 gap-4 rounded-md border border-border p-4">
|
||||
<div className="grid grid-cols-2 gap-4 rounded-lg border border-border p-4">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">Belopp (kr)</Label>
|
||||
<Input
|
||||
@@ -605,7 +605,7 @@ function AuditStep({
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">Konto</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={state.liabilityAccount}
|
||||
onChange={(e) =>
|
||||
onChange({ ...state, liabilityAccount: e.target.value as '2991' | '2992' })
|
||||
@@ -667,7 +667,7 @@ function AutoStep({
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="flex items-start gap-3 rounded-md border border-border p-3"
|
||||
className="flex items-start gap-3 rounded-lg border border-border p-3"
|
||||
>
|
||||
<Checkbox
|
||||
id={`auto-${key}`}
|
||||
@@ -808,7 +808,7 @@ function ManualEntryEditor({
|
||||
template.side === 'deferred_revenue' ? 'Intäktskonto' : 'Kostnadskonto'
|
||||
|
||||
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">{template.name}</p>
|
||||
<Button variant="ghost" size="sm" onClick={onRemove} className="h-7 px-2" aria-label="Ta bort">
|
||||
@@ -989,7 +989,7 @@ function ReviewStep({
|
||||
|
||||
function ReviewLine({ label, amount, note }: { label: string; amount: number; note?: string }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 rounded-md border border-border px-3 py-2">
|
||||
<div className="flex items-center justify-between gap-4 rounded-lg border border-border px-3 py-2">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium truncate">{label}</p>
|
||||
{note && <p className="text-xs text-muted-foreground">{note}</p>}
|
||||
|
||||
@@ -6,13 +6,13 @@ import { useLocale, useTranslations } from 'next-intl'
|
||||
import { useSearchParams, useRouter, usePathname } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { getErrorMessage, type ErrorLocale } from '@/lib/errors/get-error-message'
|
||||
import { Plus, Search, Users, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
||||
import { Plus, Users, Lock, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
||||
import { EmptyCustomers, EmptyState } from '@/components/ui/empty-state'
|
||||
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -293,18 +293,14 @@ function CustomersPageInner() {
|
||||
|
||||
{/* Toolbar: search (concept) */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="relative min-w-[220px] 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)
|
||||
setVisibleCount(INITIAL_VISIBLE_ROWS)
|
||||
}}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<ToolbarSearch
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value)
|
||||
setVisibleCount(INITIAL_VISIBLE_ROWS)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { PageHeader } from '@/components/ui/page-header'
|
||||
import { HelpPopover } from '@/components/ui/help-popover'
|
||||
import { AttnLine } from '@/components/ui/attn-line'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -2048,7 +2049,7 @@ function CSVDataImportWizard() {
|
||||
onClick={() => setEntity(opt.value)}
|
||||
aria-pressed={selected}
|
||||
className={cn(
|
||||
'relative h-9 rounded-md border px-4 text-sm font-medium transition-colors',
|
||||
'relative h-9 rounded-lg border px-4 text-sm font-medium transition-colors',
|
||||
selected
|
||||
? 'border-foreground bg-foreground text-background'
|
||||
: 'border-border bg-card text-foreground hover:border-foreground/30 hover:bg-muted',
|
||||
@@ -2203,29 +2204,14 @@ export default function ImportPage() {
|
||||
{isSandbox && <AttnLine>{t('sandbox_disabled')}</AttnLine>}
|
||||
|
||||
{/* Importera / Exportera as separate tabs (house seg), like before */}
|
||||
<div className="inline-flex shrink-0 gap-0.5 rounded-lg bg-muted/70 p-[3px]" role="tablist">
|
||||
{(
|
||||
[
|
||||
{ key: 'import', label: t('tab_import') },
|
||||
{ key: 'export', label: t('tab_export') },
|
||||
] as const
|
||||
).map(({ key, label }) => (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={view === key}
|
||||
onClick={() => handleViewChange(key)}
|
||||
className={`rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
|
||||
view === key
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={view}
|
||||
onChange={handleViewChange}
|
||||
options={[
|
||||
{ value: 'import', label: t('tab_import') },
|
||||
{ value: 'export', label: t('tab_export') },
|
||||
]}
|
||||
/>
|
||||
|
||||
{view === 'import' ? (
|
||||
<div>
|
||||
@@ -2370,7 +2356,7 @@ export default function ImportPage() {
|
||||
<label className="flex cursor-pointer items-start gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-0.5 h-4 w-4 rounded border-border"
|
||||
className="mt-0.5 h-4 w-4 rounded-sm border-border"
|
||||
checked={exportExcludeClosing}
|
||||
onChange={(e) => setExportExcludeClosing(e.target.checked)}
|
||||
/>
|
||||
@@ -2551,7 +2537,7 @@ function ImportRow({
|
||||
// with the inverse lift in dark mode.
|
||||
function LogoChip({ src, name, mono = false }: { src: string; name: string; mono?: boolean }) {
|
||||
return (
|
||||
<span className="flex items-center gap-2 rounded border border-border bg-muted/30 px-2 py-1">
|
||||
<span className="flex items-center gap-2 rounded-sm border border-border bg-muted/30 px-2 py-1">
|
||||
<img
|
||||
src={src}
|
||||
alt=""
|
||||
|
||||
@@ -1580,7 +1580,7 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
|
||||
return (
|
||||
<li
|
||||
key={p.id}
|
||||
className="flex items-center justify-between gap-3 px-2 py-2 text-sm transition-colors hover:bg-secondary/60 rounded"
|
||||
className="flex items-center justify-between gap-3 px-2 py-2 text-sm transition-colors hover:bg-secondary/60 rounded-sm"
|
||||
>
|
||||
<span className="tabular-nums text-muted-foreground">
|
||||
{formatDate(p.payment_date)}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
|
||||
import { RowStatus, type RowStatusDescriptor } from '@/components/ui/row-status'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Dialog, DialogContent, DialogTitle, DialogVeil } from '@/components/ui/dialog'
|
||||
import { DataListEmpty } from '@/components/ui/data-list'
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
ArrowUp,
|
||||
ArrowUpDown,
|
||||
Plus,
|
||||
Search,
|
||||
ReceiptText,
|
||||
Repeat,
|
||||
FileInput,
|
||||
@@ -620,18 +619,15 @@ export default function InvoicesPage() {
|
||||
annotation: tabCounts[tab] > 0 ? String(tabCounts[tab]) : undefined,
|
||||
}))}
|
||||
/>
|
||||
<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={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value)
|
||||
resetPaging()
|
||||
}}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<ToolbarSearch
|
||||
containerClassName="min-w-[190px]"
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value)
|
||||
resetPaging()
|
||||
}}
|
||||
/>
|
||||
<div className="ml-auto">
|
||||
<FyPicker
|
||||
value={fyPeriodId}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function DashboardLoading() {
|
||||
full-width list, so that shape stays there. */}
|
||||
<aside className="hidden md:flex md:w-12 shrink-0 flex-col items-center border-r border-border bg-card/40 py-3 gap-2">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
<Skeleton className="h-8 w-8 rounded-md" />
|
||||
<Skeleton className="h-8 w-8" />
|
||||
</aside>
|
||||
<aside className="flex w-full flex-col border-r border-border bg-card/40 md:hidden shrink-0">
|
||||
<div className="space-y-3 border-b border-border px-5 py-4">
|
||||
@@ -42,7 +42,7 @@ export default function DashboardLoading() {
|
||||
<Skeleton className="h-2.5 w-20" />
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton className="h-8 w-full rounded-md" />
|
||||
<Skeleton className="h-8 w-full" />
|
||||
</div>
|
||||
<div className="space-y-1 p-3">
|
||||
{['w-40', 'w-48', 'w-36', 'w-44'].map((w, i) => (
|
||||
@@ -80,7 +80,7 @@ export default function DashboardLoading() {
|
||||
key={i}
|
||||
className="flex items-start gap-3 border-b border-border px-1 py-3.5"
|
||||
>
|
||||
<Skeleton className="mt-px h-[15px] w-[15px] shrink-0 rounded" />
|
||||
<Skeleton className="mt-px h-[15px] w-[15px] shrink-0" />
|
||||
<Skeleton className={`h-3.5 ${w}`} />
|
||||
<Skeleton className="ml-auto h-5 w-7 shrink-0 rounded-full" />
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { DataListEmpty, DataListLoading } from '@/components/ui/data-list'
|
||||
import { ContextPicker } from '@/components/common/ContextPicker'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { HOVER_REVEAL_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
import {
|
||||
SlideOver,
|
||||
@@ -229,7 +230,7 @@ function useAccountNamesSource(): Record<string, string> {
|
||||
function PeriodLockBanner({ period }: { period: PeriodStatusShape }) {
|
||||
const lockedThrough = period.lock_date ? formatDate(period.lock_date) : null
|
||||
return (
|
||||
<div className="flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm">
|
||||
<Lock className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-destructive">
|
||||
@@ -704,7 +705,7 @@ export default function PendingOperationsPage() {
|
||||
</div>
|
||||
|
||||
{conversationFilter && (
|
||||
<div className="flex items-center justify-between rounded-md border bg-muted/30 px-3 py-2 text-sm">
|
||||
<div className="flex items-center justify-between rounded-lg border bg-muted/30 px-3 py-2 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageSquare className="h-4 w-4 text-muted-foreground" />
|
||||
<span>
|
||||
@@ -733,30 +734,15 @@ export default function PendingOperationsPage() {
|
||||
{/* Toolbar (concept): status seg left, source picker (convention 8)
|
||||
far right. The count chip rides only on Väntar: it is the queue. */}
|
||||
<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">
|
||||
{SEG_TABS.map(({ tab, labelKey }) => (
|
||||
<button
|
||||
key={tab}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150',
|
||||
activeTab === tab
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{t(labelKey)}
|
||||
{tab === 'pending' && (pendingCount ?? 0) > 0 && (
|
||||
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
|
||||
{pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={activeTab}
|
||||
onChange={setActiveTab}
|
||||
options={SEG_TABS.map(({ tab, labelKey }) => ({
|
||||
value: tab,
|
||||
label: t(labelKey),
|
||||
count: tab === 'pending' ? pendingCount ?? 0 : undefined,
|
||||
}))}
|
||||
/>
|
||||
<div className="ml-auto">
|
||||
<ContextPicker
|
||||
value={sourceFilter}
|
||||
@@ -1190,7 +1176,7 @@ export default function PendingOperationsPage() {
|
||||
>
|
||||
<div className="space-y-3 text-sm">
|
||||
<p>{t('bulk_confirm_intro')}</p>
|
||||
<ul className="space-y-1 rounded-md border bg-muted/30 px-3 py-2">
|
||||
<ul className="space-y-1 rounded-lg border bg-muted/30 px-3 py-2">
|
||||
{selectedBreakdown.map(({ type, count }) => (
|
||||
<li key={type} className="flex justify-between font-mono tabular-nums">
|
||||
<span className="font-sans">{bulkActionLabel(type, count, t)}</span>
|
||||
|
||||
@@ -163,7 +163,7 @@ export default function SalaryRunEmployeeDetailPage({
|
||||
>
|
||||
<ArrowLeft className="mr-1 h-3.5 w-3.5" /> {t('back_to_run')}
|
||||
</Link>
|
||||
<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">
|
||||
{error ?? t('error_load_employee')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -321,7 +321,7 @@ export default function SalaryRunEmployeeDetailPage({
|
||||
function SummaryCard({ label, value, accent, overridden }: { label: string; value: number; accent?: boolean; overridden?: boolean }) {
|
||||
const t = useTranslations('salary_run_employee')
|
||||
return (
|
||||
<div className={cn('rounded-md border bg-card p-3', accent && 'ring-1 ring-primary/40')}>
|
||||
<div className={cn('rounded-lg border bg-card p-3', accent && 'ring-1 ring-primary/40')}>
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
{label}
|
||||
{/* The override is an exception, so it is a chip, not a second ring:
|
||||
@@ -337,7 +337,7 @@ function SummaryCard({ label, value, accent, overridden }: { label: string; valu
|
||||
function AbsenceCount({ label, days }: { label: string; days: number }) {
|
||||
const t = useTranslations('salary_run_employee')
|
||||
return (
|
||||
<div className="rounded-md border bg-muted/30 px-3 py-2">
|
||||
<div className="rounded-lg border bg-muted/30 px-3 py-2">
|
||||
<div className="text-[11px] text-muted-foreground">{label}</div>
|
||||
<div className="text-sm font-medium tabular-nums">{t('days_count', { days })}</div>
|
||||
</div>
|
||||
|
||||
@@ -896,7 +896,7 @@ export default function SalaryRunPage({ params }: { params: Promise<{ id: string
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{approveOverride && approveOverride.length > 0 && (
|
||||
<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 text-warning" />
|
||||
<ul className="space-y-1">
|
||||
{approveOverride.map((reason, i) => (
|
||||
|
||||
@@ -888,7 +888,7 @@ function MatchDialog({
|
||||
)}
|
||||
|
||||
{!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>
|
||||
|
||||
@@ -1080,7 +1080,7 @@ export default function SupplierInvoiceDetailPage() {
|
||||
onChange={(e) => updateEditLine(i, { description: e.target.value })}
|
||||
placeholder="Beskrivning"
|
||||
/>
|
||||
<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' })}
|
||||
@@ -1197,7 +1197,7 @@ export default function SupplierInvoiceDetailPage() {
|
||||
? t('duplicate_payment_description_one')
|
||||
: t('duplicate_payment_description_many')}
|
||||
</p>
|
||||
<div className="space-y-2 rounded-md border bg-muted/30 p-3">
|
||||
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
|
||||
{duplicateCandidates?.map((c) => (
|
||||
<div key={c.id} className="flex items-center justify-between gap-3 text-sm">
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -8,13 +8,13 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { DataListEmpty } from '@/components/ui/data-list'
|
||||
import { TH_CLASS, TD_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
import { FyPicker } from '@/components/common/FyPicker'
|
||||
import { ContextPicker } from '@/components/common/ContextPicker'
|
||||
import { HelpPopover } from '@/components/ui/help-popover'
|
||||
import { Plus, FileInput, Lock, Search } from 'lucide-react'
|
||||
import { Plus, FileInput, Lock } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { DialogLoadingSkeleton } from '@/components/ui/dialog-loading-skeleton'
|
||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
||||
@@ -316,15 +316,12 @@ export default function SupplierInvoicesPage() {
|
||||
: undefined,
|
||||
}))}
|
||||
/>
|
||||
<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={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<ToolbarSearch
|
||||
containerClassName="min-w-[190px]"
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<div className="ml-auto flex items-center gap-4">
|
||||
<Link href="/supplier-invoices/payment-files" className={QUIET_LINK_CLASS}>
|
||||
{t('payment_files_link')}
|
||||
|
||||
@@ -6,14 +6,14 @@ import { useRouter } from 'next/navigation'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { EmptyState } from '@/components/ui/empty-state'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
|
||||
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { Plus, Search, Building2, Lock } from 'lucide-react'
|
||||
import { Plus, Building2, Lock } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
@@ -170,18 +170,14 @@ export default function SuppliersPage() {
|
||||
|
||||
{/* Toolbar: search (concept) */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="relative min-w-[220px] 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)
|
||||
setVisibleCount(INITIAL_VISIBLE_ROWS)
|
||||
}}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<ToolbarSearch
|
||||
placeholder={t('search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value)
|
||||
setVisibleCount(INITIAL_VISIBLE_ROWS)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
|
||||
@@ -14,10 +14,11 @@ import { ToastAction } from '@/components/ui/toast'
|
||||
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
|
||||
import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog'
|
||||
import { DataList, DataListEmpty } from '@/components/ui/data-list'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { SegmentedControl } from '@/components/ui/segmented-control'
|
||||
import { ToolbarSearch } from '@/components/ui/toolbar-search'
|
||||
import { TH_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
import { Loader2, Search } from 'lucide-react'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import TransactionStatusBar from '@/components/transactions/TransactionStatusBar'
|
||||
import BankSyncStatusChip from '@/components/transactions/BankSyncStatusChip'
|
||||
import { ContextPicker, type ContextPickerItem } from '@/components/common/ContextPicker'
|
||||
@@ -3177,71 +3178,25 @@ export default function TransactionsPage() {
|
||||
{/* Toolbar (concept order): [Att bokföra/Alla-seg] [sök] [Välj flera]
|
||||
... [source ContextPicker far right] */}
|
||||
<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={mode === 'inbox'}
|
||||
onClick={() => setMode('inbox')}
|
||||
className={`inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
|
||||
mode === 'inbox'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t('mode_inbox')}
|
||||
{inboxBadgeCount > 0 && (
|
||||
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
|
||||
{inboxBadgeCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={mode === 'history'}
|
||||
onClick={() => setMode('history')}
|
||||
className={`rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
|
||||
mode === 'history'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t('mode_all')}
|
||||
</button>
|
||||
{/* Review tab exists only while suggestions do (or while the user is
|
||||
standing in it after emptying the list): a permanent third tab
|
||||
would advertise a migrator surface most companies never need. */}
|
||||
{(suggestionItems.length > 0 || mode === 'review') && (
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={mode === 'review'}
|
||||
onClick={() => setMode('review')}
|
||||
className={`inline-flex items-center gap-1.5 rounded-md px-3.5 py-[5px] text-[12.5px] transition-colors duration-150 ${
|
||||
mode === 'review'
|
||||
? 'border border-border bg-card font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t('mode_review')}
|
||||
{suggestionItems.length > 0 && (
|
||||
<span className="rounded-full bg-secondary px-1.5 text-[10px] font-medium tabular-nums">
|
||||
{suggestionItems.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative min-w-[220px] 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="Sök transaktioner…"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="h-9 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={mode}
|
||||
onChange={setMode}
|
||||
options={[
|
||||
{ value: 'inbox', label: t('mode_inbox'), count: inboxBadgeCount },
|
||||
{ value: 'history', label: t('mode_all') },
|
||||
// Review tab exists only while suggestions do (or while the user is
|
||||
// standing in it after emptying the list): a permanent third tab
|
||||
// would advertise a migrator surface most companies never need.
|
||||
...(suggestionItems.length > 0 || mode === 'review'
|
||||
? [{ value: 'review' as const, label: t('mode_review'), count: suggestionItems.length }]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
<ToolbarSearch
|
||||
placeholder="Sök transaktioner…"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
{/* Far-right context group, shared by both view modes: period scope
|
||||
(rakenskapsar chip) and the account chooser (concept scene 10).
|
||||
Approved deviation from convention 8's single chip: booking is
|
||||
@@ -3276,12 +3231,12 @@ export default function TransactionsPage() {
|
||||
<DataList className="stagger-enter">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="flex items-center gap-3 px-4 py-3">
|
||||
<Skeleton className="h-5 w-5 rounded" />
|
||||
<Skeleton className="h-5 w-5" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-48 rounded" />
|
||||
<Skeleton className="h-3 w-24 rounded" />
|
||||
<Skeleton className="h-4 w-48" />
|
||||
<Skeleton className="h-3 w-24" />
|
||||
</div>
|
||||
<Skeleton className="h-5 w-20 rounded" />
|
||||
<Skeleton className="h-5 w-20" />
|
||||
</div>
|
||||
))}
|
||||
</DataList>
|
||||
@@ -3857,7 +3812,7 @@ export default function TransactionsPage() {
|
||||
fakturan istället för att bokföra direkt på leverantörsskuldskontot, annars skapas en
|
||||
dubblerad verifikation som måste stornas (BFL 5 kap 5 §).
|
||||
</p>
|
||||
<div className="space-y-2 rounded-md border bg-muted/30 p-3">
|
||||
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
|
||||
{siMatchSuggestion?.candidates.map((c) => (
|
||||
<div key={c.supplier_invoice_id} className="flex items-center justify-between gap-3 text-sm">
|
||||
<div className="min-w-0">
|
||||
@@ -3915,7 +3870,7 @@ export default function TransactionsPage() {
|
||||
istället för att bokföra direkt mot kundfordringskontot, annars skapas en dubblerad
|
||||
verifikation som måste stornas (BFL 5 kap 5 §).
|
||||
</p>
|
||||
<div className="space-y-2 rounded-md border bg-muted/30 p-3">
|
||||
<div className="space-y-2 rounded-lg border bg-muted/30 p-3">
|
||||
{ciMatchSuggestion?.candidates.map((c) => (
|
||||
<div key={c.invoice_id} className="flex items-center justify-between gap-3 text-sm">
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function GlobalError({
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90"
|
||||
className="rounded-full bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Försök igen
|
||||
</button>
|
||||
|
||||
@@ -134,7 +134,7 @@ export default async function PayslipPage({
|
||||
|
||||
<a
|
||||
href={`/api/payslip/${token}/pdf`}
|
||||
className="inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90"
|
||||
className="inline-flex h-10 w-full items-center justify-center rounded-full bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90"
|
||||
>
|
||||
Ladda ner PDF (lönespecifikation)
|
||||
</a>
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user