feat: remove F-skatt warning card from dashboard
Remove FSkattWarningCard component, related tax warning functions, types, and thresholds. Feature was not providing enough value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f3ec634a46
commit
02ebe50f52
@@ -5,12 +5,6 @@ import Link from 'next/link'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn, formatCurrency } from '@/lib/utils'
|
||||
import {
|
||||
calculateEFTax,
|
||||
calculateABTax,
|
||||
getEnhancedTaxWarningStatus
|
||||
} from '@/lib/tax/calculator'
|
||||
import FSkattWarningCard from '@/components/dashboard/FSkattWarningCard'
|
||||
import { UpcomingDeadlinesWidget } from '@/components/deadlines/UpcomingDeadlinesWidget'
|
||||
import { TaxTodoWidget } from '@/components/deadlines/TaxTodoWidget'
|
||||
import NewUserChecklist from '@/components/onboarding/NewUserChecklist'
|
||||
@@ -77,18 +71,6 @@ export default function DashboardContent({ firstName, settings, summary, onboard
|
||||
}, [])
|
||||
|
||||
const entityType = (settings?.entity_type as EntityType) || 'enskild_firma'
|
||||
const preliminaryTaxMonthly = settings?.preliminary_tax_monthly || 0
|
||||
const currentMonth = new Date().getMonth() + 1
|
||||
const preliminaryTaxPaidYTD = preliminaryTaxMonthly * currentMonth
|
||||
|
||||
const totalTaxableIncome = summary.ytd.net
|
||||
|
||||
const taxEstimate =
|
||||
entityType === 'enskild_firma'
|
||||
? calculateEFTax(totalTaxableIncome, preliminaryTaxPaidYTD, null, summary.unpaidVatTotal)
|
||||
: calculateABTax(totalTaxableIncome, 0, preliminaryTaxPaidYTD, summary.unpaidVatTotal)
|
||||
|
||||
const taxWarning = getEnhancedTaxWarningStatus(taxEstimate, preliminaryTaxMonthly, currentMonth)
|
||||
|
||||
const formatLargeNumber = (amount: number) => {
|
||||
return new Intl.NumberFormat('sv-SE', {
|
||||
@@ -492,14 +474,6 @@ export default function DashboardContent({ firstName, settings, summary, onboard
|
||||
|
||||
{showMore && (
|
||||
<div>
|
||||
{/* F-skatt warning */}
|
||||
<section id="fskatt-section" className="mb-10">
|
||||
<FSkattWarningCard
|
||||
warningStatus={taxWarning}
|
||||
onAdjustClick={() => { window.location.href = '/settings' }}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Uncategorized transactions warning */}
|
||||
{summary.uncategorizedCount > 0 && (summary.uncategorizedIncome > 0 || summary.uncategorizedExpenses > 0) && (
|
||||
<section className="mb-10">
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { InfoTooltip } from '@/components/ui/info-tooltip'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import {
|
||||
AlertTriangle,
|
||||
Info,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
TrendingUp,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
} from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import type { TaxWarningStatus } from '@/types'
|
||||
|
||||
interface FSkattWarningCardProps {
|
||||
warningStatus: TaxWarningStatus
|
||||
onAdjustClick?: () => void
|
||||
}
|
||||
|
||||
export default function FSkattWarningCard({
|
||||
warningStatus,
|
||||
onAdjustClick,
|
||||
}: FSkattWarningCardProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
// Don't show if everything is safe
|
||||
if (warningStatus.level === 'safe') {
|
||||
return null
|
||||
}
|
||||
|
||||
// Icon and colors based on warning level
|
||||
const levelConfig = {
|
||||
safe: {
|
||||
icon: CheckCircle,
|
||||
iconColor: 'text-success',
|
||||
bgColor: 'bg-success/5',
|
||||
borderColor: 'border-success/50',
|
||||
badgeVariant: 'outline' as const,
|
||||
},
|
||||
info: {
|
||||
icon: Info,
|
||||
iconColor: 'text-primary',
|
||||
bgColor: 'bg-primary/5',
|
||||
borderColor: 'border-primary/20',
|
||||
badgeVariant: 'outline' as const,
|
||||
},
|
||||
warning: {
|
||||
icon: AlertTriangle,
|
||||
iconColor: 'text-warning',
|
||||
bgColor: 'bg-warning/5',
|
||||
borderColor: 'border-warning/50',
|
||||
badgeVariant: 'warning' as const,
|
||||
},
|
||||
danger: {
|
||||
icon: AlertCircle,
|
||||
iconColor: 'text-destructive',
|
||||
bgColor: 'bg-destructive/5',
|
||||
borderColor: 'border-destructive/50',
|
||||
badgeVariant: 'destructive' as const,
|
||||
},
|
||||
}
|
||||
|
||||
const config = levelConfig[warningStatus.level]
|
||||
const Icon = config.icon
|
||||
|
||||
return (
|
||||
<Card className={`${config.borderColor} ${config.bgColor}`}>
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-4">
|
||||
{/* Main warning */}
|
||||
<div className="flex items-start gap-3">
|
||||
<Icon className={`h-5 w-5 flex-shrink-0 ${config.iconColor}`} />
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<InfoTooltip
|
||||
content={
|
||||
<div className="space-y-2">
|
||||
<p className="font-medium">Vad betyder detta?</p>
|
||||
<p>Vi jämför din beräknade skatt med vad du betalat in via F-skatt hittills i år.</p>
|
||||
<p className="text-xs text-muted-foreground">Om du betalat för lite kan du få restskatt. Justera din månatliga F-skatt för att undvika överraskningar.</p>
|
||||
</div>
|
||||
}
|
||||
side="right"
|
||||
>
|
||||
<p className="font-medium">F-skatt varning</p>
|
||||
</InfoTooltip>
|
||||
<Badge variant={config.badgeVariant}>
|
||||
{Math.round(warningStatus.percentageDifference * 100)}% skillnad
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{warningStatus.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable projection section */}
|
||||
{warningStatus.yearEndProjection && (
|
||||
<div className="pt-2">
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="flex items-center justify-between w-full text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Årsprognos
|
||||
</span>
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-3 p-3 rounded-lg bg-background/50 space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Beräknad årsskatt</span>
|
||||
<span className="font-medium">
|
||||
{formatCurrency(warningStatus.yearEndProjection.estimatedTotalTax)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Projicerad inbetalning</span>
|
||||
<span className="font-medium">
|
||||
{formatCurrency(warningStatus.yearEndProjection.projectedPreliminaryPayments)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm pt-2 border-t">
|
||||
<span className="font-medium">Projicerad skillnad</span>
|
||||
<span
|
||||
className={
|
||||
warningStatus.yearEndProjection.projectedDifference > 0
|
||||
? 'text-destructive font-medium'
|
||||
: 'text-success font-medium'
|
||||
}
|
||||
>
|
||||
{warningStatus.yearEndProjection.projectedDifference > 0 ? '+' : ''}
|
||||
{formatCurrency(warningStatus.yearEndProjection.projectedDifference)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recommendation */}
|
||||
{warningStatus.recommendation && (
|
||||
<div className="p-3 rounded-lg bg-background/50 border border-border/50">
|
||||
<p className="text-sm">
|
||||
<span className="font-medium">Rekommendation: </span>
|
||||
{warningStatus.recommendation}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action button */}
|
||||
{onAdjustClick && (
|
||||
<div className="pt-2">
|
||||
<Button variant="outline" size="sm" onClick={onAdjustClick}>
|
||||
Justera F-skatt
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
+1
-124
@@ -1,4 +1,4 @@
|
||||
import type { EntityType, TaxEstimate, TaxWarningLevel, TaxWarningStatus } from '@/types'
|
||||
import type { EntityType, TaxEstimate } from '@/types'
|
||||
|
||||
// Current Swedish tax rates (2026)
|
||||
const TAX_RATES = {
|
||||
@@ -13,14 +13,6 @@ const TAX_RATES = {
|
||||
const STATE_TAX_THRESHOLD = 643100 // Taxable income above this gets +20% state tax
|
||||
// Note: The "brytpunkt" is 660,400 kr but that includes grundavdrag
|
||||
|
||||
// F-skatt warning thresholds (percentage difference)
|
||||
const WARNING_THRESHOLDS = {
|
||||
safe: 0.05, // < 5% difference
|
||||
info: 0.15, // 5-15% difference
|
||||
warning: 0.30, // 15-30% difference
|
||||
// > 30% = danger
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate progressive grundavdrag (basic deduction) for 2026
|
||||
* Based on Skatteverket's table - varies by income level
|
||||
@@ -169,121 +161,6 @@ export function calculateAvailableBalance(
|
||||
return Math.max(0, currentBalance - taxReservation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tax warning status (legacy - simplified version)
|
||||
*/
|
||||
export function getTaxWarningStatus(
|
||||
taxEstimate: TaxEstimate
|
||||
): { level: 'ok' | 'warning' | 'danger'; message: string } {
|
||||
const enhanced = getEnhancedTaxWarningStatus(taxEstimate, 0)
|
||||
// Map new levels to legacy format
|
||||
const levelMap: Record<TaxWarningLevel, 'ok' | 'warning' | 'danger'> = {
|
||||
safe: 'ok',
|
||||
info: 'warning',
|
||||
warning: 'warning',
|
||||
danger: 'danger',
|
||||
}
|
||||
return {
|
||||
level: levelMap[enhanced.level],
|
||||
message: enhanced.message,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enhanced tax warning status with more detail
|
||||
* @param taxEstimate - Current tax estimate
|
||||
* @param preliminaryTaxMonthly - Monthly preliminary tax amount
|
||||
* @param currentMonth - Current month (1-12), defaults to current date
|
||||
*/
|
||||
export function getEnhancedTaxWarningStatus(
|
||||
taxEstimate: TaxEstimate,
|
||||
preliminaryTaxMonthly: number,
|
||||
currentMonth: number = new Date().getMonth() + 1
|
||||
): TaxWarningStatus {
|
||||
// Calculate percentage difference
|
||||
const percentageDifference =
|
||||
taxEstimate.total_tax_liability > 0
|
||||
? taxEstimate.difference / taxEstimate.total_tax_liability
|
||||
: 0
|
||||
|
||||
// Determine warning level
|
||||
let level: TaxWarningLevel
|
||||
if (taxEstimate.difference <= 0) {
|
||||
level = 'safe'
|
||||
} else if (percentageDifference < WARNING_THRESHOLDS.safe) {
|
||||
level = 'safe'
|
||||
} else if (percentageDifference < WARNING_THRESHOLDS.info) {
|
||||
level = 'info'
|
||||
} else if (percentageDifference < WARNING_THRESHOLDS.warning) {
|
||||
level = 'warning'
|
||||
} else {
|
||||
level = 'danger'
|
||||
}
|
||||
|
||||
// Calculate year-end projection
|
||||
const remainingMonths = 12 - currentMonth
|
||||
const projectedPreliminaryPayments =
|
||||
taxEstimate.preliminary_paid_ytd + preliminaryTaxMonthly * remainingMonths
|
||||
|
||||
// Project total tax assuming same income rate
|
||||
const monthsElapsed = currentMonth
|
||||
const projectedAnnualIncome =
|
||||
monthsElapsed > 0
|
||||
? (taxEstimate.total_tax_liability / monthsElapsed) * 12
|
||||
: taxEstimate.total_tax_liability
|
||||
|
||||
const yearEndProjection = {
|
||||
estimatedTotalTax: Math.round(projectedAnnualIncome),
|
||||
projectedPreliminaryPayments: Math.round(projectedPreliminaryPayments),
|
||||
projectedDifference: Math.round(projectedAnnualIncome - projectedPreliminaryPayments),
|
||||
}
|
||||
|
||||
// Generate message and recommendation
|
||||
let message: string
|
||||
let recommendation: string | undefined
|
||||
|
||||
switch (level) {
|
||||
case 'safe':
|
||||
message = 'Du ligger bra till med din preliminärskatt'
|
||||
break
|
||||
case 'info':
|
||||
message = 'Din beräknade skatt är något högre än inbetald preliminärskatt'
|
||||
recommendation = `Överväg att öka din månatliga F-skatt med ca ${formatCurrency(
|
||||
Math.ceil((taxEstimate.difference / remainingMonths) * 1.1)
|
||||
)} för att undvika kvarskatt.`
|
||||
break
|
||||
case 'warning':
|
||||
message = `Du kan behöva betala ${formatCurrency(taxEstimate.difference)} extra i skatt`
|
||||
recommendation = `Vi rekommenderar att du höjer din månatliga F-skatt till ${formatCurrency(
|
||||
Math.ceil(preliminaryTaxMonthly + taxEstimate.difference / Math.max(remainingMonths, 1))
|
||||
)} för resten av året.`
|
||||
break
|
||||
case 'danger':
|
||||
message = `Stor skillnad! Du kan behöva betala ${formatCurrency(
|
||||
taxEstimate.difference
|
||||
)} extra i skatt vid deklaration`
|
||||
recommendation = `Kontakta Skatteverket för att justera din F-skatt. Nuvarande skillnad är ${Math.round(
|
||||
percentageDifference * 100
|
||||
)}% av beräknad skatt.`
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
level,
|
||||
message,
|
||||
percentageDifference,
|
||||
yearEndProjection,
|
||||
recommendation,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to format currency for messages
|
||||
*/
|
||||
function formatCurrency(amount: number): string {
|
||||
return `${Math.round(amount).toLocaleString('sv-SE')} kr`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format tax breakdown for display
|
||||
*/
|
||||
|
||||
@@ -1009,9 +1009,6 @@ export interface CreateFiscalPeriodInput {
|
||||
period_end: string
|
||||
}
|
||||
|
||||
// Tax warning levels
|
||||
export type TaxWarningLevel = 'safe' | 'info' | 'warning' | 'danger'
|
||||
|
||||
// Onboarding progress for new user checklist
|
||||
export interface OnboardingProgress {
|
||||
hasCustomers: boolean
|
||||
@@ -1020,19 +1017,6 @@ export interface OnboardingProgress {
|
||||
hasBankConnected: boolean
|
||||
}
|
||||
|
||||
// Enhanced tax warning status
|
||||
export interface TaxWarningStatus {
|
||||
level: TaxWarningLevel
|
||||
message: string
|
||||
percentageDifference: number
|
||||
yearEndProjection?: {
|
||||
estimatedTotalTax: number
|
||||
projectedPreliminaryPayments: number
|
||||
projectedDifference: number
|
||||
}
|
||||
recommendation?: string
|
||||
}
|
||||
|
||||
// Onboarding step data
|
||||
export interface OnboardingStepData {
|
||||
step1?: {
|
||||
|
||||
Reference in New Issue
Block a user