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:
@@ -276,7 +276,7 @@ function LoginPageContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-4">
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<p className="text-sm text-muted-foreground text-center leading-relaxed">
|
||||
{showResetPassword ? tAuth('email_sent_hint_reset') : tAuth('email_sent_hint_login')}
|
||||
</p>
|
||||
@@ -327,7 +327,7 @@ function LoginPageContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<form onSubmit={handleResetPassword} className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">{tAuth('email_label')}</Label>
|
||||
@@ -382,7 +382,7 @@ function LoginPageContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
{callbackError === 'auth_error' && (
|
||||
<div className="mb-5 rounded-lg border border-destructive/30 bg-destructive/5 p-4">
|
||||
<p className="text-sm font-medium text-destructive">
|
||||
|
||||
@@ -188,7 +188,7 @@ function MfaEnrollContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border bg-muted/50 p-4">
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
@@ -242,7 +242,7 @@ function MfaEnrollContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6 space-y-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6 space-y-6">
|
||||
{/* QR Code */}
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
|
||||
@@ -171,7 +171,7 @@ function MfaVerifyContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<form onSubmit={handleVerify} className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="code">{t('verify_code_label')}</Label>
|
||||
|
||||
@@ -140,7 +140,7 @@ function RegisterPageContent() {
|
||||
})
|
||||
|
||||
if (error) {
|
||||
console.error('[register] BankID verifyOtp failed', error)
|
||||
console.error('[register] BankID verifyOtp failed', error.message)
|
||||
toast({
|
||||
title: t('register_failed_complete'),
|
||||
description: getErrorMessage(error, { context: 'auth', locale: errorLocale }),
|
||||
@@ -152,7 +152,7 @@ function RegisterPageContent() {
|
||||
router.push('/select-company')
|
||||
router.refresh()
|
||||
} catch (error) {
|
||||
console.error('[register] BankID signup error', error)
|
||||
console.error('[register] BankID signup error', error instanceof Error ? error.message : String(error))
|
||||
toast({
|
||||
title: t('register_failed_title'),
|
||||
description: getErrorMessage(error, { context: 'auth', locale: errorLocale }),
|
||||
@@ -201,14 +201,6 @@ function RegisterPageContent() {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[register] attempting signUp', {
|
||||
email: emailValue,
|
||||
hasPassword: !!passwordValue,
|
||||
passwordLength: passwordValue.length,
|
||||
redirectTo: `${window.location.origin}/auth/callback`,
|
||||
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
})
|
||||
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: emailValue,
|
||||
password: passwordValue,
|
||||
@@ -218,15 +210,7 @@ function RegisterPageContent() {
|
||||
})
|
||||
|
||||
if (error) {
|
||||
console.error('[register] signUp error', {
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
status: error.status,
|
||||
name: error.name,
|
||||
stack: error.stack,
|
||||
cause: error.cause,
|
||||
fullError: JSON.stringify(error, Object.getOwnPropertyNames(error)),
|
||||
})
|
||||
console.error('[register] signUp error', error.message)
|
||||
toast({
|
||||
title: t('register_failed_title'),
|
||||
description: getErrorMessage(error, { context: 'auth', locale: errorLocale }),
|
||||
@@ -235,16 +219,6 @@ function RegisterPageContent() {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[register] signUp response', {
|
||||
userId: data.user?.id,
|
||||
email: data.user?.email,
|
||||
isAnonymous: data.user?.is_anonymous,
|
||||
identities: data.user?.identities?.length,
|
||||
hasSession: !!data.session,
|
||||
confirmationSentAt: data.user?.confirmation_sent_at,
|
||||
provider: data.user?.app_metadata?.provider,
|
||||
})
|
||||
|
||||
// If auto-confirmed (local dev), process invite immediately and redirect
|
||||
if (data.session) {
|
||||
const cookieMatch = document.cookie.match(/gnubok-invite-token=([^;]+)/)
|
||||
@@ -260,19 +234,11 @@ function RegisterPageContent() {
|
||||
|
||||
if (res.ok) {
|
||||
document.cookie = 'gnubok-invite-token=; path=/; max-age=0'
|
||||
console.log('[register] invite accepted after auto-confirm — redirecting')
|
||||
window.location.href = '/'
|
||||
return
|
||||
}
|
||||
|
||||
// Log the error response so we can diagnose invite failures
|
||||
const errBody = await res.json().catch(() => ({}))
|
||||
console.error('[register] invite acceptance returned non-ok', {
|
||||
status: res.status,
|
||||
error: errBody.error,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('[register] invite acceptance failed:', err)
|
||||
console.error('[register] invite acceptance failed:', err instanceof Error ? err.message : String(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,13 +260,7 @@ function RegisterPageContent() {
|
||||
setEmail(emailValue)
|
||||
setIsRegistered(true)
|
||||
} catch (error) {
|
||||
console.error('[register] unexpected exception', {
|
||||
error,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
type: typeof error,
|
||||
constructor: error?.constructor?.name,
|
||||
})
|
||||
console.error('[register] unexpected exception', error instanceof Error ? error.message : String(error))
|
||||
toast({
|
||||
title: t('register_failed_title'),
|
||||
description: getErrorMessage(error, { context: 'auth', locale: errorLocale }),
|
||||
@@ -329,7 +289,7 @@ function RegisterPageContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-4">
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<p className="text-sm text-muted-foreground text-center leading-relaxed">
|
||||
{t('duplicate_hint')}
|
||||
</p>
|
||||
@@ -377,7 +337,7 @@ function RegisterPageContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-4">
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<p className="text-sm text-muted-foreground text-center leading-relaxed">
|
||||
{t('confirm_email_hint')}
|
||||
</p>
|
||||
@@ -416,7 +376,7 @@ function RegisterPageContent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
{bankIdEnabled && !bankIdUser && (
|
||||
<>
|
||||
<div className="mb-5">
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ResetPasswordPage() {
|
||||
if (password !== confirmPassword) {
|
||||
toast({
|
||||
title: t('mismatch_title'),
|
||||
description: t('weak_description'),
|
||||
description: t('mismatch_description'),
|
||||
variant: 'destructive',
|
||||
})
|
||||
setIsLoading(false)
|
||||
@@ -102,7 +102,7 @@ export default function ResetPasswordPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-6" style={{ boxShadow: 'var(--shadow-md)' }}>
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<form onSubmit={handleResetPassword} className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">{t('new_password_label')}</Label>
|
||||
|
||||
@@ -189,7 +189,7 @@ export default function ArticleDetailPage({
|
||||
<Icon className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{article.name}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{article.name}</h1>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge variant="secondary">{t(ARTICLE_TYPE_KEY[article.type])}</Badge>
|
||||
{article.article_number && (
|
||||
|
||||
@@ -255,7 +255,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight font-mono">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight font-mono">
|
||||
{formatVoucher(entry)}
|
||||
</h1>
|
||||
<JournalEntryStatusBadge entry={entry} />
|
||||
@@ -366,7 +366,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
{entry.committed_at && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('field_posted_at')}</span>
|
||||
<span>{new Date(entry.committed_at).toLocaleDateString('sv-SE')}</span>
|
||||
<span>{formatDate(entry.committed_at)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
|
||||
@@ -808,7 +808,7 @@ function ManualEntryEditor({
|
||||
<div className="rounded-md border border-border p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium">{template.name}</p>
|
||||
<Button variant="ghost" size="sm" onClick={onRemove} className="h-7 px-2">
|
||||
<Button variant="ghost" size="sm" onClick={onRemove} className="h-7 px-2" aria-label="Ta bort">
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -203,7 +203,7 @@ export default function CustomerDetailPage({
|
||||
<Icon className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{customer.name}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{customer.name}</h1>
|
||||
<Badge variant="secondary">{t(CUSTOMER_TYPE_KEY[customer.customer_type])}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DeadlineList } from '@/components/deadlines/DeadlineList'
|
||||
import { PageHeader } from '@/components/ui/page-header'
|
||||
import { AlertTriangle, ArrowRight } from 'lucide-react'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import type { Deadline } from '@/types'
|
||||
|
||||
const supabase = createClient()
|
||||
@@ -224,7 +225,7 @@ export default function DeadlinesPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<PageHeader title={t('title')} />
|
||||
|
||||
{/* Overdue invoices alert */}
|
||||
@@ -235,8 +236,8 @@ export default function DeadlinesPage() {
|
||||
<AlertTriangle className="h-4 w-4 text-destructive flex-shrink-0" />
|
||||
<p className="text-sm">
|
||||
<span className="font-medium">{t('overdue_invoices', { count: overdueInvoices.count })}</span>
|
||||
<span className="text-muted-foreground ml-1.5">
|
||||
{overdueInvoices.total.toLocaleString('sv-SE')} kr
|
||||
<span className="text-muted-foreground ml-1.5 tabular-nums">
|
||||
{formatCurrency(overdueInvoices.total)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function DashboardError({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-4">
|
||||
<h2 className="text-xl font-semibold">Nagot gick fel</h2>
|
||||
<h2 className="text-xl font-semibold">Något gick fel</h2>
|
||||
<p className="text-muted-foreground text-sm max-w-md text-center">
|
||||
Ett oväntat fel uppstod. Försök igen eller{' '}
|
||||
<SupportLink variant="inline" subject="Oväntat fel">kontakta support</SupportLink>{' '}
|
||||
|
||||
@@ -210,12 +210,12 @@ const glossaryTerms: GlossaryTerm[] = [
|
||||
]
|
||||
|
||||
const categoryConfig = {
|
||||
skatt: { labelKey: 'category_skatt', icon: Calculator, color: 'bg-orange-500/10 text-orange-600' },
|
||||
moms: { labelKey: 'category_moms', icon: Receipt, color: 'bg-blue-500/10 text-blue-600' },
|
||||
faktura: { labelKey: 'category_faktura', icon: FileText, color: 'bg-success/10 text-success' },
|
||||
bokföring: { labelKey: 'category_bokforing', icon: BookOpen, color: 'bg-purple-500/10 text-purple-600' },
|
||||
bank: { labelKey: 'category_bank', icon: Building2, color: 'bg-pink-500/10 text-pink-600' },
|
||||
företag: { labelKey: 'category_foretag', icon: Building2, color: 'bg-cyan-500/10 text-cyan-600' },
|
||||
skatt: { labelKey: 'category_skatt', icon: Calculator, color: 'bg-secondary text-muted-foreground' },
|
||||
moms: { labelKey: 'category_moms', icon: Receipt, color: 'bg-secondary text-muted-foreground' },
|
||||
faktura: { labelKey: 'category_faktura', icon: FileText, color: 'bg-secondary text-muted-foreground' },
|
||||
bokföring: { labelKey: 'category_bokforing', icon: BookOpen, color: 'bg-secondary text-muted-foreground' },
|
||||
bank: { labelKey: 'category_bank', icon: Building2, color: 'bg-secondary text-muted-foreground' },
|
||||
företag: { labelKey: 'category_foretag', icon: Building2, color: 'bg-secondary text-muted-foreground' },
|
||||
}
|
||||
|
||||
function TermCard({ term, isExpanded, onToggle }: { term: GlossaryTerm; isExpanded: boolean; onToggle: () => void }) {
|
||||
@@ -224,7 +224,7 @@ function TermCard({ term, isExpanded, onToggle }: { term: GlossaryTerm; isExpand
|
||||
const CategoryIcon = config.icon
|
||||
|
||||
return (
|
||||
<Card className={cn('transition-all', isExpanded && 'ring-2 ring-primary/20')}>
|
||||
<Card className={cn('transition-colors', isExpanded && 'ring-2 ring-primary/20')}>
|
||||
<CardContent className="pt-4">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
|
||||
@@ -2164,7 +2164,7 @@ export default function ImportPage() {
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
{view === 'export' ? t('export_title') : t('title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
@@ -2198,10 +2198,10 @@ export default function ImportPage() {
|
||||
tabIndex={isSandbox ? -1 : 0}
|
||||
aria-disabled={isSandbox}
|
||||
className={cn(
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-all',
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-colors',
|
||||
isSandbox
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: 'cursor-pointer hover:border-foreground/15 hover:shadow-[var(--shadow-sm)] active:scale-[0.998]'
|
||||
: 'cursor-pointer hover:border-foreground/15'
|
||||
)}
|
||||
onClick={() => { if (!isSandbox) setMode('psd2') }}
|
||||
onKeyDown={(e) => { if (!isSandbox && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); setMode('psd2') } }}
|
||||
@@ -2231,10 +2231,10 @@ export default function ImportPage() {
|
||||
tabIndex={isSandbox ? -1 : 0}
|
||||
aria-disabled={isSandbox}
|
||||
className={cn(
|
||||
'group rounded-lg border bg-card p-5 transition-all',
|
||||
'group rounded-lg border bg-card p-5 transition-colors',
|
||||
isSandbox
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: 'cursor-pointer hover:border-foreground/15 hover:shadow-[var(--shadow-sm)] active:scale-[0.998]'
|
||||
: 'cursor-pointer hover:border-foreground/15'
|
||||
)}
|
||||
onClick={() => { if (!isSandbox) setMode('migration') }}
|
||||
onKeyDown={(e) => { if (!isSandbox && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); setMode('migration') } }}
|
||||
@@ -2277,8 +2277,8 @@ export default function ImportPage() {
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-all',
|
||||
'cursor-pointer hover:border-foreground/15 hover:shadow-[var(--shadow-sm)] active:scale-[0.998]'
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-colors',
|
||||
'cursor-pointer hover:border-foreground/15'
|
||||
)}
|
||||
onClick={() => setMode('bank')}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setMode('bank') } }}
|
||||
@@ -2307,8 +2307,8 @@ export default function ImportPage() {
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-all',
|
||||
'cursor-pointer hover:border-foreground/15 hover:shadow-[var(--shadow-sm)] active:scale-[0.998]'
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-colors',
|
||||
'cursor-pointer hover:border-foreground/15'
|
||||
)}
|
||||
onClick={() => setMode('csv_data')}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setMode('csv_data') } }}
|
||||
@@ -2344,8 +2344,8 @@ export default function ImportPage() {
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-all',
|
||||
'cursor-pointer hover:border-foreground/15 hover:shadow-[var(--shadow-sm)] active:scale-[0.998]'
|
||||
'group flex items-start gap-4 rounded-lg border bg-card p-5 transition-colors',
|
||||
'cursor-pointer hover:border-foreground/15'
|
||||
)}
|
||||
onClick={() => setMode('sie')}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setMode('sie') } }}
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id:
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{t('title')}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{t('title')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t('subtitle', { number: invoice.invoice_number ?? '' })}
|
||||
</p>
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function KpiPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{t('title')}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{t('title')}</h1>
|
||||
<div className="flex gap-2">
|
||||
<KPISettingsDialog
|
||||
preferences={preferences}
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function DashboardLoading() {
|
||||
{/* Metrics row */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="rounded-xl border border-border/60 bg-card p-4 space-y-2">
|
||||
<div key={i} className="rounded-lg border border-border bg-card p-4 space-y-2">
|
||||
<Skeleton className="h-3.5 w-20" />
|
||||
<Skeleton className="h-7 w-28" />
|
||||
<Skeleton className="h-3 w-16" />
|
||||
@@ -17,7 +17,7 @@ export default function DashboardLoading() {
|
||||
{/* Income / Expenses */}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{[1, 2].map((i) => (
|
||||
<div key={i} className="rounded-xl border border-border/60 bg-card p-6 space-y-3">
|
||||
<div key={i} className="rounded-lg border border-border bg-card p-6 space-y-3">
|
||||
<Skeleton className="h-4 w-20" />
|
||||
<Skeleton className="h-8 w-32" />
|
||||
<Skeleton className="h-3 w-24" />
|
||||
@@ -27,12 +27,12 @@ export default function DashboardLoading() {
|
||||
|
||||
{/* Alerts + deadlines */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="rounded-xl border border-border/60 bg-card p-4 space-y-3">
|
||||
<div className="rounded-lg border border-border bg-card p-4 space-y-3">
|
||||
<Skeleton className="h-5 w-24" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
</div>
|
||||
<div className="rounded-xl border border-border/60 bg-card p-4 space-y-3">
|
||||
<div className="rounded-lg border border-border bg-card p-4 space-y-3">
|
||||
<Skeleton className="h-5 w-32" />
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="flex items-center justify-between">
|
||||
|
||||
@@ -144,7 +144,7 @@ export default function EmployeeDetailPage({ params }: { params: Promise<{ id: s
|
||||
<Link href="/salary/employees" aria-label="Tillbaka till anställda"><ArrowLeft className="h-4 w-4" /></Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
{employee.first_name} {employee.last_name}
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function NewEmployeePage() {
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link href="/salary/employees" aria-label="Tillbaka till anställda"><ArrowLeft className="h-4 w-4" /></Link>
|
||||
</Button>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">Ny anställd</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">Ny anställd</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function EmployeesPage() {
|
||||
<Link href="/salary" aria-label={t('back_to_payroll')}><ArrowLeft className="h-4 w-4" /></Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{t('title')}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{t('title')}</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">{t('registered_count', { count: employees.length })}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function SalaryRunEmployeeDetailPage({
|
||||
</Link>
|
||||
<div className="flex flex-wrap items-baseline justify-between gap-2">
|
||||
<div>
|
||||
<h1 className="font-serif text-2xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl tracking-tight">
|
||||
{employee.first_name} {employee.last_name}
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground tabular-nums">
|
||||
@@ -312,7 +312,7 @@ function SummaryCard({ label, value, accent, overridden }: { label: string; valu
|
||||
|
||||
function AbsenceCount({ label, days }: { label: string; days: number }) {
|
||||
return (
|
||||
<div className="rounded-md border bg-muted/30 px-2.5 py-1.5">
|
||||
<div className="rounded-md border bg-muted/30 px-3 py-2">
|
||||
<div className="text-[11px] text-muted-foreground">{label}</div>
|
||||
<div className="text-sm font-medium tabular-nums">{days} dagar</div>
|
||||
</div>
|
||||
|
||||
@@ -387,7 +387,7 @@ export default function SalaryRunDetailPage({ params }: { params: Promise<{ id:
|
||||
<Link href="/salary" aria-label="Tillbaka till löner"><ArrowLeft className="h-4 w-4" /></Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">
|
||||
Lönekörning {periodLabel}
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
@@ -426,7 +426,7 @@ export default function SalaryRunDetailPage({ params }: { params: Promise<{ id:
|
||||
<Card key={label}>
|
||||
<CardContent className="p-4">
|
||||
<p className="text-xs text-muted-foreground mb-2">{label}</p>
|
||||
<p className={`font-display text-xl font-medium tabular-nums leading-tight ${accent ? 'text-success' : ''}`}>
|
||||
<p className={`font-display text-xl tabular-nums leading-tight ${accent ? 'text-success' : ''}`}>
|
||||
{formatCurrency(value)}
|
||||
</p>
|
||||
</CardContent>
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function NewSalaryRunPage() {
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link href="/salary" aria-label="Tillbaka till löner"><ArrowLeft className="h-4 w-4" /></Link>
|
||||
</Button>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">Ny lönekörning</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">Ny lönekörning</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
|
||||
@@ -581,7 +581,7 @@ export default function SupplierInvoiceDetailPage() {
|
||||
{invoice.delivery_date && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('delivery_date_label')}</span>
|
||||
<span>{invoice.delivery_date}</span>
|
||||
<span className="tabular-nums">{formatDate(invoice.delivery_date)}</span>
|
||||
</div>
|
||||
)}
|
||||
{invoice.payment_reference && (
|
||||
|
||||
@@ -1439,7 +1439,7 @@ export default function NewSupplierInvoicePage() {
|
||||
{/* Valuta & moms — kept inline with the line items because they
|
||||
drive how each row is interpreted. Hidden defaults (SEK +
|
||||
normal moms) collapse to nothing so most users don't see this. */}
|
||||
<div className="mb-5 pb-5 border-b grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div className="mb-4 pb-4 border-b grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">{t('currency_label')}</Label>
|
||||
<Controller
|
||||
|
||||
@@ -152,7 +152,7 @@ export default function SupplierDetailPage() {
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{supplier.name}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{supplier.name}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{supplierTypeLabels[supplier.supplier_type]}
|
||||
{supplier.org_number && t('org_number_inline', { number: supplier.org_number })}
|
||||
@@ -189,7 +189,7 @@ export default function SupplierDetailPage() {
|
||||
<CardTitle className="text-sm text-muted-foreground">{t('outstanding')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums">{formatAmount(supplier.stats?.total_outstanding || 0)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums">{formatAmount(supplier.stats?.total_outstanding || 0)} kr</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
@@ -197,7 +197,7 @@ export default function SupplierDetailPage() {
|
||||
<CardTitle className="text-sm text-muted-foreground">{t('total_paid')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums">{formatAmount(supplier.stats?.total_paid || 0)} kr</p>
|
||||
<p className="font-display text-2xl tabular-nums">{formatAmount(supplier.stats?.total_paid || 0)} kr</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
@@ -205,7 +205,7 @@ export default function SupplierDetailPage() {
|
||||
<CardTitle className="text-sm text-muted-foreground">{t('invoice_count')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-display text-2xl font-medium tabular-nums">{supplier.stats?.invoice_count || 0}</p>
|
||||
<p className="font-display text-2xl tabular-nums">{supplier.stats?.invoice_count || 0}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { EmptyState } from '@/components/ui/empty-state'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { Plus, Search, Building2, Lock } from 'lucide-react'
|
||||
@@ -114,7 +115,7 @@ export default function SuppliersPage() {
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight">{t('title')}</h1>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{t('title')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
@@ -168,13 +169,13 @@ export default function SuppliersPage() {
|
||||
{isLoading ? (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Card key={i} className="animate-pulse">
|
||||
<Card key={i}>
|
||||
<CardContent className="p-6 space-y-3">
|
||||
<div className="h-5 bg-muted rounded w-1/2" />
|
||||
<div className="h-3 bg-muted rounded w-2/3" />
|
||||
<Skeleton className="h-5 w-1/2" />
|
||||
<Skeleton className="h-3 w-2/3" />
|
||||
<div className="h-px bg-muted" />
|
||||
<div className="h-3 bg-muted rounded w-1/2" />
|
||||
<div className="h-3 bg-muted rounded w-1/3" />
|
||||
<Skeleton className="h-3 w-1/2" />
|
||||
<Skeleton className="h-3 w-1/3" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
@@ -206,7 +207,7 @@ export default function SuppliersPage() {
|
||||
const location = formatLocation(supplier)
|
||||
return (
|
||||
<Link key={supplier.id} href={`/suppliers/${supplier.id}`} className="group">
|
||||
<Card className="h-full cursor-pointer transition-all duration-150 hover:border-foreground/20 hover:shadow-sm motion-safe:active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
|
||||
<Card className="h-full cursor-pointer transition-colors duration-150 hover:border-foreground/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
|
||||
<CardContent className="p-6 flex flex-col h-full">
|
||||
<div className="space-y-1 mb-4">
|
||||
<h3 className="text-[15px] font-semibold tracking-tight leading-tight truncate group-hover:text-primary transition-colors">
|
||||
@@ -217,7 +218,7 @@ export default function SuppliersPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<dl className="mt-auto space-y-1.5 text-sm border-t pt-3">
|
||||
<dl className="mt-auto space-y-2 text-sm border-t pt-3">
|
||||
{supplier.org_number && (
|
||||
<div className="flex items-baseline justify-between gap-3">
|
||||
<dt className="text-xs text-muted-foreground shrink-0">{t('label_org_number')}</dt>
|
||||
|
||||
@@ -2061,7 +2061,7 @@ export default function TransactionsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
{/* Status bar */}
|
||||
<TransactionStatusBar
|
||||
uncategorizedCount={totalUncategorizedCount ?? uncategorizedTransactions.length}
|
||||
@@ -2091,7 +2091,7 @@ export default function TransactionsPage() {
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-9 gap-1.5 px-3 text-sm">
|
||||
<Button variant="outline" size="sm" className="h-9 gap-2 px-3 text-sm">
|
||||
{mode === 'inbox'
|
||||
? `Att bokföra${(totalUncategorizedCount ?? uncategorizedTransactions.length) > 0 ? ` (${totalUncategorizedCount ?? uncategorizedTransactions.length})` : ''}`
|
||||
: 'Alla transaktioner'}
|
||||
@@ -2138,7 +2138,7 @@ export default function TransactionsPage() {
|
||||
</span>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-7 gap-1.5 px-2 text-xs">
|
||||
<Button variant="outline" size="sm" className="h-7 gap-2 px-2 text-xs">
|
||||
{sourceFilter === 'all'
|
||||
? t('source_all', { count: uncategorizedTransactions.length + skvUnmatched.length })
|
||||
: sourceFilter === 'bank'
|
||||
|
||||
@@ -46,7 +46,7 @@ export default async function OnboardingLayout({
|
||||
href="/settings/account"
|
||||
aria-label="Kontoinställningar"
|
||||
title="Kontoinställningar"
|
||||
className="fixed bottom-5 right-5 z-50 flex h-10 w-10 items-center justify-center rounded-full border border-border bg-background/80 text-muted-foreground shadow-sm backdrop-blur transition-colors hover:border-foreground/40 hover:text-foreground"
|
||||
className="fixed bottom-6 right-6 z-50 flex h-10 w-10 items-center justify-center rounded-full border border-border bg-background/80 text-muted-foreground shadow-sm backdrop-blur transition-colors hover:border-foreground/40 hover:text-foreground"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Link>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getBranding } from '@/lib/branding/service'
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return {
|
||||
title: `Personuppgiftsbitradesavtal - ${getBranding().appName}`,
|
||||
title: `Personuppgiftsbiträdesavtal - ${getBranding().appName}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function DPAPage() {
|
||||
<div className="max-w-3xl mx-auto space-y-8">
|
||||
<div className="text-center space-y-2">
|
||||
<h1 className="font-display text-3xl md:text-4xl tracking-tight text-foreground">
|
||||
Personuppgiftsbitradesavtal (DPA)
|
||||
Personuppgiftsbiträdesavtal (DPA)
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enligt GDPR Art. 28 · Senast uppdaterad: 2026-06-03
|
||||
@@ -112,7 +112,7 @@ export default function DPAPage() {
|
||||
åtkomst</li>
|
||||
<li><strong>Integritetskontroll:</strong> SHA-256 checksummor för alla dokument, med
|
||||
regelbunden verifiering</li>
|
||||
<li><strong>Revisionslogg:</strong> Alla ändringshandelser loggas automatiskt av databasen
|
||||
<li><strong>Revisionslogg:</strong> Alla ändringshändelser loggas automatiskt av databasen
|
||||
(ej redigerbara)</li>
|
||||
<li><strong>Oföränderlig bokföring:</strong> Bokförda verifikationer kan inte ändras eller
|
||||
raderas (databasutlösare)</li>
|
||||
@@ -243,7 +243,7 @@ export default function DPAPage() {
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Detta personuppgiftsbitradesavtal träder i kraft när du skapar ett konto på
|
||||
Detta personuppgiftsbiträdesavtal träder i kraft när du skapar ett konto på
|
||||
{' '}{appName} och gäller så länge du använder tjänsten. För frågor, kontakta oss
|
||||
på {privacyEmail}.
|
||||
</p>
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function InvoiceActionPage({ params }: { params: Promise<{ token:
|
||||
<div className="max-w-lg mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
<h1 className="text-2xl font-bold text-foreground mb-2">
|
||||
Betalningspåminnelse
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
@@ -268,7 +268,7 @@ export default function InvoiceActionPage({ params }: { params: Promise<{ token:
|
||||
onClick={() => handleAction('disputed')}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<MessageSquare className="h-5 w-5 mr-3 text-orange-600" />
|
||||
<MessageSquare className="h-5 w-5 mr-3 text-muted-foreground" />
|
||||
<div className="text-left">
|
||||
<p className="font-medium">Kontakta avsändaren</p>
|
||||
<p className="text-sm text-muted-foreground font-normal">
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function PrivacyPolicyPage() {
|
||||
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white py-12 px-4">
|
||||
<div className="max-w-3xl mx-auto space-y-6">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">
|
||||
Integritetspolicy
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
@@ -69,7 +69,7 @@ export default function PrivacyPolicyPage() {
|
||||
</li>
|
||||
<li>
|
||||
<strong>Berättigat intresse (Art. 6.1f):</strong> Produktförbättringar, säkerhet och
|
||||
bedrägeriforbud.
|
||||
bedrägeribekämpning.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Samtycke (Art. 6.1a):</strong> För AI-baserade funktioner som skickar data
|
||||
|
||||
@@ -181,7 +181,6 @@ function NewCompanyContent() {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(LOG, 'created company', result.companyId)
|
||||
toast({
|
||||
title: t('toast_company_created'),
|
||||
description: t('toast_switched_to_new'),
|
||||
@@ -263,7 +262,7 @@ function NewCompanyContent() {
|
||||
</div>
|
||||
|
||||
<div key={`title-${currentStep}`} className="animate-fade-in">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight leading-[1.1]">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight leading-[1.1]">
|
||||
{stepInfo.title}
|
||||
</h1>
|
||||
<p className="text-white/40 mt-1.5 text-sm max-w-sm leading-relaxed">
|
||||
|
||||
@@ -172,7 +172,7 @@ export default function InvitePage() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="animate-fade-in">
|
||||
<h1 className="font-display text-2xl md:text-3xl font-medium tracking-tight leading-[1.1]">
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight leading-[1.1]">
|
||||
{error ? t('header_invalid') : t('header_invited')}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user