'use client' import { useState } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Loader2, Lock } from 'lucide-react' import { useToast } from '@/components/ui/use-toast' import { useCanWrite } from '@/lib/hooks/use-can-write' import { getErrorMessage } from '@/lib/errors/get-error-message' import type { Asset, AssetCategory } from '@/types' /** The list route annotates each asset with whether any depreciation has been * posted against it. When true, the acquisition-basis fields are locked. */ type EditableAsset = Asset & { has_posted_depreciation?: boolean } interface EditAssetDialogProps { asset: EditableAsset open: boolean onOpenChange: (open: boolean) => void onSaved: () => void } // Same category labels as CreateAssetDialog. const CATEGORY_OPTIONS: { value: AssetCategory; label: string }[] = [ { value: 'computer', label: 'Dator / IT-utrustning' }, { value: 'equipment', label: 'Inventarier' }, { value: 'machinery', label: 'Maskiner' }, { value: 'vehicle', label: 'Fordon' }, { value: 'building', label: 'Byggnad' }, { value: 'land_improvement', label: 'Markanläggning' }, { value: 'immaterial', label: 'Immateriell tillgång' }, { value: 'other_tangible', label: 'Övrig materiell tillgång' }, ] export function EditAssetDialog({ asset, open, onOpenChange, onSaved }: EditAssetDialogProps) { const { toast } = useToast() const { canWrite } = useCanWrite() // Once depreciation has been booked, acquisition date/cost/category are // locked: a real change has to go through storno. The server enforces the // same rule (ASSET_CORRECTION_BLOCKED); this just makes it visible up front. const basisLocked = asset.has_posted_depreciation === true const [name, setName] = useState(asset.name) const [category, setCategory] = useState(asset.category) const [acquisitionDate, setAcquisitionDate] = useState(asset.acquisition_date) const [acquisitionCost, setAcquisitionCost] = useState(String(asset.acquisition_cost)) const [usefulLifeYears, setUsefulLifeYears] = useState( String(Math.round(asset.useful_life_months / 12)), ) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const handleSubmit = async () => { setError(null) const trimmedName = name.trim() if (!trimmedName) { setError('Namnet får inte vara tomt.') return } // Send only what actually changed. This keeps a name-only edit on a // depreciated asset from tripping the acquisition-basis guard, and avoids // clobbering a useful_life_months value that isn't a clean multiple of 12. const patch: Record = {} if (trimmedName !== asset.name) patch.name = trimmedName if (!basisLocked) { if (category !== asset.category) patch.category = category if (acquisitionDate !== asset.acquisition_date) patch.acquisition_date = acquisitionDate const cost = parseFloat(acquisitionCost) if (!Number.isFinite(cost) || cost <= 0) { setError('Anskaffningsvärdet måste vara större än 0.') return } if (cost !== Number(asset.acquisition_cost)) patch.acquisition_cost = cost } const years = parseInt(usefulLifeYears, 10) if (!Number.isFinite(years) || years <= 0) { setError('Ange en avskrivningstid (minst 1 år).') return } const months = years * 12 if (months !== asset.useful_life_months) patch.useful_life_months = months if (Object.keys(patch).length === 0) { toast({ title: 'Inga ändringar', description: 'Inget att spara.' }) onOpenChange(false) return } setSubmitting(true) try { const res = await fetch(`/api/assets/${asset.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(patch), }) const body = await res.json() if (!res.ok) { setError(getErrorMessage(body?.error ?? body) || 'Kunde inte spara ändringen.') return } toast({ title: 'Tillgång uppdaterad', description: trimmedName }) onSaved() } catch (err) { setError(getErrorMessage(err)) } finally { setSubmitting(false) } } return ( Ändra anläggningstillgång
setName(e.target.value)} autoFocus />
setAcquisitionDate(e.target.value)} disabled={basisLocked} className="tabular-nums" />
setAcquisitionCost(e.target.value)} disabled={basisLocked} className="tabular-nums" />
{basisLocked && (
Anskaffningsdatum, anskaffningsvärde och kategori är låsta eftersom avskrivningar redan har bokförts. Återför avskrivningen (storno) eller använd avyttring för att ändra grunduppgifterna. Namn och avskrivningstid kan fortfarande justeras.
)}
setUsefulLifeYears(e.target.value)} className="tabular-nums" />
{error && (
{error}
)}
) }