Files
accounted/components/salary/TaxTableStatus.tsx
T
Mattsson 3fa871c742 Bug/accounting suggestion (#456)
* feat: add bike benefit handling and optional vacation accrual

- Introduced bike benefit (cykelförmån) with calculations for annual market value and monthly taxable value.
- Updated schemas to include new benefit types and validation rules.
- Implemented API routes for creating, updating, and deleting employee benefits.
- Enhanced salary calculation logic to accommodate new vacation rule options, including a 'none' option for no accrual.
- Added UI components for managing employee benefits, including input for bike benefit specifics.
- Created database migrations for employee benefits and updated salary line items to support new benefit types.

* chore: remove Langfuse env var checks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: enhance OAuth callback URL handling and update default scopes for Visma integration

* feat: remove trade_name field and simplify company naming in invoices

* refactor: destructure canWrite from useCanWrite for consistency across components

* feat: enhance PATCH endpoint to validate existing benefits and handle bike benefit updates

* feat: add missing label for bike benefit in salary line item types

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:26:04 +02:00

87 lines
2.4 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { Globe, CheckCircle2, AlertTriangle, RefreshCcw, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
type Source = 'api' | 'fallback' | 'unavailable'
interface Status {
year: number
source: Source
reachable: boolean
checkedAt: string
}
interface Props {
year?: number
compact?: boolean
}
export function TaxTableStatus({ year, compact = false }: Props) {
const [status, setStatus] = useState<Status | null>(null)
const [loading, setLoading] = useState(true)
async function check() {
setLoading(true)
const params = new URLSearchParams()
if (year) params.set('year', String(year))
const res = await fetch(`/api/salary/tax-tables/status?${params}`)
if (res.ok) {
const { data } = await res.json()
setStatus(data)
}
setLoading(false)
}
useEffect(() => {
check()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [year])
if (loading && !status) {
return (
<div className="text-xs text-muted-foreground flex items-center gap-1.5">
<Loader2 className="h-3 w-3 animate-spin" />
Kontrollerar skattetabeller
</div>
)
}
if (!status) return null
const Icon = status.source === 'api' ? CheckCircle2 : AlertTriangle
const iconColor =
status.source === 'api' ? 'text-success' :
status.source === 'fallback' ? 'text-warning' :
'text-destructive'
const label =
status.source === 'api'
? `Skattetabeller för ${status.year} hämtas live från Skatteverket`
: status.source === 'fallback'
? `Skatteverkets API är inte nåbart — använder lokal reservdata för ${status.year}`
: `Skattetabeller för ${status.year} kunde inte hämtas`
if (compact) {
return (
<p className="text-xs text-muted-foreground flex items-center gap-1.5">
<Globe className="h-3 w-3" />
{label}
</p>
)
}
return (
<div className="flex items-center justify-between gap-3 rounded-md border bg-muted/30 px-3 py-2">
<div className="flex items-center gap-2 text-sm">
<Icon className={`h-4 w-4 ${iconColor}`} />
<span>{label}</span>
</div>
<Button variant="ghost" size="sm" onClick={check} disabled={loading} aria-label="Kontrollera igen">
{loading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RefreshCcw className="h-3.5 w-3.5" />}
</Button>
</div>
)
}