UI badge cleanup + /chart-of-accounts route + loop skills (#850)
Bundles three separable concerns: - style(ui): badge audit + cleanup across 45 files — real-status chips use Badge variants (raw Tailwind colors dropped), non-status count/type/label chips demoted to muted text, clustered badges consolidated; 20 unused imports removed. - feat(bookkeeping): Kontoplan moved to a dedicated /chart-of-accounts route (nav + command palette wired); /bookkeeping shows the journal list only. - chore(skills): loop-* automation skills + design-scan workflow under .claude/. fix(reports): restored the destructive count badge on blocking errors in the periodisk sammanställning (EC Sales List) — a genuine status cue the audit had wrongly flattened; flagged by the PR reviewer and Swedish compliance bot, now clean. All CI green; compliance bots report no findings. 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
d63d2aecf0
commit
2da9c71eb3
@@ -641,19 +641,24 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
|
||||
label: submission.status,
|
||||
variant: 'outline' as const,
|
||||
}
|
||||
const envLabel =
|
||||
submission.environment === 'prod'
|
||||
? ''
|
||||
: submission.environment === 'test'
|
||||
? 'Testmiljö'
|
||||
: submission.environment === 'acceptans'
|
||||
? 'Acceptansmiljö'
|
||||
: submission.environment.charAt(0).toUpperCase() +
|
||||
submission.environment.slice(1)
|
||||
return (
|
||||
<div
|
||||
key={submission.id}
|
||||
className="flex items-start justify-between gap-4 border-b border-border last:border-b-0 pb-3 last:pb-0"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={badge.variant}>{badge.label}</Badge>
|
||||
{submission.environment !== 'prod' && (
|
||||
<Badge variant="outline">{submission.environment}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant={badge.variant}>{badge.label}</Badge>
|
||||
<p className="text-xs text-muted-foreground tabular-nums">
|
||||
{envLabel ? `${envLabel} · ` : ''}
|
||||
{formatDate(submission.created_at)}
|
||||
{submission.idnummer ? ` · id ${submission.idnummer}` : ''}
|
||||
{submission.undertecknare_namn ? ` · ${submission.undertecknare_namn}` : ''}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import {
|
||||
Dialog,
|
||||
@@ -193,17 +192,14 @@ export default function BookingTemplatePicker({ onApply, entityType, defaultAmou
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm">{t.name}</span>
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 shrink-0">
|
||||
<ScopeIcon className="h-3 w-3 mr-0.5" />
|
||||
<div className="font-medium text-sm">{t.name}</div>
|
||||
<div className="flex items-center gap-1 text-[11px] text-muted-foreground mt-0.5">
|
||||
<ScopeIcon className="h-3 w-3 shrink-0" />
|
||||
<span>
|
||||
{SCOPE_LABELS[scope]}
|
||||
</Badge>
|
||||
{t.entity_type !== 'all' && (
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0 shrink-0">
|
||||
{t.entity_type === 'enskild_firma' ? 'EF' : 'AB'}
|
||||
</Badge>
|
||||
)}
|
||||
{t.entity_type !== 'all' &&
|
||||
` · ${t.entity_type === 'enskild_firma' ? 'EF' : 'AB'}`}
|
||||
</span>
|
||||
</div>
|
||||
{t.description && (
|
||||
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2">
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ChevronDown, ChevronRight, Search } from 'lucide-react'
|
||||
@@ -127,7 +126,7 @@ export default function ChartOfAccounts() {
|
||||
<span className="font-semibold">
|
||||
Klass {cls}: {CLASS_LABELS[classNum] || ''}
|
||||
</span>
|
||||
<Badge variant="secondary">{classAccounts.length} konton</Badge>
|
||||
<span className="text-sm text-muted-foreground">{classAccounts.length} konton</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -179,18 +178,16 @@ export default function ChartOfAccounts() {
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{account.account_type === 'asset'
|
||||
? 'Tillgång'
|
||||
: account.account_type === 'liability'
|
||||
? 'Skuld'
|
||||
: account.account_type === 'equity'
|
||||
? 'EK'
|
||||
: account.account_type === 'revenue'
|
||||
? 'Intäkt'
|
||||
: 'Kostnad'}
|
||||
</Badge>
|
||||
<td className="py-2 text-center text-xs text-muted-foreground">
|
||||
{account.account_type === 'asset'
|
||||
? 'Tillgång'
|
||||
: account.account_type === 'liability'
|
||||
? 'Skuld'
|
||||
: account.account_type === 'equity'
|
||||
? 'EK'
|
||||
: account.account_type === 'revenue'
|
||||
? 'Intäkt'
|
||||
: 'Kostnad'}
|
||||
</td>
|
||||
<td className="py-2 text-center text-xs text-muted-foreground">
|
||||
{account.normal_balance === 'debit' ? 'Debet' : 'Kredit'}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
@@ -283,9 +282,9 @@ export default function ChartOfAccountsManager() {
|
||||
<TabsList>
|
||||
<TabsTrigger value="my-accounts">
|
||||
{t('tab_my_accounts')}
|
||||
<Badge variant="secondary" className="ml-1.5 text-xs">
|
||||
{accounts.length}
|
||||
</Badge>
|
||||
<span className="ml-1.5 text-xs text-muted-foreground tabular-nums">
|
||||
({accounts.length})
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="bas-catalog">
|
||||
<BookOpen className="mr-1.5 h-3.5 w-3.5" />
|
||||
@@ -349,9 +348,9 @@ export default function ChartOfAccountsManager() {
|
||||
<span className="font-semibold text-left">
|
||||
{t('class_heading', { cls, label: classLabel(classNum) })}
|
||||
</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{activeCount}/{classAccounts.length}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">
|
||||
{t('active_count_label', { active: activeCount, total: classAccounts.length })}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -383,9 +382,9 @@ export default function ChartOfAccountsManager() {
|
||||
<span className="flex items-center gap-1.5">
|
||||
{account.account_name}
|
||||
{account.is_system_account && (
|
||||
<Badge variant="outline" className="text-[10px] px-1 py-0">
|
||||
<span className="text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||
{t('system_badge')}
|
||||
</Badge>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
@@ -395,9 +394,9 @@ export default function ChartOfAccountsManager() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{typeLabel(account.account_type)}
|
||||
</Badge>
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<Switch
|
||||
@@ -479,9 +478,9 @@ export default function ChartOfAccountsManager() {
|
||||
<span className="font-semibold text-left">
|
||||
{t('class_heading', { cls, label: classLabel(classNum) })}
|
||||
</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<span className="text-xs text-muted-foreground tabular-nums">
|
||||
{t('active_count_label', { active: activatedCount, total: classAccounts.length })}
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -524,9 +523,9 @@ export default function ChartOfAccountsManager() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{typeLabel(account.account_type)}
|
||||
</Badge>
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 text-right">
|
||||
{account.is_activated ? (
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import AccountCombobox from '@/components/bookkeeping/AccountCombobox'
|
||||
import CorrectionPreview from '@/components/bookkeeping/CorrectionPreview'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
@@ -166,9 +165,9 @@ export default function CorrectionEntryDialog({ entry, open, onOpenChange, onCor
|
||||
{/* Original entry metadata — lines live inside CorrectionPreview below */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground flex-wrap">
|
||||
<span className="text-muted-foreground">Original</span>
|
||||
<span className="font-mono">{formatVoucher(entry)}</span>
|
||||
<span className="tabular-nums">{formatDate(entry.entry_date)}</span>
|
||||
<Badge variant="outline" className="text-xs">Original</Badge>
|
||||
</div>
|
||||
<p className="text-sm">{entry.description}</p>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -228,15 +227,13 @@ export default function InboxDocumentPicker({ open, onClose, journalEntryId, onL
|
||||
<span className="text-sm font-medium truncate">
|
||||
{it.supplier_name ?? it.file_name}
|
||||
</span>
|
||||
<Badge variant="outline" className="shrink-0 text-[10px]">
|
||||
{sourceLabel}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground tabular-nums">
|
||||
{it.invoice_date && <span>{formatDate(it.invoice_date)}</span>}
|
||||
{it.supplier_name && (
|
||||
<span className="truncate font-normal">{it.file_name}</span>
|
||||
)}
|
||||
<span className="shrink-0">{sourceLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
{it.amount != null && (
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
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 { Skeleton } from '@/components/ui/skeleton'
|
||||
@@ -327,9 +326,7 @@ export function EfDeclarationSection({
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-base">{item.label}</CardTitle>
|
||||
<p className="text-sm text-muted-foreground mt-1">{item.description}</p>
|
||||
<Badge variant="outline" className="mt-2">
|
||||
{item.ne_ruta}
|
||||
</Badge>
|
||||
<p className="text-xs text-muted-foreground mt-1">NE-ruta {item.ne_ruta}</p>
|
||||
</div>
|
||||
<p className="font-display text-2xl tabular-nums shrink-0">
|
||||
{formatCurrency(item.amount)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Wallet,
|
||||
Building2,
|
||||
BookOpen,
|
||||
ListTree,
|
||||
BarChart3,
|
||||
Upload,
|
||||
Package,
|
||||
@@ -47,6 +48,7 @@ const PAGE_ENTRIES: Entry[] = [
|
||||
{ id: 'leverantörer', label: 'Leverantörer', icon: Building2, href: '/suppliers' },
|
||||
{ id: 'leverantörsfakturor', label: 'Leverantörsfakturor', icon: Wallet, href: '/supplier-invoices' },
|
||||
{ id: 'bokföring', label: 'Bokföring', icon: BookOpen, href: '/bookkeeping', keywords: 'verifikat journal ledger' },
|
||||
{ id: 'kontoplan', label: 'Kontoplan', icon: ListTree, href: '/chart-of-accounts', keywords: 'kontoplan konton bas chart of accounts konto' },
|
||||
{ id: 'anläggningstillgångar', label: 'Anläggningstillgångar', icon: Package, href: '/assets', keywords: 'tillgångar assets' },
|
||||
{ id: 'rapporter', label: 'Rapporter', icon: BarChart3, href: '/reports' },
|
||||
{ id: 'rapport-resultatrapport', label: 'Visa rapport: Resultatrapport', icon: BarChart3, href: '/reports/resultatrapport', keywords: 'rapport resultat intäkter kostnader' },
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Users,
|
||||
ArrowLeftRight,
|
||||
BookOpen,
|
||||
ListTree,
|
||||
BarChart3,
|
||||
Settings,
|
||||
LogOut,
|
||||
@@ -92,6 +93,7 @@ type NavLabelKey =
|
||||
| 'review'
|
||||
| 'transactions'
|
||||
| 'bookkeeping'
|
||||
| 'chart_of_accounts'
|
||||
| 'assets'
|
||||
| 'reports'
|
||||
| 'import'
|
||||
@@ -144,6 +146,7 @@ const navItems: NavItem[] = [
|
||||
// Redovisning dropdown
|
||||
{ href: '/kpi', labelKey: 'kpi', icon: TrendingUp, group: 'redovisning' },
|
||||
{ href: '/bookkeeping', labelKey: 'bookkeeping', icon: BookOpen, group: 'redovisning' },
|
||||
{ href: '/chart-of-accounts', labelKey: 'chart_of_accounts', icon: ListTree, group: 'redovisning' },
|
||||
{ href: '/assets', labelKey: 'assets', icon: Package, group: 'redovisning' },
|
||||
{ href: '/reports', labelKey: 'reports', icon: BarChart3, group: 'redovisning' },
|
||||
{ href: '/import', labelKey: 'import', icon: Upload, group: 'redovisning' },
|
||||
|
||||
@@ -158,9 +158,9 @@ export function TaxTodoWidget({ deadlines, onStatusChange }: TaxTodoWidgetProps)
|
||||
{formatDate(deadline.due_date)}
|
||||
</span>
|
||||
{deadline.tax_deadline_type && (
|
||||
<Badge variant="outline" className="text-xs px-1.5 py-0 h-5">
|
||||
{TAX_DEADLINE_TYPE_LABELS[deadline.tax_deadline_type]}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
· {TAX_DEADLINE_TYPE_LABELS[deadline.tax_deadline_type]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -142,17 +142,16 @@ export function UpcomingDeadlinesWidget({ deadlines, maxItems = 5, onStatusChang
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium truncate">{deadline.title}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDate(deadline.due_date)}
|
||||
{deadline.due_time && ` ${t('time_prefix')} ${deadline.due_time.slice(0, 5)}`}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDate(deadline.due_date)}
|
||||
{deadline.due_time && ` ${t('time_prefix')} ${deadline.due_time.slice(0, 5)}`}
|
||||
{deadline.tax_deadline_type && (
|
||||
<Badge variant="outline" className="text-xs px-1 py-0">
|
||||
{t('tax_badge')}
|
||||
</Badge>
|
||||
<>
|
||||
{' '}
|
||||
<span className="text-muted-foreground/30">·</span> {t('tax_badge')}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,22 +2,20 @@
|
||||
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { ExtensionCategory } from '@/lib/extensions/types'
|
||||
|
||||
const CATEGORY_CONFIG: Record<ExtensionCategory, { labelKey: string; className: string }> = {
|
||||
accounting: { labelKey: 'category_accounting', className: 'bg-destructive/10 text-destructive border-destructive/30' },
|
||||
reports: { labelKey: 'category_reports', className: 'bg-primary/10 text-primary border-primary/30' },
|
||||
import: { labelKey: 'category_import', className: 'bg-success/10 text-success border-success/30' },
|
||||
operations: { labelKey: 'category_operations', className: 'bg-muted text-muted-foreground border-border' },
|
||||
const CATEGORY_LABEL_KEY: Record<ExtensionCategory, string> = {
|
||||
accounting: 'category_accounting',
|
||||
reports: 'category_reports',
|
||||
import: 'category_import',
|
||||
operations: 'category_operations',
|
||||
}
|
||||
|
||||
export default function CategoryBadge({ category }: { category: ExtensionCategory }) {
|
||||
const t = useTranslations('extensions')
|
||||
const config = CATEGORY_CONFIG[category]
|
||||
return (
|
||||
<Badge variant="outline" className={cn('text-[10px] font-medium', config.className)}>
|
||||
{t(config.labelKey)}
|
||||
<Badge variant="outline" className="text-[10px] font-medium">
|
||||
{t(CATEGORY_LABEL_KEY[category])}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Button, buttonVariants } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -304,10 +305,10 @@ function ProviderStep({
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="font-medium">{providerInfo?.name ?? consent.provider}</p>
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-600 dark:text-emerald-400">
|
||||
<Badge variant="success" className="gap-1">
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
Ansluten
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-0.5 space-y-0.5">
|
||||
{consent.companyName && (
|
||||
@@ -411,19 +412,13 @@ function ProviderStep({
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="font-medium">{provider.name}</p>
|
||||
{comingSoon && (
|
||||
<span className="inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
|
||||
Kommer snart
|
||||
</span>
|
||||
<Badge variant="secondary">Kommer snart</Badge>
|
||||
)}
|
||||
{alreadyConnected && (
|
||||
<span className="inline-flex items-center rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-600 dark:text-emerald-400">
|
||||
Ansluten
|
||||
</span>
|
||||
<Badge variant="success">Ansluten</Badge>
|
||||
)}
|
||||
{needsSieFirst && !comingSoon && !alreadyConnected && (
|
||||
<span className="inline-flex items-center rounded-full bg-amber-500/10 px-2 py-0.5 text-[10px] font-medium text-amber-600 dark:text-amber-500">
|
||||
SIE krävs först
|
||||
</span>
|
||||
<Badge variant="warning">SIE krävs först</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import {
|
||||
@@ -169,11 +168,9 @@ export default function BankFileConfirmStep({
|
||||
|
||||
{/* Additional info */}
|
||||
{refsCount > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline" className="text-primary border-primary/30">
|
||||
<Link2 className="mr-1 h-3 w-3" />
|
||||
{refsCount} med OCR/referens
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Link2 className="h-3 w-3" />
|
||||
{refsCount} med OCR/referens
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -160,14 +159,8 @@ export default function BankFilePreviewStep({
|
||||
</TableCell>
|
||||
)}
|
||||
{transactions.some((t) => t.reference) && (
|
||||
<TableCell className="text-sm">
|
||||
{tx.reference ? (
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{tx.reference}
|
||||
</Badge>
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
<TableCell className="font-mono text-sm text-muted-foreground">
|
||||
{tx.reference ? tx.reference : '–'}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useState, useEffect, useRef } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
@@ -373,9 +372,9 @@ export default function ImportReviewStep({
|
||||
<Label htmlFor="mark-no-doc-required" className="font-medium flex items-center gap-2">
|
||||
Markera som "Inget underlag krävs"
|
||||
{isHistoricalImport && (
|
||||
<Badge variant="secondary" className="text-[10px] font-normal">
|
||||
Rekommenderas vid migrering
|
||||
</Badge>
|
||||
<span className="text-xs font-normal text-muted-foreground">
|
||||
· Rekommenderas vid migrering
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -25,7 +25,6 @@ import { formatCurrency } from '@/lib/utils'
|
||||
import { getVatRules, getAvailableVatRates } from '@/lib/invoices/vat-rules'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
|
||||
import { Loader2, Plus, Trash2, ArrowLeft, Send, Eye, Landmark, Lock, AlertTriangle, MoreVertical, CalendarClock } from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
@@ -1526,9 +1525,9 @@ export default function InvoiceEditor(props: InvoiceEditorProps = { mode: 'creat
|
||||
{isInvoiceDoc && watchItems[index]?.deduction_type && (
|
||||
<div className="md:col-span-12 mt-2 md:mt-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary" className="tabular-nums">
|
||||
{watchItems[index]?.deduction_type === 'rot' ? 'ROT(30)' : 'RUT(50)'}
|
||||
</Badge>
|
||||
<span className="text-xs font-medium tabular-nums text-muted-foreground">
|
||||
{watchItems[index]?.deduction_type === 'rot' ? 'ROT 30%' : 'RUT 50%'}
|
||||
</span>
|
||||
<Controller
|
||||
name={`items.${index}.work_type`}
|
||||
control={control}
|
||||
|
||||
@@ -236,28 +236,14 @@ export function InvoiceReviewContent({
|
||||
{(yourReference || ourReference || notes) && (
|
||||
<div className="border-t pt-3 space-y-2 text-sm text-muted-foreground">
|
||||
{yourReference && (
|
||||
<div>
|
||||
<span>{t('your_reference')}</span>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{yourReference.split(',').map((ref, i) => (
|
||||
<Badge key={i} variant="secondary" className="text-xs font-normal">
|
||||
{ref.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<span>{t('your_reference')}</span> {yourReference}
|
||||
</p>
|
||||
)}
|
||||
{ourReference && (
|
||||
<div>
|
||||
<span>{t('our_reference')}</span>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{ourReference.split(',').map((ref, i) => (
|
||||
<Badge key={i} variant="secondary" className="text-xs font-normal">
|
||||
{ref.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<span>{t('our_reference')}</span> {ourReference}
|
||||
</p>
|
||||
)}
|
||||
{notes && <p>{t('notes_prefix', { notes })}</p>}
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,16 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getSector } from '@/lib/extensions/sectors'
|
||||
import { resolveIcon } from '@/lib/extensions/icon-resolver'
|
||||
import CategoryBadge from '@/components/extensions/CategoryBadge'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { ArrowRight, Loader2 } from 'lucide-react'
|
||||
import type { SectorSlug, ExtensionDefinition } from '@/lib/extensions/types'
|
||||
import type { SectorSlug, ExtensionDefinition, ExtensionCategory } from '@/lib/extensions/types'
|
||||
|
||||
const CATEGORY_LABEL_KEY: Record<ExtensionCategory, string> = {
|
||||
accounting: 'category_accounting',
|
||||
reports: 'category_reports',
|
||||
import: 'category_import',
|
||||
operations: 'category_operations',
|
||||
}
|
||||
|
||||
interface Step3Props {
|
||||
sectorSlug: string | null
|
||||
@@ -18,6 +25,7 @@ interface Step3Props {
|
||||
}
|
||||
|
||||
export default function Step3ExtensionSuggestions({ sectorSlug, onNext, onBack, isSaving }: Step3Props) {
|
||||
const tExt = useTranslations('extensions')
|
||||
const generalSector = getSector('general')
|
||||
const selectedSector = sectorSlug ? getSector(sectorSlug as SectorSlug) : null
|
||||
|
||||
@@ -90,7 +98,9 @@ export default function Step3ExtensionSuggestions({ sectorSlug, onNext, onBack,
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-medium">{ext.name}</p>
|
||||
<CategoryBadge category={ext.category} />
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{tExt(CATEGORY_LABEL_KEY[ext.category])}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground line-clamp-1">{ext.description}</p>
|
||||
</div>
|
||||
|
||||
@@ -771,9 +771,9 @@ export function BankReconciliationView({ periodId, periodBounds }: BankReconcili
|
||||
</td>
|
||||
<td className="py-2 tabular-nums">{formatDate(m.entry_date)}</td>
|
||||
<td className="py-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{METHOD_LABELS[m.method] || m.method}
|
||||
</Badge>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -840,9 +840,7 @@ export function BankReconciliationView({ periodId, periodBounds }: BankReconcili
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground tabular-nums">
|
||||
<span>{formatDate(tx.date)}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<Badge variant="outline" className="text-[10px] uppercase tracking-wider">
|
||||
{tx.currency}
|
||||
</Badge>
|
||||
<span>{tx.currency}</span>
|
||||
{tx.reference && (
|
||||
<>
|
||||
<span aria-hidden>·</span>
|
||||
@@ -1066,7 +1064,7 @@ export function BankReconciliationView({ periodId, periodBounds }: BankReconcili
|
||||
<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>
|
||||
<span className="tabular-nums">{tx.currency}</span>
|
||||
</td>
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{formatCurrency(tx.amount)}
|
||||
@@ -1130,9 +1128,9 @@ export function BankReconciliationView({ periodId, periodBounds }: BankReconcili
|
||||
</td>
|
||||
<td className="py-2">
|
||||
{tx.reconciliation_method && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{METHOD_LABELS[tx.reconciliation_method] || tx.reconciliation_method}
|
||||
</Badge>
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2">
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Download, AlertCircle, Info } from 'lucide-react'
|
||||
import { AccountNumber } from '@/components/ui/account-number'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
@@ -133,19 +132,13 @@ export function INK2DeclarationView({ periodId }: { periodId: string }) {
|
||||
{/* Company info */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>
|
||||
{data.companyInfo.companyName}
|
||||
</CardTitle>
|
||||
<Badge variant="secondary">
|
||||
{data.fiscalYear.name}
|
||||
</Badge>
|
||||
</div>
|
||||
{data.companyInfo.orgNumber && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Org.nr: {data.companyInfo.orgNumber}
|
||||
</p>
|
||||
)}
|
||||
<CardTitle>
|
||||
{data.companyInfo.companyName}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{data.fiscalYear.name}
|
||||
{data.companyInfo.orgNumber && ` · Org.nr: ${data.companyInfo.orgNumber}`}
|
||||
</p>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Download, AlertCircle } from 'lucide-react'
|
||||
import { AccountNumber } from '@/components/ui/account-number'
|
||||
import type { NEDeclaration } from '@/lib/reports/ne-bilaga/types'
|
||||
@@ -128,14 +127,12 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
{/* Company info */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>
|
||||
{data.companyInfo.companyName}
|
||||
</CardTitle>
|
||||
<Badge variant="secondary">
|
||||
{data.fiscalYear.name}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle>
|
||||
{data.companyInfo.companyName}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{data.fiscalYear.name}
|
||||
</p>
|
||||
{data.companyInfo.orgNumber && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Org.nr: {data.companyInfo.orgNumber}
|
||||
|
||||
@@ -63,7 +63,9 @@ export function ReportLibrary({
|
||||
}
|
||||
|
||||
function EntityBadge({ item }: { item: ReportDescriptor }) {
|
||||
if (item.entityType === 'enskild_firma') return <Badge variant="outline">EF</Badge>
|
||||
if (item.entityType === 'aktiebolag') return <Badge variant="outline">AB</Badge>
|
||||
if (item.entityType === 'enskild_firma')
|
||||
return <span className="text-xs text-muted-foreground">EF</span>
|
||||
if (item.entityType === 'aktiebolag')
|
||||
return <span className="text-xs text-muted-foreground">AB</span>
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ function SkatteverketPanelInner({ periodType, year, period, hasData, rutor }: Sk
|
||||
Skicka till Skatteverket
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-success border-success/30 gap-1">
|
||||
<Badge variant="success" className="gap-1">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
Ansluten
|
||||
</Badge>
|
||||
|
||||
@@ -298,8 +298,9 @@ export function AgentMemoryPanel() {
|
||||
|
||||
<div className="flex-1 min-w-0 space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant={kindBadge(row.kind)}>{KIND_LABEL[row.kind]}</Badge>
|
||||
<Badge variant="outline">{SOURCE_LABEL[row.source]}</Badge>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{KIND_LABEL[row.kind]} · {SOURCE_LABEL[row.source]}
|
||||
</span>
|
||||
{dimmed && <Badge variant="secondary">Dold</Badge>}
|
||||
</div>
|
||||
|
||||
@@ -388,16 +389,3 @@ export function AgentMemoryPanel() {
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function kindBadge(kind: Kind): 'default' | 'secondary' | 'outline' | 'success' | 'warning' {
|
||||
switch (kind) {
|
||||
case 'fact':
|
||||
return 'default'
|
||||
case 'preference':
|
||||
return 'secondary'
|
||||
case 'pattern':
|
||||
return 'outline'
|
||||
case 'correction':
|
||||
return 'warning'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ export function ApiKeysPanel() {
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<p className="text-sm font-medium">Claude.ai</p>
|
||||
<Badge variant="secondary" className="text-[10px] font-normal px-1.5 py-0">{t('recommended_badge')}</Badge>
|
||||
<span className="text-xs text-muted-foreground">{t('recommended_badge')}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
{t.rich('claude_ai_instructions', {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import type { BillingPlan } from '@/lib/stripe/client'
|
||||
|
||||
@@ -96,7 +95,7 @@ export function BillingActions({ isPaying, configured }: { isPaying: boolean; co
|
||||
'yearly',
|
||||
<>
|
||||
Årsvis
|
||||
<Badge variant="success" className="ml-2">Spara 2 mån</Badge>
|
||||
<span className="ml-2 text-xs text-muted-foreground">Spara 2 mån</span>
|
||||
</>,
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -314,7 +314,7 @@ function TemplateSection({
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
<h3 className="text-sm font-medium">{title}</h3>
|
||||
<Badge variant="secondary" className="text-xs">{templates.length}</Badge>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">{templates.length}</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{templates.map((tt) => {
|
||||
@@ -335,14 +335,10 @@ function TemplateSection({
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-sm font-medium">{tt.name}</span>
|
||||
<div className="flex items-center gap-1.5 mt-0.5 flex-wrap">
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{TEMPLATE_CATEGORY_LABELS[tt.category]}
|
||||
</Badge>
|
||||
{tt.entity_type !== 'all' && (
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
||||
{entityLabels[tt.entity_type]}
|
||||
</Badge>
|
||||
)}
|
||||
{tt.entity_type !== 'all' && ` · ${entityLabels[tt.entity_type]}`}
|
||||
</span>
|
||||
{!isConvertible && (
|
||||
<Badge variant="warning" className="text-[10px] px-1.5 py-0">
|
||||
{t('unconvertible_badge')}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Calendar, Copy, RefreshCw, Loader2, ExternalLink, Check } from 'lucide-react'
|
||||
import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog'
|
||||
import type { CalendarFeed } from '@/types'
|
||||
@@ -261,9 +260,10 @@ export function CalendarFeedSettings() {
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<span className="text-muted-foreground/30">·</span>
|
||||
<span className="tabular-nums">
|
||||
{t('times_count', { count: feed.access_count })}
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
@@ -242,9 +241,7 @@ export function CompanyMembersSection() {
|
||||
{roleLabels[member.role] || member.role}
|
||||
</span>
|
||||
{member.source === 'team' && (
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
||||
{t('members_team_badge')}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">· {t('members_team_badge')}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -295,9 +292,9 @@ export function CompanyMembersSection() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{roleLabels[inv.role] || inv.role}
|
||||
</Badge>
|
||||
</span>
|
||||
{canInvite && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslations } from 'next-intl'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { Loader2, Trash2, Users, ChevronDown } from 'lucide-react'
|
||||
import { formatAccountWithName } from '@/lib/bookkeeping/client-account-names'
|
||||
@@ -130,9 +129,6 @@ export function CounterpartyTemplatesPanel() {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-medium truncate">{formatCounterpartyName(tt.counterparty_name)}</p>
|
||||
<Badge variant="outline" className="text-[10px] shrink-0">
|
||||
{SOURCE_LABELS[tt.source] || tt.source}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 mt-0.5 text-xs text-muted-foreground">
|
||||
{isMultiLine ? (
|
||||
@@ -157,6 +153,8 @@ export function CounterpartyTemplatesPanel() {
|
||||
<span className={`tabular-nums ${confidenceColor(Number(tt.confidence))}`}>
|
||||
{Math.round(Number(tt.confidence) * 100)}%
|
||||
</span>
|
||||
<span className="text-muted-foreground/30">·</span>
|
||||
<span>{SOURCE_LABELS[tt.source] || tt.source}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDown className={`h-4 w-4 text-muted-foreground shrink-0 transition-transform ${isExpanded ? 'rotate-180' : ''}`} />
|
||||
@@ -172,9 +170,9 @@ export function CounterpartyTemplatesPanel() {
|
||||
<div className="space-y-1">
|
||||
{tt.line_pattern!.map((lp, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-xs">
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 w-14 justify-center">
|
||||
<span className="w-14 shrink-0 text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||
{lp.side === 'debit' ? t('debit_label') : t('credit_label')}
|
||||
</Badge>
|
||||
</span>
|
||||
<span className="font-mono">{formatAccountWithName(lp.account)}</span>
|
||||
{lp.type === 'vat' && lp.vat_rate && (
|
||||
<span className="text-muted-foreground">{t('vat_paren', { rate: Math.round(lp.vat_rate * 100) })}</span>
|
||||
@@ -188,11 +186,11 @@ export function CounterpartyTemplatesPanel() {
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 w-14 justify-center">{t('debit_label')}</Badge>
|
||||
<span className="w-14 shrink-0 text-[10px] uppercase tracking-wider text-muted-foreground">{t('debit_label')}</span>
|
||||
<span className="font-mono">{formatAccountWithName(tt.debit_account)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 w-14 justify-center">{t('credit_label')}</Badge>
|
||||
<span className="w-14 shrink-0 text-[10px] uppercase tracking-wider text-muted-foreground">{t('credit_label')}</span>
|
||||
<span className="font-mono">{formatAccountWithName(tt.credit_account)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -254,7 +254,7 @@ function EnvironmentBadge({ environment, disabled }: { environment?: Environment
|
||||
}
|
||||
if (environment === 'test') {
|
||||
return (
|
||||
<Badge variant="outline" className="border-amber-400 text-amber-700 dark:border-amber-600 dark:text-amber-400">
|
||||
<Badge variant="warning">
|
||||
<FlaskConical className="mr-1 h-3 w-3" />
|
||||
{t('env_test')}
|
||||
</Badge>
|
||||
@@ -262,7 +262,7 @@ function EnvironmentBadge({ environment, disabled }: { environment?: Environment
|
||||
}
|
||||
if (environment === 'prod') {
|
||||
return (
|
||||
<Badge variant="outline" className="border-emerald-400 text-emerald-700 dark:border-emerald-600 dark:text-emerald-400">
|
||||
<Badge variant="success">
|
||||
{t('env_prod')}
|
||||
</Badge>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import type { CompanySettings } from '@/types'
|
||||
|
||||
interface TaxSettingsFormProps {
|
||||
@@ -35,9 +34,9 @@ export function TaxSettingsForm({ settings }: TaxSettingsFormProps) {
|
||||
{t('entity_form_heading')}
|
||||
</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="secondary" className="text-sm">
|
||||
<span className="text-sm font-medium">
|
||||
{settings.entity_type === 'aktiebolag' ? t('entity_aktiebolag') : t('entity_enskild_firma')}
|
||||
</Badge>
|
||||
</span>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('entity_form_help')}
|
||||
</p>
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function BankSyncSinceLastVisit() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center gap-2 rounded-md border border-success/30 bg-success/5 px-2.5 py-1 text-xs text-success">
|
||||
<div className="inline-flex items-center gap-2 rounded-md border border-border bg-secondary px-2.5 py-1 text-xs text-foreground">
|
||||
<span>
|
||||
{count === 1
|
||||
? t('bank_sync_new_since_last_visit_one')
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
@@ -463,9 +462,9 @@ export default function BulkBookDialog({
|
||||
<FileText className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-sm font-medium truncate">{tpl.name}</span>
|
||||
{tpl.is_system && (
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
<span className="flex-shrink-0 text-[10px] text-muted-foreground">
|
||||
{t('system_badge')}
|
||||
</Badge>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{tpl.description && (
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
@@ -316,9 +315,9 @@ export default function QuickReviewDialog({
|
||||
{isCounterpartyTemplate ? t('label_counterparty_template') : template ? t('label_template') : t('label_category')}
|
||||
</label>
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-sm py-1 px-3">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{template ? template.name_sv : categoryLabel}
|
||||
</Badge>
|
||||
</span>
|
||||
{onChangeTemplate && !isCounterpartyTemplate && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -151,10 +151,10 @@ export default function SkattekontoInboxCard({
|
||||
<DataListMeta>
|
||||
<span className="tabular-nums">{formatDate(row.transaktionsdatum)}</span>
|
||||
<DataListMetaSeparator />
|
||||
<Badge variant="outline" className="h-4 gap-1 px-1.5 py-0 text-[10px]">
|
||||
<span className="inline-flex items-center gap-1 text-muted-foreground">
|
||||
<Landmark className="h-3 w-3" />
|
||||
{t('skv_badge')}
|
||||
</Badge>
|
||||
</span>
|
||||
{matchSuggestion && (
|
||||
<>
|
||||
<DataListMetaSeparator />
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Search, ChevronDown, ChevronUp, AlertTriangle, Info, Building2 } from 'lucide-react'
|
||||
import { Search, ChevronDown, ChevronUp, AlertTriangle, Info, Building2, PenLine } from 'lucide-react'
|
||||
import {
|
||||
getCommonTemplates,
|
||||
getAdvancedTemplates,
|
||||
@@ -60,6 +60,12 @@ function getVatLabelKey(template: BookingTemplate): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse charge (omvänd moms) carries the ochre emphasis via the sanctioned
|
||||
// `warning` variant; every other VAT treatment uses the neutral `secondary`.
|
||||
function getVatBadgeVariant(vatTreatment: string | null | undefined): 'secondary' | 'warning' {
|
||||
return vatTreatment === 'reverse_charge' ? 'warning' : 'secondary'
|
||||
}
|
||||
|
||||
function groupTemplates(templates: BookingTemplate[]): Map<TemplateGroup, BookingTemplate[]> {
|
||||
const grouped = new Map<TemplateGroup, BookingTemplate[]>()
|
||||
for (const t of templates) {
|
||||
@@ -117,20 +123,17 @@ function LibraryTemplateCard({ raw, converted, selected, onClick }: LibraryTempl
|
||||
)}
|
||||
{vatLabelKey && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`text-[10px] px-1.5 py-0 ${
|
||||
converted?.vat_treatment === 'reverse_charge'
|
||||
? 'bg-warning/10 text-warning-foreground'
|
||||
: ''
|
||||
}`}
|
||||
variant={getVatBadgeVariant(converted?.vat_treatment)}
|
||||
className="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
{t(vatLabelKey)}
|
||||
</Badge>
|
||||
)}
|
||||
{!converted && (
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
||||
<span className="inline-flex items-center gap-1 text-[10px] text-muted-foreground">
|
||||
<PenLine className="h-3 w-3" />
|
||||
{t('opens_editor_badge')}
|
||||
</Badge>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,18 +172,14 @@ function TemplateCard({ template, selected, onClick, compact }: TemplateCardProp
|
||||
</span>
|
||||
{vatLabelKey && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`text-[10px] px-1.5 py-0 ${
|
||||
template.vat_treatment === 'reverse_charge'
|
||||
? 'bg-warning/10 text-warning-foreground'
|
||||
: ''
|
||||
}`}
|
||||
variant={getVatBadgeVariant(template.vat_treatment)}
|
||||
className="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
{t(vatLabelKey)}
|
||||
</Badge>
|
||||
)}
|
||||
{template.requires_vat_registration_data && (
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0 border-warning/30 text-warning-foreground gap-0.5">
|
||||
<Badge variant="warning" className="text-[10px] px-1.5 py-0 gap-0.5">
|
||||
<AlertTriangle className="h-2.5 w-2.5" />
|
||||
{t('requires_vat_reg')}
|
||||
</Badge>
|
||||
|
||||
@@ -473,13 +473,12 @@ export default function TransactionInboxCard({
|
||||
{transaction.title_edited_at && (
|
||||
<>
|
||||
<DataListMetaSeparator />
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="h-4 px-1.5 py-0 text-[10px]"
|
||||
<span
|
||||
className="text-muted-foreground"
|
||||
title={originalName ? t('original_name_tooltip', { name: originalName }) : undefined}
|
||||
>
|
||||
{t('edited_badge')}
|
||||
</Badge>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{skvCounterpartDate && (
|
||||
|
||||
Reference in New Issue
Block a user