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:
Jakob Wennberg
2026-07-01 14:29:23 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent d63d2aecf0
commit 2da9c71eb3
57 changed files with 613 additions and 315 deletions
+3 -15
View File
@@ -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'
}
}
+1 -1
View File
@@ -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', {
+1 -2
View File
@@ -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')}
+3 -3
View File
@@ -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>
)
+2 -3
View File
@@ -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>