style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup (#835)
* style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup Multi-agent scan of all 404 UI files against the locked design system, then 141 verified surgical fixes across 109 files (net -32 lines): - Remove forbidden elevation/motion: shadow-* and rounded-xl on cards, active:scale bounce, hover:shadow on list items, transition-all -> transition-colors. - Drop font-medium from single-weight Hedvig display headings/numerals. - Replace raw rainbow Tailwind status colors with Badge variants / brand tokens / neutral surfaces (achromatic chrome, semantic colors stay data-only). - Route raw dates through formatDate(), hand-rolled currency through formatCurrency(), add tabular-nums to financial figures; text-gray-* -> text-foreground tokens. - Swap hand-rolled skeletons for the Skeleton primitive; off-scale spacing -> token scale. - Fix copy: mislabeled "Leverantörsfakturor" -> "Utgifter" on bank-import outflow total, collapse no-op identical-branch ternaries, broken Swedish diacritics (mojibake), correct mismatch-password toast, correct supplier currency-field label. - Remove PII-leaking debug console.log on register, stray console.logs. Verified: tsc clean on all changed files, eslint clean, production build passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): sanitize residual error logs in register flow Follow-up to PR review (compliance swarm V16 / GDPR Art.5(1)(f)): the remaining console.error calls in the register flow passed raw error objects, which Supabase may populate with PII (email) in nested fields. Log only sanitized message strings instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a8072f6423
commit
b800dcd403
@@ -28,7 +28,7 @@ export default function ChatIntakeStarter() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="flex items-center gap-3 border-b border-border px-5 py-4 shrink-0">
|
||||
<header className="flex items-center gap-3 border-b border-border px-6 py-4 shrink-0">
|
||||
<AgentAvatar avatarId={identity.avatarId} size="sm" alt={agentName} />
|
||||
<div className="min-w-0">
|
||||
<h1 className="font-display text-lg tracking-tight truncate">{agentName} är redo</h1>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function ChatNewStarter({
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="flex items-center gap-3 border-b border-border px-5 py-4 shrink-0">
|
||||
<header className="flex items-center gap-3 border-b border-border px-6 py-4 shrink-0">
|
||||
<AgentAvatar avatarId={identity.avatarId} size="sm" alt={agentName} />
|
||||
<div className="min-w-0">
|
||||
<h1 className="font-display text-lg tracking-tight truncate">{agentName}</h1>
|
||||
|
||||
@@ -441,7 +441,7 @@ export function BankIdAuth({ mode, onComplete }: BankIdAuthProps) {
|
||||
className="gap-1.5 text-muted-foreground"
|
||||
>
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
BankID pa den har enheten
|
||||
BankID på den här enheten
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -449,7 +449,7 @@ export function BankIdAuth({ mode, onComplete }: BankIdAuthProps) {
|
||||
{isMobile() && (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Smartphone className="h-8 w-8 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">Oppnar BankID-appen...</p>
|
||||
<p className="text-sm text-muted-foreground">Öppnar BankID-appen...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Info } from 'lucide-react'
|
||||
import JournalEntryStatusBadge from '@/components/bookkeeping/JournalEntryStatusBadge'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
import { formatDate, formatCurrency } from '@/lib/utils'
|
||||
import { formatVoucher } from '@/lib/bookkeeping/voucher-series-resolver'
|
||||
import type { JournalEntry, JournalEntryLine } from '@/types'
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function CorrectionChain({ currentEntryId, chain }: Props) {
|
||||
</Badge>
|
||||
)}
|
||||
<span className="ml-auto text-sm tabular-nums text-muted-foreground">
|
||||
{total.toLocaleString('sv-SE', { minimumFractionDigits: 2 })} kr
|
||||
{formatCurrency(total)}
|
||||
</span>
|
||||
</div>
|
||||
{entry.description && (
|
||||
|
||||
@@ -207,7 +207,7 @@ export default function DocumentUploadZone({
|
||||
<div
|
||||
className={`
|
||||
relative border-2 border-dashed rounded-lg text-center transition-colors
|
||||
${compact ? 'p-3' : 'p-5'}
|
||||
${compact ? 'p-3' : 'p-6'}
|
||||
${isDragging ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'}
|
||||
${disabled ? 'pointer-events-none opacity-50' : 'cursor-pointer hover:border-primary/50'}
|
||||
`}
|
||||
@@ -281,6 +281,7 @@ export default function DocumentUploadZone({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Ta bort fil"
|
||||
className="h-6 w-6 p-0 shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { AccountNumber } from '@/components/ui/account-number'
|
||||
import { CheckCircle2, Paperclip } from 'lucide-react'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
|
||||
interface ReviewLine {
|
||||
account_number: string
|
||||
@@ -58,7 +59,7 @@ export function JournalEntryReviewContent({
|
||||
{!hideDate && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">Datum</span>
|
||||
<p className="font-medium">{entryDate}</p>
|
||||
<p className="font-medium">{formatDate(entryDate)}</p>
|
||||
</div>
|
||||
)}
|
||||
{voucherSeries && (
|
||||
@@ -84,7 +85,7 @@ export function JournalEntryReviewContent({
|
||||
{(showBalanceBadge || (attachmentCount != null && attachmentCount > 0)) && (
|
||||
<div className="flex items-center gap-2">
|
||||
{showBalanceBadge && (
|
||||
<Badge className="bg-success/10 text-success">
|
||||
<Badge variant="success">
|
||||
<CheckCircle2 className="h-3 w-3 mr-1" />
|
||||
Debet = Kredit
|
||||
</Badge>
|
||||
@@ -92,7 +93,7 @@ export function JournalEntryReviewContent({
|
||||
{attachmentCount != null && attachmentCount > 0 && (
|
||||
<Badge variant="outline">
|
||||
<Paperclip className="h-3 w-3 mr-1" />
|
||||
{attachmentCount} {attachmentCount === 1 ? 'underlag' : 'underlag'}
|
||||
{attachmentCount} underlag
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -308,7 +308,7 @@ function ManualEntryEditor({
|
||||
{entry.kind === 'manual_prepaid_expense' && 'Förutbetald kostnad'}
|
||||
{entry.kind === 'manual_accrued_expense' && 'Upplupen kostnad'}
|
||||
</p>
|
||||
<Button variant="ghost" size="sm" onClick={onRemove} className="h-7 px-2">
|
||||
<Button variant="ghost" size="sm" aria-label="Ta bort post" onClick={onRemove} className="h-7 px-2">
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function AgentSetupBanner({ companyName }: Props) {
|
||||
href="/onboarding/agent"
|
||||
className="group block rounded-lg border border-border bg-card hover:bg-secondary/60 transition-colors duration-150 mb-8"
|
||||
>
|
||||
<div className="flex items-center gap-4 p-5">
|
||||
<div className="flex items-center gap-4 p-6">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-foreground text-background shrink-0">
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function CompanySwitcher() {
|
||||
return (
|
||||
<Link
|
||||
href="/select-company"
|
||||
className="flex items-center gap-2 w-full text-left rounded-lg border border-dashed border-border/60 hover:border-foreground/30 hover:bg-muted/40 -mx-1 px-2 py-1.5 transition-all duration-150"
|
||||
className="flex items-center gap-2 w-full text-left rounded-lg border border-dashed border-border/60 hover:border-foreground/30 hover:bg-muted/40 -mx-1 px-2 py-1.5 transition-colors duration-150"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-[13px] text-muted-foreground truncate">{t('add_company')}</span>
|
||||
@@ -133,7 +133,7 @@ export default function CompanySwitcher() {
|
||||
<button
|
||||
ref={triggerRef}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex items-center gap-1.5 w-full text-left rounded-lg border border-transparent hover:border-border/60 hover:bg-muted/40 -mx-1 px-2 py-1.5 transition-all duration-150"
|
||||
className="flex items-center gap-1.5 w-full text-left rounded-lg border border-transparent hover:border-border/60 hover:bg-muted/40 -mx-1 px-2 py-1.5 transition-colors duration-150"
|
||||
aria-expanded={open}
|
||||
aria-haspopup="listbox"
|
||||
>
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function ConsultantEmptyState({ firstName }: ConsultantEmptyState
|
||||
return (
|
||||
<div className="stagger-enter">
|
||||
<header className="mb-16">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
{greeting}{firstName ? `, ${firstName}` : ''}
|
||||
</h1>
|
||||
</header>
|
||||
@@ -24,7 +24,7 @@ export default function ConsultantEmptyState({ firstName }: ConsultantEmptyState
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<h2 className="font-display text-lg font-medium mb-2">
|
||||
<h2 className="font-display text-lg mb-2">
|
||||
Inga företag ännu
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mb-8">
|
||||
@@ -33,7 +33,7 @@ export default function ConsultantEmptyState({ firstName }: ConsultantEmptyState
|
||||
|
||||
<Link
|
||||
href="/onboarding"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors active:scale-[0.98]"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Lägg till företag
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function DashboardContent({ companyId, summary, worklist, suggest
|
||||
<section>
|
||||
<Link href="/onboarding/agent" className="block group">
|
||||
<Card className="transition-colors hover:border-primary/50">
|
||||
<CardContent className="p-6 flex items-center gap-5">
|
||||
<CardContent className="p-6 flex items-center gap-4">
|
||||
<div className="flex-shrink-0 h-10 w-10 rounded-lg flex items-center justify-center bg-foreground text-background">
|
||||
<MessageCircle className="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
@@ -225,7 +225,6 @@ export default function WelcomeOnboarding({
|
||||
return
|
||||
}
|
||||
|
||||
console.log(LOG, 'onboarding completed', result.companyId)
|
||||
toast({
|
||||
title: t('toast_welcome_title'),
|
||||
description: t('toast_company_ready'),
|
||||
@@ -253,12 +252,12 @@ export default function WelcomeOnboarding({
|
||||
return (
|
||||
<div className="flex flex-col items-start justify-center min-h-[60vh] animate-fade-in">
|
||||
<p className="text-muted-foreground/50 text-sm mb-2">{greeting}</p>
|
||||
<h1 className="font-display text-4xl md:text-5xl font-medium tracking-tight leading-[1.05] mb-10">
|
||||
<h1 className="font-display text-4xl md:text-5xl tracking-tight leading-[1.05] mb-10">
|
||||
{t('welcome_title', { appName: branding.appName })}
|
||||
</h1>
|
||||
<button
|
||||
onClick={() => setStarted(true)}
|
||||
className="px-5 py-2.5 rounded-lg bg-foreground text-background text-sm font-medium hover:bg-foreground/85 transition-colors duration-150 active:scale-[0.98]"
|
||||
className="px-4 py-2 rounded-lg bg-foreground text-background text-sm font-medium hover:bg-foreground/85 transition-colors duration-150"
|
||||
>
|
||||
{hasExistingCompanies ? t('add_a_company') : t('add_first_company')}
|
||||
</button>
|
||||
@@ -270,7 +269,7 @@ export default function WelcomeOnboarding({
|
||||
<div className="stagger-enter">
|
||||
{/* Greeting header */}
|
||||
<header className="mb-10">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
{greeting}{firstName ? `, ${firstName}` : ''}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm mt-1.5">
|
||||
@@ -319,7 +318,7 @@ export default function WelcomeOnboarding({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="font-display text-lg font-medium tracking-tight leading-tight">
|
||||
<h2 className="font-display text-lg tracking-tight leading-tight">
|
||||
{stepInfo.title}
|
||||
</h2>
|
||||
<p className="text-white/40 mt-1 text-sm">
|
||||
|
||||
@@ -151,7 +151,7 @@ export function DeadlineCard({
|
||||
{/* Date block */}
|
||||
<div className="flex-shrink-0 w-12 text-center">
|
||||
<span className={cn(
|
||||
'block text-lg font-display font-medium leading-none tabular-nums',
|
||||
'block text-lg font-display leading-none tabular-nums',
|
||||
completed && 'text-muted-foreground',
|
||||
)}>
|
||||
{dayNum}
|
||||
|
||||
@@ -395,10 +395,10 @@ function ProviderStep({
|
||||
<button
|
||||
key={provider.id}
|
||||
disabled={isDisabled}
|
||||
className={`relative flex items-center gap-4 rounded-lg border p-4 text-left transition-all ${
|
||||
className={`relative flex items-center gap-4 rounded-lg border p-4 text-left transition-colors ${
|
||||
isDisabled
|
||||
? 'cursor-not-allowed border-border/50 opacity-60'
|
||||
: 'border-border hover:border-primary/50 hover:bg-accent/50 active:scale-[0.98]'
|
||||
: 'border-border hover:border-primary/50 hover:bg-accent/50'
|
||||
}`}
|
||||
onClick={() => !isDisabled && onSelect(provider.id)}
|
||||
>
|
||||
@@ -1448,7 +1448,7 @@ function ResultStep({
|
||||
{/* ── Header card with overall summary ── */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2.5">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<StatusIcon status={nothingNew ? 'success' : overallIcon} />
|
||||
{nothingNew ? 'Allt är uppdaterat' :
|
||||
anySieFailed ? 'Migrering delvis genomförd' :
|
||||
|
||||
@@ -634,7 +634,7 @@ export default function BookDirectlyDialog({ open, onOpenChange, item, onSuccess
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'w-full flex items-center justify-between gap-3 px-3 py-2.5 text-left text-sm transition-colors',
|
||||
'w-full flex items-center justify-between gap-3 px-3 py-2 text-left text-sm transition-colors',
|
||||
isSelected
|
||||
? 'bg-primary/10 border-l-2 border-primary'
|
||||
: 'border-l-2 border-transparent hover:bg-accent/40'
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function CalendarWorkspace({ userId }: WorkspaceComponentProps) {
|
||||
setCustomers(customersData || [])
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Kunde inte hamta data',
|
||||
title: 'Kunde inte hämta data',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
|
||||
@@ -1225,7 +1225,7 @@ function OnboardingCard({
|
||||
{
|
||||
done: hasAnyItem,
|
||||
title: 'Ladda upp eller maila in ett underlag',
|
||||
hint: 'accounted tolkar fakturan eller kvittot åt dig och fyller i fält automatiskt.',
|
||||
hint: 'Accounted tolkar fakturan eller kvittot åt dig och fyller i fält automatiskt.',
|
||||
},
|
||||
{
|
||||
done: hasResolvedItem,
|
||||
@@ -1589,7 +1589,7 @@ function FieldsRail({
|
||||
AI-tolkning ingår i abonnemanget
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1.5 leading-relaxed">
|
||||
Uppgradera för att låta accounted läsa av leverantör, belopp och
|
||||
Uppgradera för att låta Accounted läsa av leverantör, belopp och
|
||||
moms automatiskt. Du kan fortfarande fylla i fälten manuellt eller
|
||||
koppla dokumentet till en transaktion nedan.
|
||||
</p>
|
||||
|
||||
@@ -116,7 +116,7 @@ export default function CsvImportWizard({
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
{step === 1 && <><Upload className="h-4 w-4" /> Steg 1: Valj fil</>}
|
||||
{step === 1 && <><Upload className="h-4 w-4" /> Steg 1: Välj fil</>}
|
||||
{step === 2 && <><FileText className="h-4 w-4" /> Steg 2: Kolumnmappning</>}
|
||||
{step === 3 && <><Check className="h-4 w-4" /> Import klar</>}
|
||||
</CardTitle>
|
||||
@@ -127,11 +127,11 @@ export default function CsvImportWizard({
|
||||
<div className="border-2 border-dashed rounded-lg p-8 text-center">
|
||||
<Upload className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Valj en CSV-fil att importera
|
||||
Välj en CSV-fil att importera
|
||||
</p>
|
||||
<Label htmlFor="csv-upload" className="cursor-pointer">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<span>Valj fil</span>
|
||||
<span>Välj fil</span>
|
||||
</Button>
|
||||
</Label>
|
||||
<input
|
||||
@@ -161,7 +161,7 @@ export default function CsvImportWizard({
|
||||
onValueChange={(val) => setMappings(prev => ({ ...prev, [field.key]: val }))}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Valj kolumn..." />
|
||||
<SelectValue placeholder="Välj kolumn..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{headers.map(h => (
|
||||
@@ -218,7 +218,7 @@ export default function CsvImportWizard({
|
||||
</div>
|
||||
<p className="font-medium">{importCount} rader importerades</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Fran {fileName}
|
||||
Från {fileName}
|
||||
</p>
|
||||
<Button variant="outline" size="sm" onClick={reset} className="mt-4">
|
||||
Importera fler
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function ExtensionLoadingSkeleton() {
|
||||
{/* KPI cards skeleton */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="rounded-xl border p-6 space-y-2">
|
||||
<div key={i} className="rounded-lg border p-6 space-y-2">
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="h-8 w-16" />
|
||||
<Skeleton className="h-3 w-32" />
|
||||
@@ -14,7 +14,7 @@ export default function ExtensionLoadingSkeleton() {
|
||||
))}
|
||||
</div>
|
||||
{/* Content skeleton */}
|
||||
<div className="rounded-xl border p-6 space-y-4">
|
||||
<div className="rounded-lg border p-6 space-y-4">
|
||||
<Skeleton className="h-6 w-48" />
|
||||
<Skeleton className="h-40 w-full" />
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function KPICard({ label, value, suffix, trend, className }: KPIC
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-sm text-muted-foreground">{label}</p>
|
||||
<div className="flex items-baseline gap-1 mt-1">
|
||||
<span className="text-2xl font-semibold tracking-tight">{value}</span>
|
||||
<span className="text-2xl font-semibold tracking-tight tabular-nums">{value}</span>
|
||||
{suffix && <span className="text-sm text-muted-foreground">{suffix}</span>}
|
||||
</div>
|
||||
{trend && (
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function MonthlyTrendTable({
|
||||
}: MonthlyTrendTableProps) {
|
||||
if (rows.length === 0) {
|
||||
return (
|
||||
<div className={cn('rounded-xl border p-6 text-center text-sm text-muted-foreground', className)}>
|
||||
<div className={cn('rounded-lg border p-6 text-center text-sm text-muted-foreground', className)}>
|
||||
Ingen data att visa
|
||||
</div>
|
||||
)
|
||||
@@ -38,7 +38,7 @@ export default function MonthlyTrendTable({
|
||||
const fmt = formatValue ?? ((v: number) => v.toLocaleString('sv-SE'))
|
||||
|
||||
return (
|
||||
<div className={cn('rounded-xl border', className)}>
|
||||
<div className={cn('rounded-lg border', className)}>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function SetupPrompt({ title, description, fields, onSave }: Setu
|
||||
</div>
|
||||
))}
|
||||
<Button type="submit" className="w-full" disabled={!allFilled || isSaving}>
|
||||
{isSaving ? 'Sparar...' : 'Kom igang'}
|
||||
{isSaving ? 'Sparar...' : 'Kom igång'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
|
||||
@@ -343,7 +343,7 @@ function ConfidenceBadge({
|
||||
}
|
||||
|
||||
if (confidence >= 0.9) {
|
||||
return <Badge variant="default" className="bg-success">Exakt</Badge>
|
||||
return <Badge variant="success">Exakt</Badge>
|
||||
}
|
||||
|
||||
if (confidence >= 0.7) {
|
||||
|
||||
@@ -259,6 +259,7 @@ export default function ArticlesEditStep({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
aria-label="Ta bort rad"
|
||||
onClick={() => deleteRow(row.id)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function BankFileConfirmStep({
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-xs">Transaktioner</span>
|
||||
</div>
|
||||
<p className="text-xl font-display font-medium tabular-nums">{stats.parsed_rows}</p>
|
||||
<p className="text-xl font-display tabular-nums">{stats.parsed_rows}</p>
|
||||
{stats.skipped_rows > 0 && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{stats.skipped_rows} rader hoppades över
|
||||
@@ -125,16 +125,16 @@ export default function BankFileConfirmStep({
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<span className="text-xs">Inkomster</span>
|
||||
</div>
|
||||
<p className="text-xl font-display font-medium tabular-nums">
|
||||
<p className="text-xl font-display tabular-nums">
|
||||
{formatCurrency(stats.total_income)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-muted/50 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<span className="text-xs">Leverantörsfakturor</span>
|
||||
<span className="text-xs">Utgifter</span>
|
||||
</div>
|
||||
<p className="text-xl font-display font-medium tabular-nums">
|
||||
<p className="text-xl font-display tabular-nums">
|
||||
{formatCurrency(stats.total_expenses)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function BankFilePreviewStep({
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-sm">Transaktioner</span>
|
||||
</div>
|
||||
<p className="text-2xl font-display font-medium tabular-nums">{stats.parsed_rows}</p>
|
||||
<p className="text-2xl font-display tabular-nums">{stats.parsed_rows}</p>
|
||||
{stats.skipped_rows > 0 && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{stats.skipped_rows} rader hoppades över
|
||||
@@ -75,7 +75,7 @@ export default function BankFilePreviewStep({
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
<span className="text-sm">Inkomster</span>
|
||||
</div>
|
||||
<p className="text-lg font-display font-medium tabular-nums">
|
||||
<p className="text-lg font-display tabular-nums">
|
||||
{formatCurrency(stats.total_income)}
|
||||
</p>
|
||||
</CardContent>
|
||||
@@ -85,9 +85,9 @@ export default function BankFilePreviewStep({
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<TrendingDown className="h-4 w-4" />
|
||||
<span className="text-sm">Leverantörsfakturor</span>
|
||||
<span className="text-sm">Utgifter</span>
|
||||
</div>
|
||||
<p className="text-lg font-display font-medium tabular-nums">
|
||||
<p className="text-lg font-display tabular-nums">
|
||||
{formatCurrency(stats.total_expenses)}
|
||||
</p>
|
||||
</CardContent>
|
||||
@@ -96,9 +96,9 @@ export default function BankFilePreviewStep({
|
||||
|
||||
{/* Warnings */}
|
||||
{warnings.length > 0 && (
|
||||
<Card className="border-yellow-300">
|
||||
<Card>
|
||||
<CardHeader className="py-3">
|
||||
<CardTitle className="text-sm flex items-center gap-2 text-yellow-600">
|
||||
<CardTitle className="text-sm flex items-center gap-2 text-warning">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
{warnings.length} varning{warnings.length !== 1 ? 'ar' : ''}
|
||||
</CardTitle>
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function ImportResultStep({ result, onNewImport, onUndo }: Import
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Success/Failure header */}
|
||||
<Card className={result.success ? 'border-success/50' : 'border-destructive/50'}>
|
||||
<Card className={result.success ? 'border-border' : 'border-destructive/50'}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
{result.success ? (
|
||||
@@ -116,7 +116,7 @@ export default function ImportResultStep({ result, onNewImport, onUndo }: Import
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-sm">Verifikationer skapade</span>
|
||||
</div>
|
||||
<p className="text-2xl font-display font-medium tabular-nums">{result.journalEntriesCreated}</p>
|
||||
<p className="text-2xl font-display tabular-nums">{result.journalEntriesCreated}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -125,9 +125,9 @@ export default function ImportResultStep({ result, onNewImport, onUndo }: Import
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<span className="text-sm">Räkenskapsår</span>
|
||||
</div>
|
||||
<div className="text-2xl font-display font-medium">
|
||||
<div className="text-2xl font-display">
|
||||
{result.fiscalPeriodId ? (
|
||||
<Badge variant="default" className="bg-success">Skapat</Badge>
|
||||
<Badge variant="success">Skapat</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">Befintligt</Badge>
|
||||
)}
|
||||
@@ -140,9 +140,9 @@ export default function ImportResultStep({ result, onNewImport, onUndo }: Import
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<span className="text-sm">Ingående balanser</span>
|
||||
</div>
|
||||
<div className="text-2xl font-display font-medium">
|
||||
<div className="text-2xl font-display">
|
||||
{result.openingBalanceEntryId ? (
|
||||
<Badge variant="default" className="bg-success">Importerade</Badge>
|
||||
<Badge variant="success">Importerade</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">Inga</Badge>
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
import {
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
@@ -183,7 +183,7 @@ export default function ImportReviewStep({
|
||||
{preview.voucherCount} verifikationer bearbetas
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-2xl font-display font-medium tabular-nums text-muted-foreground">
|
||||
<div className="text-2xl font-display tabular-nums text-muted-foreground">
|
||||
{elapsed}s
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground max-w-sm">
|
||||
@@ -224,11 +224,11 @@ export default function ImportReviewStep({
|
||||
<p className="font-medium">Räkenskapsår</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{preview.fiscalYearStart
|
||||
? new Date(preview.fiscalYearStart).toLocaleDateString('sv-SE')
|
||||
? formatDate(preview.fiscalYearStart)
|
||||
: '?'}{' '}
|
||||
-{' '}
|
||||
{preview.fiscalYearEnd
|
||||
? new Date(preview.fiscalYearEnd).toLocaleDateString('sv-SE')
|
||||
? formatDate(preview.fiscalYearEnd)
|
||||
: '?'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function SIEPreviewStep({
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-sm">Konton</span>
|
||||
</div>
|
||||
<p className="text-2xl font-display font-medium tabular-nums">{preview.accountCount}</p>
|
||||
<p className="text-2xl font-display tabular-nums">{preview.accountCount}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function SIEPreviewStep({
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
<span className="text-sm">Verifikationer</span>
|
||||
</div>
|
||||
<p className="text-2xl font-display font-medium tabular-nums">{preview.voucherCount}</p>
|
||||
<p className="text-2xl font-display tabular-nums">{preview.voucherCount}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function SIEPreviewStep({
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<span className="text-sm">Transaktionsrader</span>
|
||||
</div>
|
||||
<p className="text-2xl font-display font-medium tabular-nums">{preview.transactionLineCount}</p>
|
||||
<p className="text-2xl font-display tabular-nums">{preview.transactionLineCount}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function SIEPreviewStep({
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
<span className="text-sm">IB Summa</span>
|
||||
</div>
|
||||
<p className="text-2xl font-display font-medium tabular-nums">{formatCurrency(preview.openingBalanceTotal)}</p>
|
||||
<p className="text-2xl font-display tabular-nums">{formatCurrency(preview.openingBalanceTotal)}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -161,7 +161,7 @@ export default function SIEPreviewStep({
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Status</p>
|
||||
{preview.trialBalance.isBalanced ? (
|
||||
<Badge variant="default" className="bg-success">Balanserar</Badge>
|
||||
<Badge variant="success">Balanserar</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">
|
||||
Diff: {formatCurrency(preview.trialBalance.totalDebit - preview.trialBalance.totalCredit)}
|
||||
|
||||
@@ -193,7 +193,8 @@ export default function SuppliersEditStep({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
className="h-8 w-8"
|
||||
aria-label="Ta bort rad"
|
||||
onClick={() => deleteRow(row.id)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
|
||||
@@ -1113,7 +1113,7 @@ export default function InvoiceEditor(props: InvoiceEditorProps = { mode: 'creat
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
{titleText}
|
||||
{numberPreview && !isSelfBilled && (
|
||||
<span className="ml-2 text-muted-foreground tabular-nums text-xl md:text-2xl">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslations } from 'next-intl'
|
||||
import { CalendarClock } from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { getDisplayTotal } from '@/lib/invoices/rounding'
|
||||
import { itemHasAccrual } from '@/lib/bookkeeping/accruals/account-suggestions'
|
||||
import type { Customer, Currency } from '@/types'
|
||||
@@ -108,11 +108,11 @@ export function InvoiceReviewContent({
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t('invoice_date')}</span>
|
||||
<p className="font-medium">{invoiceDate}</p>
|
||||
<p className="font-medium">{invoiceDate ? formatDate(invoiceDate) : ''}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t('due_date')}</span>
|
||||
<p className="font-medium">{dueDate}</p>
|
||||
<p className="font-medium">{dueDate ? formatDate(dueDate) : ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export function KPIExpenseMixChart({ composition }: KPIExpenseMixChartProps) {
|
||||
{t('expense_mix_total')}
|
||||
</span>
|
||||
<span
|
||||
className="font-display text-lg font-medium tabular-nums"
|
||||
className="font-display text-lg tabular-nums"
|
||||
title={formatCurrency(total)}
|
||||
>
|
||||
{totalCompact}
|
||||
|
||||
@@ -147,7 +147,7 @@ export function KPIHeroCards({ report, preferences }: KPIHeroCardsProps) {
|
||||
<p className="text-xs text-muted-foreground">{t(`def_${def.id}_label`)}</p>
|
||||
</InfoTooltip>
|
||||
<p
|
||||
className={`font-display text-2xl font-medium tabular-nums tracking-tight mt-2 ${color}`}
|
||||
className={`font-display text-2xl tabular-nums tracking-tight mt-2 ${color}`}
|
||||
>
|
||||
{formatted}
|
||||
</p>
|
||||
|
||||
@@ -106,7 +106,7 @@ export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDi
|
||||
return (
|
||||
<div
|
||||
key={def.id}
|
||||
className="rounded-lg border border-border/60 p-3"
|
||||
className="rounded-lg border border-border p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
@@ -139,7 +139,7 @@ export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDi
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-3 ml-5.5 space-y-2.5 text-xs text-muted-foreground">
|
||||
<div className="mt-3 ml-5.5 space-y-2 text-xs text-muted-foreground">
|
||||
<p>{t(`def_${def.id}_description`)}</p>
|
||||
<div>
|
||||
<p className="font-medium text-foreground/80 mb-0.5">
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function BankIdCompanyPicker({
|
||||
return (
|
||||
<div className="stagger-enter">
|
||||
<header className="mb-10">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
{greeting}{firstName ? `, ${firstName}` : ''}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm mt-1.5">
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function Step1EntityType({ initialData, onNext, isSaving }: Step1
|
||||
>
|
||||
<Card
|
||||
className={cn(
|
||||
'relative p-4 transition-all',
|
||||
'relative p-4 transition-colors',
|
||||
option.disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: 'cursor-pointer hover:border-primary/50',
|
||||
@@ -77,7 +77,7 @@ export default function Step1EntityType({ initialData, onNext, isSaving }: Step1
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={cn(
|
||||
'p-2.5 rounded-lg',
|
||||
'p-2 rounded-lg',
|
||||
isSelected ? 'bg-primary/10' : 'bg-muted/50'
|
||||
)}>
|
||||
<Icon className={cn(
|
||||
|
||||
@@ -288,7 +288,7 @@ export default function Step2CompanyDetails({
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t">
|
||||
<h3 className="font-medium mb-4">{t('step2_address_heading')}</h3>
|
||||
<h3 className="mb-4">{t('step2_address_heading')}</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function Step2SectorSelection({ onNext, onBack, isSaving }: Step2
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Vilken bransch verkar du inom?</h2>
|
||||
<h2 className="text-lg">Vilken bransch verkar du inom?</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Vi anpassar verktyg och tillägg baserat på din bransch. Du kan alltid ändra detta senare.
|
||||
</p>
|
||||
@@ -36,7 +36,7 @@ export default function Step2SectorSelection({ onNext, onBack, isSaving }: Step2
|
||||
<Card
|
||||
key={sector.slug}
|
||||
className={cn(
|
||||
'cursor-pointer transition-all',
|
||||
'cursor-pointer transition-colors',
|
||||
isSelected
|
||||
? 'border-primary ring-2 ring-primary/20'
|
||||
: 'hover:border-primary/50'
|
||||
|
||||
@@ -347,7 +347,7 @@ export default function Step3TaxRegistration({
|
||||
className="text-left"
|
||||
>
|
||||
<Card className={cn(
|
||||
'p-3 transition-all cursor-pointer hover:border-primary/50',
|
||||
'p-3 transition-colors cursor-pointer hover:border-primary/50',
|
||||
field.value && 'border-primary ring-2 ring-primary/20'
|
||||
)}>
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -369,7 +369,7 @@ export default function Step3TaxRegistration({
|
||||
className="text-left"
|
||||
>
|
||||
<Card className={cn(
|
||||
'p-3 transition-all cursor-pointer hover:border-primary/50',
|
||||
'p-3 transition-colors cursor-pointer hover:border-primary/50',
|
||||
!field.value && 'border-primary ring-2 ring-primary/20'
|
||||
)}>
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -127,7 +127,7 @@ export default function Step4VatAccounting({
|
||||
})} className="space-y-6">
|
||||
{/* VAT section */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-medium flex items-center gap-2">
|
||||
<h3 className="flex items-center gap-2">
|
||||
<InfoTooltip
|
||||
content={
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -1063,7 +1063,7 @@ export function BankReconciliationView({ periodId, periodBounds }: BankReconcili
|
||||
<tbody>
|
||||
{ignoredTx.map((tx) => (
|
||||
<tr key={tx.id} className="border-b last:border-0 text-muted-foreground">
|
||||
<td className="py-2">{tx.date}</td>
|
||||
<td className="py-2 tabular-nums">{formatDate(tx.date)}</td>
|
||||
<td className="py-2 truncate max-w-[300px]">{tx.description}</td>
|
||||
<td className="py-2 text-xs">
|
||||
<Badge variant="outline" className="text-xs">{tx.currency}</Badge>
|
||||
@@ -1123,7 +1123,7 @@ export function BankReconciliationView({ periodId, periodBounds }: BankReconcili
|
||||
<tbody>
|
||||
{matchedTx.map((tx) => (
|
||||
<tr key={tx.id} className="border-b last:border-0">
|
||||
<td className="py-2">{tx.date}</td>
|
||||
<td className="py-2 tabular-nums">{formatDate(tx.date)}</td>
|
||||
<td className="py-2 truncate max-w-[300px]">{tx.description}</td>
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{formatCurrency(tx.amount)}
|
||||
|
||||
@@ -116,13 +116,13 @@ export function INK2DeclarationView({ periodId }: { periodId: string }) {
|
||||
<>
|
||||
{/* Warnings */}
|
||||
{data.warnings.length > 0 && (
|
||||
<Card className="border-orange-200 bg-orange-50">
|
||||
<Card className="border-border">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-orange-600 mt-0.5" />
|
||||
<AlertCircle className="h-5 w-5 text-warning mt-0.5" />
|
||||
<div>
|
||||
{data.warnings.map((warning, i) => (
|
||||
<p key={i} className="text-sm text-orange-800">{warning}</p>
|
||||
<p key={i} className="text-sm text-foreground">{warning}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@ export function INK2DeclarationView({ periodId }: { periodId: string }) {
|
||||
<CardTitle>
|
||||
{data.companyInfo.companyName}
|
||||
</CardTitle>
|
||||
<Badge className="bg-primary/10 text-primary">
|
||||
<Badge variant="secondary">
|
||||
{data.fiscalYear.name}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ export function INK2DeclarationView({ periodId }: { periodId: string }) {
|
||||
<tfoot>
|
||||
<tr className="border-t-2 font-semibold">
|
||||
<td className="py-2">Summa tillgångar</td>
|
||||
<td className="py-2 text-right">
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{formatCurrency(data.totals.totalAssets)}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -200,7 +200,7 @@ export function INK2DeclarationView({ periodId }: { periodId: string }) {
|
||||
<tfoot>
|
||||
<tr className="border-t-2 font-semibold">
|
||||
<td className="py-2">Summa eget kapital och skulder</td>
|
||||
<td className="py-2 text-right">
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{formatCurrency(data.totals.totalEquityLiabilities)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -94,13 +94,13 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
<>
|
||||
{/* Warnings */}
|
||||
{data.warnings.length > 0 && (
|
||||
<Card className="border-orange-200 bg-orange-50">
|
||||
<Card className="border-border">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-orange-600 mt-0.5" />
|
||||
<AlertCircle className="h-5 w-5 text-warning mt-0.5" />
|
||||
<div>
|
||||
{data.warnings.map((warning, i) => (
|
||||
<p key={i} className="text-sm text-orange-800">{warning}</p>
|
||||
<p key={i} className="text-sm text-foreground">{warning}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,7 +115,7 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
<CardTitle>
|
||||
{data.companyInfo.companyName}
|
||||
</CardTitle>
|
||||
<Badge className="bg-primary/10 text-primary">
|
||||
<Badge variant="secondary">
|
||||
{data.fiscalYear.name}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -152,7 +152,7 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
<tfoot>
|
||||
<tr className="border-t-2 font-semibold">
|
||||
<td className="py-2">Summa intäkter</td>
|
||||
<td className="py-2 text-right">
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{formatCurrency(
|
||||
data.rutor.R1 + data.rutor.R2 + data.rutor.R3 + data.rutor.R4
|
||||
)}
|
||||
@@ -189,7 +189,7 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
<tfoot>
|
||||
<tr className="border-t-2 font-semibold">
|
||||
<td className="py-2">Summa kostnader</td>
|
||||
<td className="py-2 text-right">
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
-{formatCurrency(
|
||||
data.rutor.R5 + data.rutor.R6 + data.rutor.R7 +
|
||||
data.rutor.R8 + data.rutor.R9 + data.rutor.R10
|
||||
@@ -207,10 +207,10 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="font-mono text-xs bg-muted px-1 rounded mr-2">R11</span>
|
||||
<span className="font-display font-medium text-xl">Årets resultat</span>
|
||||
<span className="font-display text-xl">Årets resultat</span>
|
||||
</div>
|
||||
<span
|
||||
className={`font-display text-2xl font-medium tabular-nums ${
|
||||
className={`font-display text-2xl tabular-nums ${
|
||||
data.rutor.R11 >= 0 ? 'text-success' : 'text-destructive'
|
||||
}`}
|
||||
>
|
||||
@@ -266,7 +266,7 @@ function NEDeclarationRow({
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 text-right">
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{isExpense && amount > 0 ? '-' : ''}{formatCurrency(Math.abs(amount))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -200,19 +200,19 @@ export function PeriodiskSammanstallningView() {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground uppercase tracking-wider">Tjänster (typ 3)</div>
|
||||
<div className="font-display text-xl font-medium tabular-nums">
|
||||
<div className="font-display text-xl tabular-nums">
|
||||
{formatAmount(data.totals.services)} kr
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground uppercase tracking-wider">Varor (typ 1)</div>
|
||||
<div className="font-display text-xl font-medium tabular-nums">
|
||||
<div className="font-display text-xl tabular-nums">
|
||||
{formatAmount(data.totals.goods)} kr
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground uppercase tracking-wider">Trepart (typ 2)</div>
|
||||
<div className="font-display text-xl font-medium tabular-nums">
|
||||
<div className="font-display text-xl tabular-nums">
|
||||
{formatAmount(data.totals.triangulation)} kr
|
||||
</div>
|
||||
</div>
|
||||
@@ -260,7 +260,7 @@ export function PeriodiskSammanstallningView() {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base text-destructive flex items-center gap-2">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
{errors.length} {errors.length === 1 ? 'fel måste' : 'fel måste'} åtgärdas
|
||||
{errors.length} fel måste åtgärdas
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm">
|
||||
|
||||
@@ -604,7 +604,7 @@ function SkatteverketPanelInner({ periodType, year, period, hasData, rutor }: Sk
|
||||
{localChecks.map((c, i) => (
|
||||
<div
|
||||
key={`${c.code}-${i}`}
|
||||
className={`flex items-start gap-2 text-sm rounded-lg p-2.5 ${
|
||||
className={`flex items-start gap-2 text-sm rounded-lg p-3 ${
|
||||
c.status === 'ERROR'
|
||||
? 'bg-destructive/5 text-destructive'
|
||||
: 'bg-amber-50 text-amber-800 dark:bg-amber-950/30 dark:text-amber-200'
|
||||
@@ -756,7 +756,7 @@ function SkatteverketPanelInner({ periodType, year, period, hasData, rutor }: Sk
|
||||
{kontroller.map((k, i) => (
|
||||
<div
|
||||
key={`${k.kod}-${i}`}
|
||||
className={`flex items-start gap-2 text-sm rounded-lg p-2.5 ${
|
||||
className={`flex items-start gap-2 text-sm rounded-lg p-3 ${
|
||||
k.status === 'ERROR'
|
||||
? 'bg-destructive/5 text-destructive'
|
||||
: 'bg-amber-50 text-amber-800 dark:bg-amber-950/30 dark:text-amber-200'
|
||||
|
||||
@@ -1599,7 +1599,7 @@ export function SupplierLedgerView({ periodId }: { periodId: string }) {
|
||||
<CardTitle className="text-sm text-muted-foreground">Totalt utestående</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums">{formatAmount(ledger.total_outstanding)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums">{formatAmount(ledger.total_outstanding)} kr</p>
|
||||
<p className="text-xs text-muted-foreground">{ledger.unpaid_count} fakturor</p>
|
||||
{ledger.unconverted_fx_count > 0 && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
@@ -1613,7 +1613,7 @@ export function SupplierLedgerView({ periodId }: { periodId: string }) {
|
||||
<CardTitle className="text-sm text-muted-foreground">Ej förfallet</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums text-success">{formatAmount(ledger.total_current)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums text-success">{formatAmount(ledger.total_current)} kr</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
@@ -1621,7 +1621,7 @@ export function SupplierLedgerView({ periodId }: { periodId: string }) {
|
||||
<CardTitle className="text-sm text-muted-foreground">Förfallet</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums text-destructive">{formatAmount(ledger.total_overdue)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums text-destructive">{formatAmount(ledger.total_overdue)} kr</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -1930,15 +1930,15 @@ export function GeneralLedgerView({ periodId, initialAccountFilter }: { periodId
|
||||
{formatVoucher(line)}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="py-1.5">{line.date}</td>
|
||||
<td className="py-1.5">{formatDate(line.date)}</td>
|
||||
<td className="py-1.5 truncate max-w-[200px]">{line.description}</td>
|
||||
<td className="py-1.5 text-right">
|
||||
<td className="py-1.5 text-right tabular-nums">
|
||||
{line.debit > 0 ? formatAmount(line.debit) : ''}
|
||||
</td>
|
||||
<td className="py-1.5 text-right">
|
||||
<td className="py-1.5 text-right tabular-nums">
|
||||
{line.credit > 0 ? formatAmount(line.credit) : ''}
|
||||
</td>
|
||||
<td className="py-1.5 text-right font-mono">{formatAmount(line.balance)}</td>
|
||||
<td className="py-1.5 text-right font-mono tabular-nums">{formatAmount(line.balance)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -2102,7 +2102,7 @@ export function JournalRegisterView({ periodId }: { periodId: string }) {
|
||||
<td className="py-2 font-mono text-xs">
|
||||
{formatVoucher(entry)}
|
||||
</td>
|
||||
<td className="py-2">{entry.date}</td>
|
||||
<td className="py-2">{formatDate(entry.date)}</td>
|
||||
<td className="py-2">
|
||||
{entry.description}
|
||||
{isReversed && (
|
||||
@@ -2110,8 +2110,8 @@ export function JournalRegisterView({ periodId }: { periodId: string }) {
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 text-xs text-muted-foreground">{entry.source_type}</td>
|
||||
<td className="py-2 text-right">{formatAmount(entry.total_debit)}</td>
|
||||
<td className="py-2 text-right">{formatAmount(entry.total_credit)}</td>
|
||||
<td className="py-2 text-right tabular-nums">{formatAmount(entry.total_debit)}</td>
|
||||
<td className="py-2 text-right tabular-nums">{formatAmount(entry.total_credit)}</td>
|
||||
</tr>
|
||||
{isExpanded && entry.lines.map((line, lineIndex) => (
|
||||
<tr key={`${index}-${lineIndex}`} className="bg-muted/30 border-b last:border-0">
|
||||
@@ -2366,7 +2366,7 @@ export function ARLedgerView({ periodId }: { periodId: string }) {
|
||||
<CardTitle className="text-sm text-muted-foreground">Totalt utestående</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums">{formatAmount(ledger.total_outstanding)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums">{formatAmount(ledger.total_outstanding)} kr</p>
|
||||
<p className="text-xs text-muted-foreground">{ledger.unpaid_count} fakturor</p>
|
||||
{ledger.unconverted_fx_count > 0 && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
@@ -2380,7 +2380,7 @@ export function ARLedgerView({ periodId }: { periodId: string }) {
|
||||
<CardTitle className="text-sm text-muted-foreground">Ej förfallet</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums text-success">{formatAmount(ledger.total_current)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums text-success">{formatAmount(ledger.total_current)} kr</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
@@ -2388,7 +2388,7 @@ export function ARLedgerView({ periodId }: { periodId: string }) {
|
||||
<CardTitle className="text-sm text-muted-foreground">Förfallet</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums text-destructive">{formatAmount(ledger.total_overdue)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums text-destructive">{formatAmount(ledger.total_overdue)} kr</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -690,7 +690,7 @@ export function AGIPanel(props: AGIPanelProps) {
|
||||
<span>Arbetsgivardeklaration (AGI)</span>
|
||||
<span className="flex items-center gap-2 text-xs font-normal text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-600" />
|
||||
<CheckCircle2 className="h-3.5 w-3.5 text-success" />
|
||||
Ansluten
|
||||
</span>
|
||||
{!readOnly && (
|
||||
@@ -1005,7 +1005,7 @@ function StatusRow({
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{ok ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-emerald-600" />
|
||||
<CheckCircle2 className="h-4 w-4 text-success" />
|
||||
) : (
|
||||
<Link2Off className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
@@ -101,7 +101,7 @@ export function PaymentFilePanel({
|
||||
<CardContent className="space-y-4">
|
||||
{paymentFileFormat && paymentFileGeneratedAt && (
|
||||
<div className="flex items-start gap-2 text-sm text-muted-foreground">
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5 text-emerald-600 dark:text-emerald-400" />
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5 text-success" />
|
||||
<div>
|
||||
Senast genererad:{' '}
|
||||
<span className="text-foreground">
|
||||
@@ -158,7 +158,7 @@ export function PaymentFilePanel({
|
||||
</div>
|
||||
))}
|
||||
<p className="pt-1 text-muted-foreground border-t mt-2">
|
||||
Filen innehåller både lön till anställda och beloppen är förberedda — bankens BankID-signering är det enda steget kvar.
|
||||
Beloppen är förifyllda — kvar är att signera med BankID i banken.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -139,7 +139,7 @@ export function TaxPaymentPanel({
|
||||
|
||||
{paymentFileGeneratedAt && (
|
||||
<div className="flex items-start gap-2 text-sm text-muted-foreground">
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5 text-emerald-600 dark:text-emerald-400" />
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5 text-success" />
|
||||
<div>
|
||||
Betalfil senast genererad{' '}
|
||||
<span className="text-foreground">
|
||||
@@ -150,7 +150,7 @@ export function TaxPaymentPanel({
|
||||
)}
|
||||
|
||||
{taxPaidAt && (
|
||||
<div className="flex items-start gap-2 text-sm text-emerald-700 dark:text-emerald-400">
|
||||
<div className="flex items-start gap-2 text-sm text-success">
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5" />
|
||||
<div>
|
||||
Markerad som betald{' '}
|
||||
|
||||
@@ -102,13 +102,13 @@ export function AccountDangerZone() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="space-y-4 border-t border-border/8 pt-8">
|
||||
<section className="space-y-4 border-t border-border pt-8">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-destructive/80">
|
||||
{t('heading')}
|
||||
</h2>
|
||||
|
||||
{hasBlockers && (
|
||||
<div className="rounded-lg border border-border/60 bg-muted/30 p-4 space-y-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-4 space-y-3">
|
||||
<p className="text-sm font-medium">{t('blockers_title')}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('blockers_description')}
|
||||
@@ -117,7 +117,7 @@ export function AccountDangerZone() {
|
||||
{blockers.map((b) => (
|
||||
<li
|
||||
key={b.id}
|
||||
className="flex items-center justify-between rounded-md border border-border/40 bg-background px-3 py-2"
|
||||
className="flex items-center justify-between rounded-md border border-border bg-background px-3 py-2"
|
||||
>
|
||||
<span className="text-sm font-medium">{b.name}</span>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
|
||||
@@ -187,7 +187,7 @@ function CopyBlock({ text, copyAriaLabel }: { text: string; copyAriaLabel: strin
|
||||
aria-label={copyAriaLabel}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-600" />
|
||||
<Check className="h-3.5 w-3.5 text-success" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
)}
|
||||
@@ -700,7 +700,7 @@ export function ApiKeysPanel() {
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-green-600" />
|
||||
<Check className="h-4 w-4 text-success" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
|
||||
@@ -243,7 +243,7 @@ export function BackupDownloadForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-border/60 bg-muted/30 p-3 text-sm">
|
||||
<div className="rounded-md 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 ? (
|
||||
|
||||
@@ -105,7 +105,7 @@ export function BankIdSettings() {
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
{identity ? (
|
||||
<ShieldCheck className="h-4 w-4 text-green-600" />
|
||||
<ShieldCheck className="h-4 w-4 text-success" />
|
||||
) : (
|
||||
<Shield className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
@@ -161,7 +161,7 @@ export function BankNameCombobox({ defaultValue = '', value: controlledValue, on
|
||||
role="option"
|
||||
aria-selected={highlightedIndex === i}
|
||||
className={cn(
|
||||
'flex items-center gap-2.5 px-3 py-2 text-sm cursor-pointer transition-colors',
|
||||
'flex items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors',
|
||||
highlightedIndex === i && 'bg-accent text-accent-foreground',
|
||||
)}
|
||||
onMouseEnter={() => setHighlightedIndex(i)}
|
||||
|
||||
@@ -81,7 +81,7 @@ export function CompanyDangerZone() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="space-y-4 border-t border-border/8 pt-8">
|
||||
<section className="space-y-4 border-t border-border pt-8">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-destructive/80">
|
||||
{t('danger_heading')}
|
||||
</h2>
|
||||
|
||||
@@ -21,8 +21,8 @@ function formatDate(iso: string | null) {
|
||||
}
|
||||
|
||||
function confidenceColor(c: number): string {
|
||||
if (c >= 0.8) return 'text-emerald-600 dark:text-emerald-400'
|
||||
if (c >= 0.5) return 'text-amber-600 dark:text-amber-400'
|
||||
if (c >= 0.8) return 'text-success'
|
||||
if (c >= 0.5) return 'text-warning'
|
||||
return 'text-muted-foreground'
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ export function FiscalPeriodEditor() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="space-y-4 border-t border-border/8 pt-8">
|
||||
<section className="space-y-4 border-t border-border pt-8">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{t('fp_heading')}
|
||||
@@ -286,7 +286,7 @@ function BlockedState({
|
||||
: t('fp_blocked_reason_posted', { count: postedCount })
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-border/60 bg-muted/30 p-4 space-y-3">
|
||||
<div className="rounded-lg border border-border bg-muted/30 p-4 space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<Lock className="h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
|
||||
@@ -117,7 +117,7 @@ export function LogoUpload({ logoUrl, onUpdate }: LogoUploadProps) {
|
||||
|
||||
{preview ? (
|
||||
<div className="space-y-3">
|
||||
<div className="inline-block rounded-lg border border-border/60 bg-muted/30 p-4">
|
||||
<div className="inline-block rounded-lg border border-border bg-muted/30 p-4">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={preview}
|
||||
@@ -157,7 +157,7 @@ export function LogoUpload({ logoUrl, onUpdate }: LogoUploadProps) {
|
||||
onDragLeave={handleDragLeave}
|
||||
disabled={isUploading}
|
||||
className={`flex flex-col items-center justify-center w-full max-w-xs rounded-lg border-2 border-dashed py-8 px-4 text-center transition-colors disabled:opacity-50 ${
|
||||
isDragging ? 'border-foreground bg-muted/40' : 'border-border/60 hover:border-border hover:bg-muted/20'
|
||||
isDragging ? 'border-foreground bg-muted/40' : 'border-border hover:border-border hover:bg-muted/20'
|
||||
}`}
|
||||
>
|
||||
{isUploading ? (
|
||||
|
||||
@@ -272,11 +272,11 @@ export function SecuritySettings() {
|
||||
</div>
|
||||
) : hasMfa ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 p-4 rounded-lg border bg-green-50 dark:bg-green-950/20 border-green-200 dark:border-green-900">
|
||||
<ShieldCheck className="h-5 w-5 text-green-600 dark:text-green-500" />
|
||||
<div className="flex items-center gap-3 p-4 rounded-lg border border-border bg-secondary">
|
||||
<ShieldCheck className="h-5 w-5 text-success" />
|
||||
<div>
|
||||
<p className="font-medium text-green-900 dark:text-green-100">{t('mfa_active_title')}</p>
|
||||
<p className="text-sm text-green-700 dark:text-green-400">
|
||||
<p className="font-medium text-foreground">{t('mfa_active_title')}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('mfa_active_description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function SettingsRail({ variant, display, activeId }: SettingsRailProps)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav aria-label={t('aria_label')} className="space-y-5">
|
||||
<nav aria-label={t('aria_label')} className="space-y-6">
|
||||
{groups.map((g) => (
|
||||
<div key={g.key} className="space-y-1">
|
||||
<p className="px-3 text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||
|
||||
@@ -108,7 +108,7 @@ export function SkatteverketConnectPanel() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{status?.disabled && (
|
||||
<div className="flex gap-2 rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-100">
|
||||
<div className="flex gap-2 rounded-md border border-border bg-secondary/40 p-3 text-sm text-foreground">
|
||||
<ShieldAlert className="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<p>{t('disabled_message')}</p>
|
||||
</div>
|
||||
@@ -211,7 +211,7 @@ export function SkatteverketConnectPanel() {
|
||||
</div>
|
||||
|
||||
{status.disabled && (
|
||||
<div className="flex gap-2 rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-100">
|
||||
<div className="flex gap-2 rounded-md border border-border bg-secondary/40 p-3 text-sm text-foreground">
|
||||
<ShieldAlert className="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<p>{t('disabled_filings_message')}</p>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ export function TaxSettingsForm({ settings }: TaxSettingsFormProps) {
|
||||
</section>
|
||||
|
||||
{/* F-skatt */}
|
||||
<section className="border-t border-border/8 pt-8 space-y-4">
|
||||
<section className="border-t border-border pt-8 space-y-4">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{t('tax_vat_heading')}
|
||||
</h2>
|
||||
@@ -140,7 +140,7 @@ export function TaxSettingsForm({ settings }: TaxSettingsFormProps) {
|
||||
</section>
|
||||
|
||||
{/* Tax contact — required for SKV-filings */}
|
||||
<section className="border-t border-border/8 pt-8 space-y-4">
|
||||
<section className="border-t border-border pt-8 space-y-4">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{t('tax_contact_heading')}
|
||||
</h2>
|
||||
@@ -181,7 +181,7 @@ export function TaxSettingsForm({ settings }: TaxSettingsFormProps) {
|
||||
</section>
|
||||
|
||||
{/* Fiscal year & salaries */}
|
||||
<section className="border-t border-border/8 pt-8 space-y-4">
|
||||
<section className="border-t border-border pt-8 space-y-4">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{t('fiscal_year_salaries_heading')}
|
||||
</h2>
|
||||
@@ -235,7 +235,7 @@ export function TaxSettingsForm({ settings }: TaxSettingsFormProps) {
|
||||
</section>
|
||||
|
||||
{/* Preliminary tax */}
|
||||
<section className="border-t border-border/8 pt-8 space-y-4">
|
||||
<section className="border-t border-border pt-8 space-y-4">
|
||||
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{t('preliminary_tax_heading')}
|
||||
</h2>
|
||||
|
||||
@@ -164,7 +164,7 @@ export function AccountSettingsContent() {
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => setTheme(value)}
|
||||
className={`flex items-center gap-2 rounded-lg border-2 px-4 py-2.5 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-2 rounded-lg border-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
theme === value
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/40'
|
||||
@@ -193,7 +193,7 @@ export function AccountSettingsContent() {
|
||||
type="button"
|
||||
onClick={() => handleLocaleChange(value)}
|
||||
disabled={savingLocale}
|
||||
className={`flex items-center gap-2 rounded-lg border-2 px-4 py-2.5 text-sm font-medium transition-colors disabled:opacity-50 ${
|
||||
className={`flex items-center gap-2 rounded-lg border-2 px-4 py-3 text-sm font-medium transition-colors disabled:opacity-50 ${
|
||||
activeLocale === value
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/40'
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { formatVoucher } from '@/lib/bookkeeping/voucher-series-resolver'
|
||||
import type { StoredSkattekontoTransaction } from '@/types/skatteverket'
|
||||
|
||||
@@ -135,7 +135,7 @@ export function SkattekontoMatchDialog({
|
||||
<DialogDescription>
|
||||
{row && (
|
||||
<>
|
||||
{row.transaktionsdatum} • {row.transaktionstext} •{' '}
|
||||
{formatDate(row.transaktionsdatum)} • {row.transaktionstext} •{' '}
|
||||
<span className="tabular-nums">
|
||||
{formatCurrency(Number(row.belopp_skatteverket))}
|
||||
</span>
|
||||
@@ -176,7 +176,7 @@ export function SkattekontoMatchDialog({
|
||||
<TableBody>
|
||||
{candidates.map(c => (
|
||||
<TableRow key={c.journal_entry_id}>
|
||||
<TableCell className="tabular-nums">{c.entry_date}</TableCell>
|
||||
<TableCell className="tabular-nums">{formatDate(c.entry_date)}</TableCell>
|
||||
<TableCell className="tabular-nums">
|
||||
{formatVoucher(c)}
|
||||
</TableCell>
|
||||
|
||||
@@ -243,7 +243,7 @@ export default function SupplierForm({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="default_currency">{t('default_account_label')}</Label>
|
||||
<Label htmlFor="default_currency">{t('default_currency_label')}</Label>
|
||||
<Controller
|
||||
name="default_currency"
|
||||
control={control}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CalendarClock } from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { AccountNumber } from '@/components/ui/account-number'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
import {
|
||||
resolveReverseChargeRate,
|
||||
isReverseChargeBasisAccount,
|
||||
@@ -276,16 +276,16 @@ export function SupplierInvoiceReviewContent({
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t('invoice_date_label')}</span>
|
||||
<p className="font-medium">{invoiceDate}</p>
|
||||
<p className="font-medium">{formatDate(invoiceDate)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t('due_date_label')}</span>
|
||||
<p className="font-medium">{dueDate}</p>
|
||||
<p className="font-medium">{formatDate(dueDate)}</p>
|
||||
</div>
|
||||
{deliveryDate && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t('delivery_date_label')}</span>
|
||||
<p className="font-medium">{deliveryDate}</p>
|
||||
<p className="font-medium">{formatDate(deliveryDate)}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -380,21 +380,21 @@ export function SupplierInvoiceReviewContent({
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('net_excl_vat')}</span>
|
||||
<span>{formatCurrency(subtotal, currency)}</span>
|
||||
<span className="tabular-nums">{formatCurrency(subtotal, currency)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{reverseCharge ? t('vat_reverse_charge') : t('vat_label_short')}</span>
|
||||
<span>{formatCurrency(totalVat, currency)}</span>
|
||||
<span className="tabular-nums">{formatCurrency(totalVat, currency)}</span>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="flex justify-between font-bold text-xl sm:text-2xl">
|
||||
<span>{t('total_label')}</span>
|
||||
<span>{formatCurrency(total, currency)}</span>
|
||||
<span className="tabular-nums">{formatCurrency(total, currency)}</span>
|
||||
</div>
|
||||
{currency !== 'SEK' && exchangeRate && (
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>{t('review_sek_amount_at_rate', { rate: exchangeRate })}</span>
|
||||
<span>{formatCurrency(total * parseFloat(exchangeRate))}</span>
|
||||
<span className="tabular-nums">{formatCurrency(total * parseFloat(exchangeRate))}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -260,7 +260,7 @@ export default function QuickReviewDialog({
|
||||
{/* Transaction summary */}
|
||||
<div className="flex items-center gap-3 rounded-lg border p-3">
|
||||
<div
|
||||
className={`h-9 w-9 rounded-full flex items-center justify-center flex-shrink-0 ${isIncome ? 'bg-success/10 text-success' : 'bg-destructive/10 text-destructive'}`}
|
||||
className={`h-9 w-9 rounded-full flex items-center justify-center flex-shrink-0 ${isIncome ? 'text-success' : 'text-destructive'}`}
|
||||
>
|
||||
{isIncome ? (
|
||||
<ArrowUpRight className="h-4 w-4" />
|
||||
|
||||
@@ -123,8 +123,8 @@ export default function TransactionAttachDocumentDialog({
|
||||
<div
|
||||
className={`h-9 w-9 rounded-full flex items-center justify-center flex-shrink-0 ${
|
||||
isIncome
|
||||
? 'bg-success/10 text-success'
|
||||
: 'bg-destructive/10 text-destructive'
|
||||
? 'text-success'
|
||||
: 'text-destructive'
|
||||
}`}
|
||||
>
|
||||
{isIncome ? (
|
||||
|
||||
@@ -31,7 +31,7 @@ const DataListHeader = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-3 border-b border-border bg-secondary/40 px-4 py-2.5",
|
||||
"flex flex-wrap items-center gap-3 border-b border-border bg-secondary/40 px-4 py-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function EmptyState({
|
||||
<div className={cn('flex flex-col items-center justify-center py-12 px-4 text-center', className)}>
|
||||
{Icon && (
|
||||
<div className="mb-6">
|
||||
<div className="p-5 rounded-full bg-muted">
|
||||
<div className="p-4 rounded-full bg-muted">
|
||||
<Icon className="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@ function InfoTooltip({
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded-full p-0.5 text-muted-foreground/70 hover:text-muted-foreground hover:bg-muted/50 transition-all cursor-help',
|
||||
'inline-flex items-center justify-center rounded-full p-0.5 text-muted-foreground/70 hover:text-muted-foreground hover:bg-muted/50 transition-colors cursor-help',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -51,7 +51,7 @@ export function SuccessAnimation({
|
||||
|
||||
{/* Text */}
|
||||
<div className="text-center animate-slide-up" style={{ animationDelay: '300ms' }}>
|
||||
<p className="text-lg font-display font-medium">{title}</p>
|
||||
<p className="text-lg font-display">{title}</p>
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground mt-1">{description}</p>
|
||||
)}
|
||||
|
||||
@@ -77,7 +77,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-3 py-1.5 text-sm shadow-xs transition-colors',
|
||||
'flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-3 py-1.5 text-sm transition-colors',
|
||||
'focus-within:ring-1 focus-within:ring-ring',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
className
|
||||
|
||||
Reference in New Issue
Block a user