feat: overhaul all 12 sector extensions with full CRUD, validation, and tests

Add shared components (ConfirmDeleteDialog, EditEntryDialog, validation utils),
enhance all 12 extension workspaces with edit/delete dialogs, input validation,
period comparisons, and new analytics features. Fix critical bugs in
ProjectBilling margin calculation and EarningsPerLiter revenue allocation.
Add pure calculation modules with 183 new tests across all extensions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Emil
2026-02-21 15:21:49 +01:00
co-authored by Claude Opus 4.6
parent 026497ed75
commit 6a5b2b7792
52 changed files with 14130 additions and 222 deletions
@@ -0,0 +1,72 @@
'use client'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Settings } from 'lucide-react'
import { useState } from 'react'
interface SetupField {
key: string
label: string
type?: 'number' | 'text'
placeholder?: string
}
interface SetupPromptProps {
title: string
description: string
fields: SetupField[]
onSave: (values: Record<string, string>) => Promise<void>
}
export default function SetupPrompt({ title, description, fields, onSave }: SetupPromptProps) {
const [values, setValues] = useState<Record<string, string>>({})
const [isSaving, setIsSaving] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSaving(true)
try {
await onSave(values)
} finally {
setIsSaving(false)
}
}
const allFilled = fields.every(f => values[f.key]?.trim())
return (
<div className="flex items-center justify-center py-12">
<Card className="w-full max-w-md">
<CardContent className="pt-6">
<div className="text-center mb-6">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-muted mb-3">
<Settings className="h-6 w-6 text-muted-foreground" />
</div>
<h3 className="font-semibold">{title}</h3>
<p className="text-sm text-muted-foreground mt-1">{description}</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{fields.map(field => (
<div key={field.key} className="space-y-2">
<Label htmlFor={`setup-${field.key}`}>{field.label}</Label>
<Input
id={`setup-${field.key}`}
type={field.type ?? 'text'}
placeholder={field.placeholder}
value={values[field.key] ?? ''}
onChange={e => setValues(prev => ({ ...prev, [field.key]: e.target.value }))}
/>
</div>
))}
<Button type="submit" className="w-full" disabled={!allFilled || isSaving}>
{isSaving ? 'Sparar...' : 'Kom igang'}
</Button>
</form>
</CardContent>
</Card>
</div>
)
}