Refactor to generic ERP base

Remove influencer-specific features (campaigns, TikTok, gifts, shadow ledger,
contracts, briefings) and consolidate into a clean ERP foundation with core
bookkeeping, invoicing, receipts, tax reporting, and calendar functionality.
Reorganize database migrations into a clean numbered sequence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-13 15:39:13 +01:00
co-authored by Claude Opus 4.6
parent a25d10a528
commit 0aecf5b41c
184 changed files with 1418 additions and 24763 deletions
+2 -100
View File
@@ -14,8 +14,6 @@ import {
import { getSchablonavdragSummary } from '@/lib/tax/schablonavdrag'
import FSkattWarningCard from '@/components/dashboard/FSkattWarningCard'
import { UpcomingDeadlinesWidget } from '@/components/calendar/UpcomingDeadlinesWidget'
import { CampaignsWidget } from '@/components/campaigns/CampaignsWidget'
import { TikTokStatsWidget } from '@/components/tiktok'
import NewUserChecklist from '@/components/onboarding/NewUserChecklist'
import {
TrendingUp,
@@ -23,15 +21,13 @@ import {
Receipt,
ArrowLeftRight,
ChevronDown,
Gift,
ArrowRight,
Megaphone,
Camera,
HelpCircle,
Users,
FileText,
} from 'lucide-react'
import type { CompanySettings, EntityType, MileageEntry, SchablonavdragSettings, GiftSummary, Deadline, Campaign, TikTokStatsSummary, ReceiptQueueSummary, OnboardingProgress } from '@/types'
import type { CompanySettings, EntityType, MileageEntry, SchablonavdragSettings, Deadline, ReceiptQueueSummary, OnboardingProgress } from '@/types'
interface DashboardContentProps {
firstName?: string | null
@@ -48,56 +44,15 @@ interface DashboardContentProps {
overdueInvoicesCount: number
bankBalance: number | null
mileageEntries: MileageEntry[]
giftSummary: GiftSummary | null
deadlines: Deadline[]
campaigns: Campaign[]
receiptQueue: ReceiptQueueSummary | null
}
onboardingProgress?: OnboardingProgress
}
export default function DashboardContent({ firstName, settings, summary, onboardingProgress }: DashboardContentProps) {
const [tiktokStats, setTiktokStats] = useState<TikTokStatsSummary | null>(null)
const [isTiktokSyncing, setIsTiktokSyncing] = useState(false)
const [showAllAlerts, setShowAllAlerts] = useState(false)
useEffect(() => {
fetchTikTokStats()
}, [])
const fetchTikTokStats = async () => {
try {
const response = await fetch('/api/tiktok/stats')
const data = await response.json()
if (data.summary) {
setTiktokStats(data.summary)
}
} catch (error) {
// TikTok not connected or error - that's fine
}
}
const handleTikTokSync = async () => {
setIsTiktokSyncing(true)
try {
const accountsResponse = await fetch('/api/tiktok/accounts')
const accountsData = await accountsResponse.json()
const activeAccount = accountsData.accounts?.find((a: { status: string }) => a.status === 'active')
if (activeAccount) {
await fetch('/api/tiktok/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ account_id: activeAccount.id, sync_type: 'full' }),
})
await fetchTikTokStats()
}
} catch (error) {
console.error('TikTok sync failed:', error)
}
setIsTiktokSyncing(false)
}
const entityType = (settings?.entity_type as EntityType) || 'enskild_firma'
const preliminaryTaxMonthly = settings?.preliminary_tax_monthly || 0
const currentMonth = new Date().getMonth() + 1
@@ -111,11 +66,7 @@ export default function DashboardContent({ firstName, settings, summary, onboard
currentMonth
)
const giftTaxableValue = summary.giftSummary?.taxable_value || 0
const giftDeductibleValue = summary.giftSummary?.deductible_value || 0
const netGiftTaxableIncome = giftTaxableValue - giftDeductibleValue
const totalTaxableIncome = summary.ytd.net + netGiftTaxableIncome
const totalTaxableIncome = summary.ytd.net
const taxEstimate =
entityType === 'enskild_firma'
@@ -242,36 +193,6 @@ export default function DashboardContent({ firstName, settings, summary, onboard
)
}
if (summary.giftSummary && summary.giftSummary.total_count > 0) {
alertItems.push(
<Link key="gifts" href="/gifts" className="group">
<Card className="h-full border-l-4 border-l-accent hover:border-primary/30 transition-colors">
<CardContent className="p-5">
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
<div className="p-2 rounded-lg bg-accent/10">
<Gift className="h-4 w-4 text-accent" />
</div>
<div>
<p className="font-medium text-sm">Gåvor & Förmåner</p>
<p className="text-sm text-muted-foreground mt-0.5">
{summary.giftSummary.total_count} produkter
</p>
{summary.giftSummary.taxable_count > 0 && (
<Badge variant="destructive" className="mt-1.5">
{formatCurrency(summary.giftSummary.taxable_value)} skattepliktig
</Badge>
)}
</div>
</div>
<ArrowRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground group-hover:translate-x-0.5 transition-all" />
</div>
</CardContent>
</Card>
</Link>
)
}
const MAX_VISIBLE_ALERTS = 3
const visibleAlerts = showAllAlerts ? alertItems : alertItems.slice(0, MAX_VISIBLE_ALERTS)
const hasMoreAlerts = alertItems.length > MAX_VISIBLE_ALERTS
@@ -280,7 +201,6 @@ export default function DashboardContent({ firstName, settings, summary, onboard
const quickActions = [
{ href: '/invoices/new', icon: Receipt, label: 'Ny faktura', desc: 'Skapa och skicka', accent: true },
{ href: '/receipts/scan', icon: Camera, label: 'Skanna kvitto', desc: 'Fotografera & spara' },
{ href: '/campaigns/new', icon: Megaphone, label: 'Nytt samarbete', desc: 'Spåra innehåll' },
{ href: '/customers', icon: Users, label: 'Ny kund', desc: 'Lägg till kunduppgifter' },
{ href: '/transactions', icon: ArrowLeftRight, label: 'Transaktioner', desc: 'Kategorisera' },
]
@@ -404,24 +324,6 @@ export default function DashboardContent({ firstName, settings, summary, onboard
/>
</section>
{/* TikTok stats widget */}
{tiktokStats && (
<section className="mb-8">
<TikTokStatsWidget
stats={tiktokStats}
onSync={handleTikTokSync}
isSyncing={isTiktokSyncing}
/>
</section>
)}
{/* Active campaigns */}
{summary.campaigns && summary.campaigns.length > 0 && (
<section className="mb-8">
<CampaignsWidget campaigns={summary.campaigns} />
</section>
)}
{/* Alerts section - with urgency indicators and limit */}
{alertItems.length > 0 && (
<section id="alerts-section" className="mb-16">
+13 -30
View File
@@ -16,18 +16,14 @@ import {
Settings,
LogOut,
Calculator,
Gift,
Upload,
Calendar,
Megaphone,
FileUp,
Camera,
Menu,
X,
HelpCircle,
ChevronDown,
ChevronUp,
Wallet,
} from 'lucide-react'
import type { EntityType } from '@/types'
@@ -48,19 +44,14 @@ interface NavItem {
const navItems: NavItem[] = [
{ href: '/', label: 'Översikt', icon: LayoutDashboard, group: 'main' },
{ href: '/calendar', label: 'Kalender', icon: Calendar, group: 'main' },
{ href: '/campaigns', label: 'Samarbeten', icon: Megaphone, group: 'main' },
{ href: '/campaigns/import', label: 'Importera avtal', icon: FileUp, group: 'main' },
{ href: '/analytics', label: 'Analytics', icon: BarChart3, group: 'main' },
{ href: '/shadow-ledger', label: 'Utbetalningar', icon: Wallet, group: 'finans', modes: ['light'] },
{ href: '/invoices', label: 'Fakturor', icon: Receipt, group: 'finans', modes: ['enskild_firma', 'aktiebolag'] },
{ href: '/customers', label: 'Kunder', icon: Users, group: 'finans', modes: ['enskild_firma', 'aktiebolag'] },
{ href: '/invoices', label: 'Fakturor', icon: Receipt, group: 'finans' },
{ href: '/customers', label: 'Kunder', icon: Users, group: 'finans' },
{ href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight, group: 'finans' },
{ href: '/receipts', label: 'Kvitton', icon: Camera, group: 'finans' },
{ href: '/gifts', label: 'Gåvor', icon: Gift, group: 'finans' },
{ href: '/deductions', label: 'Avdrag', icon: Calculator, group: 'finans', modes: ['enskild_firma', 'aktiebolag'] },
{ href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'finans', modes: ['enskild_firma', 'aktiebolag'] },
{ href: '/import', label: 'Importera', icon: Upload, group: 'övrigt', modes: ['enskild_firma', 'aktiebolag'] },
{ href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'övrigt', modes: ['enskild_firma', 'aktiebolag'] },
{ href: '/deductions', label: 'Avdrag', icon: Calculator, group: 'finans' },
{ href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'finans' },
{ href: '/import', label: 'Importera', icon: Upload, group: 'övrigt' },
{ href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'övrigt' },
{ href: '/help', label: 'Hjälp', icon: HelpCircle, group: 'övrigt' },
{ href: '/settings', label: 'Inställningar', icon: Settings, group: 'övrigt' },
]
@@ -101,20 +92,12 @@ export default function DashboardNav({ companyName, entityType }: DashboardNavPr
const finansItems = filteredItems.filter(i => i.group === 'finans')
const övrigtItems = filteredItems.filter(i => i.group === 'övrigt')
// Mobile nav items - light mode shows Utbetalningar instead of Fakturor
const mobileNavItems = entityType === 'light'
? [
{ href: '/', label: 'Översikt', icon: LayoutDashboard },
{ href: '/campaigns', label: 'Samarbeten', icon: Megaphone },
{ href: '/receipts/scan', label: 'Skanna', icon: Camera, isScan: true },
{ href: '/shadow-ledger', label: 'Utbetalningar', icon: Wallet },
]
: [
{ href: '/', label: 'Översikt', icon: LayoutDashboard },
{ href: '/campaigns', label: 'Samarbeten', icon: Megaphone },
{ href: '/receipts/scan', label: 'Skanna', icon: Camera, isScan: true },
{ href: '/invoices', label: 'Fakturor', icon: Receipt },
]
const mobileNavItems = [
{ href: '/', label: 'Översikt', icon: LayoutDashboard },
{ href: '/invoices', label: 'Fakturor', icon: Receipt },
{ href: '/receipts/scan', label: 'Skanna', icon: Camera, isScan: true },
{ href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight },
]
return (
<>
@@ -128,7 +111,7 @@ export default function DashboardNav({ companyName, entityType }: DashboardNavPr
{companyName}
</h1>
<p className="text-xs text-muted-foreground mt-0.5 tracking-wide uppercase">
{entityType === 'light' ? 'Egenanställd' : 'Ekonomi'}
Ekonomi
</p>
</div>
-79
View File
@@ -1,79 +0,0 @@
'use client'
import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { cn, formatCurrency } from '@/lib/utils'
import { Gift, ArrowRight, CheckCircle2 } from 'lucide-react'
interface GiftTaxDebtCardProps {
virtualTaxDebt: number
taxableGiftCount: number
effectiveRate: number
}
export default function GiftTaxDebtCard({
virtualTaxDebt,
taxableGiftCount,
effectiveRate,
}: GiftTaxDebtCardProps) {
const hasDebt = virtualTaxDebt > 0
return (
<Link href="/gifts" className="group block">
<Card className="h-full hover:border-primary/30 transition-colors">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Gift className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-lg">G\u00e5voskatt</CardTitle>
</div>
<ArrowRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground group-hover:translate-x-0.5 transition-all" />
</div>
</CardHeader>
<CardContent>
{hasDebt ? (
<div className="space-y-3">
<div>
<p className="font-display text-2xl font-medium tabular-nums text-amber-600">
{formatCurrency(virtualTaxDebt)}
</p>
<p className="text-xs text-muted-foreground mt-0.5">
Virtuell skatteskuld
</p>
</div>
<div className="flex items-center gap-3 text-sm text-muted-foreground">
<div>
<span className="tabular-nums font-medium text-foreground">
{taxableGiftCount}
</span>{' '}
skattepliktiga g\u00e5vor
</div>
<span className="text-border">{'\u00b7'}</span>
<div>
<span className="tabular-nums font-medium text-foreground">
{(effectiveRate * 100).toFixed(1)}%
</span>{' '}
skattesats
</div>
</div>
</div>
) : (
<div className="flex items-center gap-3 py-2">
<CheckCircle2 className="h-5 w-5 text-emerald-500 flex-shrink-0" />
<div>
<p className="text-sm font-medium text-emerald-700">
Ingen g\u00e5voskatt att betala
</p>
<p className="text-xs text-muted-foreground mt-0.5">
Du har inga skattepliktiga g\u00e5vor registrerade i \u00e5r
</p>
</div>
</div>
)}
</CardContent>
</Card>
</Link>
)
}
@@ -1,134 +0,0 @@
'use client'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import SafeToSpendGauge from '@/components/dashboard/SafeToSpendGauge'
import SGIShieldWidget from '@/components/dashboard/SGIShieldWidget'
import GiftTaxDebtCard from '@/components/dashboard/GiftTaxDebtCard'
import RecentPayoutsCard from '@/components/dashboard/RecentPayoutsCard'
import {
Banknote,
Camera,
Megaphone,
Gift,
} from 'lucide-react'
interface RecentEntry {
id: string
date: string
description: string | null
gross_amount: number
net_amount: number
service_fee: number
pension_deduction: number
social_fees: number
income_tax_withheld: number
platform_fee: number
type: string
provider: string | null
}
interface LightDashboardContentProps {
firstName: string | null
bankBalance: number | null
giftTaxDebt: number
taxableGiftCount: number
effectiveRate: number
daysSinceLastPayout: number | null
recentEntries: RecentEntry[]
hobbyReserve?: number
}
export default function LightDashboardContent({
firstName,
bankBalance,
giftTaxDebt,
taxableGiftCount,
effectiveRate,
daysSinceLastPayout,
recentEntries,
hobbyReserve = 0,
}: LightDashboardContentProps) {
const greeting = (() => {
const hour = new Date().getHours()
if (hour < 12) return 'Godmorgon'
if (hour < 18) return 'God eftermiddag'
return 'God kv\u00e4ll'
})()
const quickActions = [
{
href: '/shadow-ledger/new',
icon: Banknote,
label: 'Logga utbetalning',
},
{
href: '/receipts/scan',
icon: Camera,
label: 'Skanna kvitto',
},
{
href: '/campaigns/new',
icon: Megaphone,
label: 'Nytt samarbete',
},
{
href: '/gifts',
icon: Gift,
label: 'Ny g\u00e5va',
},
]
return (
<div className="stagger-enter">
{/* Greeting header */}
<header className="mb-10">
<h1 className="font-display text-4xl md:text-5xl font-medium tracking-tight">
{greeting}{firstName ? `, ${firstName}` : ''}!
</h1>
</header>
{/* SafeToSpendGauge - full width */}
<section className="mb-6">
<SafeToSpendGauge
bankBalance={bankBalance ?? 0}
giftTaxDebt={giftTaxDebt}
hobbyReserve={hobbyReserve}
/>
</section>
{/* SGIShieldWidget - full width */}
<section className="mb-6">
<SGIShieldWidget daysSinceLastPayout={daysSinceLastPayout} />
</section>
{/* GiftTaxDebtCard + RecentPayoutsCard side by side on md+ */}
<section className="grid md:grid-cols-2 gap-6 mb-10">
<GiftTaxDebtCard
virtualTaxDebt={giftTaxDebt}
taxableGiftCount={taxableGiftCount}
effectiveRate={effectiveRate}
/>
<RecentPayoutsCard entries={recentEntries} />
</section>
{/* Quick actions row */}
<section className="mb-12">
<h2 className="font-display text-xl font-medium mb-4">Snabb\u00e5tg\u00e4rder</h2>
<div className="flex flex-wrap gap-3">
{quickActions.map((action) => {
const Icon = action.icon
return (
<Link key={action.href} href={action.href}>
<Button variant="outline" size="default" className="gap-2">
<Icon className="h-4 w-4" />
{action.label}
</Button>
</Link>
)
})}
</div>
</section>
</div>
)
}
@@ -1,94 +0,0 @@
'use client'
import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { formatCurrency, formatDate } from '@/lib/utils'
import { ArrowRight, Banknote, Plus } from 'lucide-react'
import { Button } from '@/components/ui/button'
interface RecentPayoutsCardProps {
entries: Array<{
id: string
date: string
description: string | null
gross_amount: number
net_amount: number
service_fee: number
pension_deduction: number
social_fees: number
income_tax_withheld: number
platform_fee: number
type: string
provider: string | null
}>
}
export default function RecentPayoutsCard({ entries }: RecentPayoutsCardProps) {
const displayEntries = entries.slice(0, 5)
const isEmpty = displayEntries.length === 0
return (
<Card className="h-full">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Banknote className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-lg">Senaste utbetalningar</CardTitle>
</div>
{!isEmpty && (
<Link
href="/shadow-ledger"
className="text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
>
Visa alla
<ArrowRight className="h-3.5 w-3.5" />
</Link>
)}
</div>
</CardHeader>
<CardContent>
{isEmpty ? (
<div className="text-center py-6">
<p className="text-sm text-muted-foreground mb-4">
Inga utbetalningar registrerade
</p>
<Link href="/shadow-ledger/new">
<Button variant="outline" size="sm">
<Plus className="mr-2 h-4 w-4" />
Logga utbetalning
</Button>
</Link>
</div>
) : (
<div className="space-y-3">
{displayEntries.map((entry) => (
<div
key={entry.id}
className="flex items-center justify-between py-2 border-b border-border/50 last:border-0"
>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium truncate">
{entry.description || entry.provider || 'Utbetalning'}
</p>
<p className="text-xs text-muted-foreground">
{formatDate(entry.date)}
</p>
</div>
<div className="text-right ml-4 flex-shrink-0">
<p className="text-sm tabular-nums font-medium">
{formatCurrency(entry.net_amount)}
</p>
{entry.gross_amount !== entry.net_amount && (
<p className="text-xs text-muted-foreground tabular-nums">
brutto {formatCurrency(entry.gross_amount)}
</p>
)}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
)
}
-107
View File
@@ -1,107 +0,0 @@
'use client'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'
import { Shield, Info } from 'lucide-react'
interface SGIShieldWidgetProps {
daysSinceLastPayout: number | null
}
export default function SGIShieldWidget({
daysSinceLastPayout,
}: SGIShieldWidgetProps) {
const maxDays = 90
const days = daysSinceLastPayout ?? 0
const progress = daysSinceLastPayout !== null ? Math.min((days / maxDays) * 100, 100) : 0
// Determine status
let level: 'green' | 'yellow' | 'red'
let statusMessage: string | null = null
if (daysSinceLastPayout === null) {
level = 'green'
} else if (days < 30) {
level = 'green'
} else if (days < 75) {
level = 'yellow'
statusMessage = 'Varning: Logga uppdrag snart'
} else {
level = 'red'
statusMessage = 'Kritiskt: SGI riskerar nollst\u00e4llning'
}
return (
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Shield
className={cn(
'h-5 w-5',
level === 'green' && 'text-emerald-500',
level === 'yellow' && 'text-amber-500',
level === 'red' && 'text-red-500'
)}
/>
<CardTitle className="text-lg">SGI-skydd</CardTitle>
</div>
{daysSinceLastPayout !== null && (
<span className="text-sm text-muted-foreground tabular-nums">
{days} dagar sedan senast
</span>
)}
</div>
</CardHeader>
<CardContent>
{daysSinceLastPayout === null ? (
<div className="flex items-start gap-3 py-2">
<Info className="h-4 w-4 text-muted-foreground mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm text-muted-foreground">
Inga utbetalningar registrerade \u00e4nnu
</p>
<p className="text-xs text-muted-foreground mt-1">
SGI (sjukpenninggrundande inkomst) baseras p\u00e5 dina l\u00f6neutbetalningar.
Logga din f\u00f6rsta utbetalning f\u00f6r att b\u00f6rja sp\u00e5ra.
</p>
</div>
</div>
) : (
<>
{/* Progress bar */}
<div className="w-full h-3 rounded-full overflow-hidden bg-muted mb-2">
<div
className={cn(
'h-full rounded-full transition-all duration-500',
level === 'green' && 'bg-emerald-500',
level === 'yellow' && 'bg-amber-500',
level === 'red' && 'bg-red-500'
)}
style={{ width: `${progress}%` }}
/>
</div>
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span className="tabular-nums">{days}/{maxDays}</span>
<span>N\u00e4sta varning: dag 30 {'\u00b7'} Kritisk: dag 75</span>
</div>
{/* Status message */}
{statusMessage && (
<div className="mt-3">
<Badge
variant={level === 'red' ? 'destructive' : 'warning'}
className="text-xs"
>
{statusMessage}
</Badge>
</div>
)}
</>
)}
</CardContent>
</Card>
)
}
-131
View File
@@ -1,131 +0,0 @@
'use client'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { cn, formatCurrency } from '@/lib/utils'
import { Wallet } from 'lucide-react'
interface SafeToSpendGaugeProps {
bankBalance: number
giftTaxDebt: number
hobbyReserve: number
}
export default function SafeToSpendGauge({
bankBalance,
giftTaxDebt,
hobbyReserve,
}: SafeToSpendGaugeProps) {
const safeToSpend = Math.max(0, bankBalance - giftTaxDebt - hobbyReserve)
const total = bankBalance || 1 // Avoid division by zero
const safePercent = bankBalance > 0 ? (safeToSpend / total) * 100 : 0
const giftPercent = bankBalance > 0 ? (giftTaxDebt / total) * 100 : 0
const hobbyPercent = bankBalance > 0 ? (hobbyReserve / total) * 100 : 0
// Determine color level
let level: 'green' | 'yellow' | 'red'
if (safePercent > 70) {
level = 'green'
} else if (safePercent > 30) {
level = 'yellow'
} else {
level = 'red'
}
const noBankConnected = !bankBalance || bankBalance === 0
return (
<Card className={cn(noBankConnected && 'opacity-60')}>
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Wallet className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-lg">Att spendera</CardTitle>
</div>
</CardHeader>
<CardContent>
{noBankConnected ? (
<div className="text-center py-4">
<p className="text-2xl font-display font-medium text-muted-foreground">
Ingen bank ansluten
</p>
<p className="text-sm text-muted-foreground mt-1">
Koppla din bank f\u00f6r att se ditt disponibla belopp
</p>
</div>
) : (
<>
<div className="text-center mb-6">
<p
className={cn(
'font-display text-4xl font-medium tabular-nums',
level === 'green' && 'text-emerald-600',
level === 'yellow' && 'text-amber-600',
level === 'red' && 'text-red-600'
)}
>
{formatCurrency(safeToSpend)}
</p>
<p className="text-sm text-muted-foreground mt-1">
av {formatCurrency(bankBalance)} p\u00e5 kontot
</p>
</div>
{/* Stacked bar */}
<div className="w-full h-4 rounded-full overflow-hidden flex bg-muted">
{safePercent > 0 && (
<div
className={cn(
'h-full transition-all duration-500',
level === 'green' && 'bg-emerald-500',
level === 'yellow' && 'bg-amber-500',
level === 'red' && 'bg-red-500'
)}
style={{ width: `${safePercent}%` }}
/>
)}
{giftPercent > 0 && (
<div
className="h-full bg-amber-400 transition-all duration-500"
style={{ width: `${giftPercent}%` }}
/>
)}
{hobbyPercent > 0 && (
<div
className="h-full bg-orange-400 transition-all duration-500"
style={{ width: `${hobbyPercent}%` }}
/>
)}
</div>
{/* Legend */}
<div className="flex flex-wrap gap-x-5 gap-y-1 mt-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1.5">
<span
className={cn(
'inline-block h-2.5 w-2.5 rounded-full',
level === 'green' && 'bg-emerald-500',
level === 'yellow' && 'bg-amber-500',
level === 'red' && 'bg-red-500'
)}
/>
Disponibelt
</div>
{giftTaxDebt > 0 && (
<div className="flex items-center gap-1.5">
<span className="inline-block h-2.5 w-2.5 rounded-full bg-amber-400" />
G\u00e5voskatt ({formatCurrency(giftTaxDebt)})
</div>
)}
{hobbyReserve > 0 && (
<div className="flex items-center gap-1.5">
<span className="inline-block h-2.5 w-2.5 rounded-full bg-orange-400" />
Hobby ({formatCurrency(hobbyReserve)})
</div>
)}
</div>
</>
)}
</CardContent>
</Card>
)
}