feat(bokslut): the bokslut trio in the flat concept language (scenes 34-36) (#1161)

* feat(bokslut): Arsbokslut hub as Stegen with de-boxed linear steps (trio 1/3)

Scene 34: the six-step wizard gets the house horizontal stepper (done
checks behind the current step, click-back navigation, forward stays
with each step's continue action), the period select moves into the
header as the context control, and the progress-bar and picker cards
die. Preflight, Preview, Execute and Result are de-boxed: sans eyebrow
sections with hairlines, quiet action links, attn lines instead of
boxed alerts, muted text for normal states. Step content is centered
at reading width. Accruals/Dispositions keep their internals for now
(interactive panels; follow-up pass). All wizard logic untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(bokslut): INK2 and NE-bilaga views in the flat house language (trio 2/3)

Scene 36 direction: the declaration views lose every card. Statutory
sections (Tillgangar, Eget kapital och skulder, Resultatrakning, INK2S,
Intakter, Kostnader) become sans eyebrow sections with hairlines, the
header card becomes a flat block with the SRU download beside it, the
filing instructions lose their info box, warnings render as attn lines,
and the NE R11 result becomes the emphasized document-foot row. All
ruta tables, SRU downloads and warnings logic untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(bokslut): Arsredovisning page, studio and digital filing in the flat house language (trio 3/3)

De-boxes the whole scene-35 surface: the page shell (period picker,
K3 note, narrativ, flerarsoversikt, underskrifter, PDF section), the
AnnualReportStudio (workflow strip, scope form, completeness checks,
versions) and DigitalInlamning (iXBRL review, submission form,
status history) all move from Card shells to sans-eyebrow sections
with hairlines. Inner info boxes flatten to bordered text blocks;
the digital-checks warning becomes an attn line. The iframe border
and the Kommer snart overlay sign stay: one frames an external
document, the other floats above blurred content.

Statutory Swedish-only surface: no new i18n keys.

* polish(bokslut): align trio controls with the house language

Founder feedback: buttons and selects still read old-style next to the
bookkeeping page. Sweeps all trio surfaces:

- Drop every min-h-11 override on Buttons and Inputs (44px chunky
  controls) so the compact house pill and h-10 input apply.
- Replace all native <select> elements (BooleanQuestion, currency,
  group size, signing method, signer role, versions, AGM outcome,
  roll) with the shadcn Select primitive used system-wide.
- Step navigation matches the merged Moms Stegen: white outline
  size-sm pills labelled 'Nästa: <steg> →' and '← Tillbaka'. Booking
  commits (Verkställ, Bokför valda dispositioner) stay primary.
- Year-end period picker becomes the ContextPicker chip (the house
  context-picker idiom), and the stepper row widens to max-w-4xl so
  step 6 'Klart' no longer clips.
- font-sans on two uppercase h3 eyebrows that rendered serif via the
  global h1-h3 display rule.

* fix(vat): one toolbar row and the FyPicker chip on every fiscal-year surface

The prod momsdeklaration (helårsmoms) broke into two sparse rows:
Exportera alone, then a labelled 280px FiscalYearSelector below it,
plus a serif uppercase worklist heading and internal check codes in
the UI.

- The VAT toolbar is one flat row: periodicity picker, fiscal-year
  chip, black Exportera, all h-9 aligned.
- FyPicker (the rounded ContextPicker chip from UI-migration PR 3)
  replaces FiscalYearSelector on every page-level surface it had
  left: the VAT toolbar, FocusedReport's header (all report detail
  pages) and Kassaflödesanalys. Dialog/settings forms keep the
  labelled select, which is a form field, not a context picker.
- 'Verifikationer som saknar basbelopp' becomes a sans eyebrow with
  hairline; internal codes (RC_BASIS_MISSING et al) no longer render
  in check rows, the Swedish message already cites the SKV felkod.
- font-sans on the SkatteverketPanel validation eyebrow.

* polish(vat): periodicity, year and quarter/month pickers as ContextPicker chips

Founder feedback: the Arsvis dropdown should also be the rounded
style. The cadence choice now lives behind a settings-style 'Period'
chip (Manadsvis/Kvartalsvis/Arsvis with a check on the active one),
and the year and quarter/month selects become chips too, so the
whole momsdeklaration toolbar is chip-shaped: Period, 2026,
Kvartal 3 (jul-sep) or Rakenskapsar 2026, then the black Exportera
pill. The concrete period chip already implies the cadence, so the
Period chip can stay label-only.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-24 20:20:00 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 49e86e2e67
commit 98e1a48c2b
17 changed files with 778 additions and 824 deletions
+112 -102
View File
@@ -5,9 +5,15 @@ import { useTranslations } from 'next-intl'
import { AlertCircle, CheckCircle2, FileClock, Loader2, LockKeyhole, Save } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { useToast } from '@/components/ui/use-toast'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
import type {
@@ -60,18 +66,18 @@ function BooleanQuestion({
return (
<div className="space-y-2">
<Label htmlFor={id}>{label}</Label>
<select
id={id}
className="min-h-11 w-full rounded-md border border-border bg-background px-3 text-sm"
<Select
value={value === null ? '' : value ? 'yes' : 'no'}
onChange={(event) =>
onChange(event.target.value === '' ? null : event.target.value === 'yes')
}
onValueChange={(next) => onChange(next === 'yes')}
>
<option value="">{t('choose')}</option>
<option value="no">{t('no')}</option>
<option value="yes">{t('yes')}</option>
</select>
<SelectTrigger id={id}>
<SelectValue placeholder={t('choose')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="no">{t('no')}</SelectItem>
<SelectItem value="yes">{t('yes')}</SelectItem>
</SelectContent>
</Select>
</div>
)
}
@@ -289,60 +295,57 @@ export function AnnualReportStudio({
if (loading || !profile || !compliance) {
return (
<Card>
<CardContent className="flex min-h-24 items-center justify-center p-6 text-sm text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> {t('loading')}
</CardContent>
</Card>
<div className="flex min-h-24 items-center justify-center px-1 py-6 text-sm text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> {t('loading')}
</div>
)
}
return (
<div className="space-y-6">
<Card>
<CardHeader className="space-y-3">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<CardTitle className="text-lg">{t('title')}</CardTitle>
<p className="mt-1 text-sm text-muted-foreground">{t('description')}</p>
<div className="space-y-8">
<div className="space-y-3 px-1">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h3 className="font-sans text-sm font-medium">{t('title')}</h3>
<p className="mt-1 text-sm text-muted-foreground">{t('description')}</p>
</div>
{blockingIssues.length === 0 ? (
<span className="text-xs text-muted-foreground">{t('no_blockers')}</span>
) : (
<Badge variant="warning">{t('blocker_count', { count: blockingIssues.length })}</Badge>
)}
</div>
<div className="grid gap-2 sm:grid-cols-4">
{[
[
t('step_scope'),
compliance.eligibility.issues.every((issue) => issue.severity !== 'error'),
],
[t('step_content'), Boolean(profile.narrative_confirmed_at)],
[t('step_signatures'), versions.some((version) => version.status === 'signed')],
[t('step_filing'), versions.some((version) => ['filed', 'registered'].includes(version.status))],
].map(([label, complete], index) => (
<div key={String(label)} className="flex items-center gap-2 border-b border-border/60 px-1 py-2 text-sm">
{complete ? (
<CheckCircle2 className="h-4 w-4 shrink-0 text-success" />
) : (
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-border text-xs">
{index + 1}
</span>
)}
<span>{label}</span>
</div>
<Badge variant={blockingIssues.length === 0 ? 'success' : 'warning'}>
{blockingIssues.length === 0
? t('no_blockers')
: t('blocker_count', { count: blockingIssues.length })}
</Badge>
</div>
<div className="grid gap-2 sm:grid-cols-4">
{[
[
t('step_scope'),
compliance.eligibility.issues.every((issue) => issue.severity !== 'error'),
],
[t('step_content'), Boolean(profile.narrative_confirmed_at)],
[t('step_signatures'), versions.some((version) => version.status === 'signed')],
[t('step_filing'), versions.some((version) => ['filed', 'registered'].includes(version.status))],
].map(([label, complete], index) => (
<div key={String(label)} className="flex min-h-11 items-center gap-2 border-b border-border px-1 py-2 text-sm">
{complete ? (
<CheckCircle2 className="h-4 w-4 shrink-0 text-success" />
) : (
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-border text-xs">
{index + 1}
</span>
)}
<span>{label}</span>
</div>
))}
</div>
</CardHeader>
</Card>
))}
</div>
</div>
<Card>
<CardHeader>
<CardTitle className="text-base">{t('scope_title')}</CardTitle>
<p className="text-sm text-muted-foreground">{t('scope_description')}</p>
</CardHeader>
<CardContent className="space-y-6">
<section>
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">{t('scope_title')}</h3>
<div className="h-px flex-1 bg-border/60" />
</div>
<p className="px-1 text-sm text-muted-foreground">{t('scope_description')}</p>
<div className="space-y-6 px-1 pt-4">
<div className="grid gap-4 md:grid-cols-2">
<BooleanQuestion
id="ar-public"
@@ -376,20 +379,23 @@ export function AnnualReportStudio({
/>
<div className="space-y-2">
<Label htmlFor="ar-reporting-currency">{t('reporting_currency')}</Label>
<select
id="ar-reporting-currency"
className="min-h-11 w-full rounded-md border border-border bg-background px-3 text-sm"
<Select
value={profile.reporting_currency}
onChange={(event) =>
onValueChange={(next) =>
updateProfile(
'reporting_currency',
event.target.value as AnnualReportProfile['reporting_currency'],
next as AnnualReportProfile['reporting_currency'],
)
}
>
<option value="SEK">SEK</option>
<option value="EUR">EUR</option>
</select>
<SelectTrigger id="ar-reporting-currency">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="SEK">SEK</SelectItem>
<SelectItem value="EUR">EUR</SelectItem>
</SelectContent>
</Select>
</div>
{profile.auditor_report_required && (
<BooleanQuestion
@@ -405,21 +411,23 @@ export function AnnualReportStudio({
<div className="grid gap-4 border-t border-border pt-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="ar-group-size">{t('group_size')}</Label>
<select
id="ar-group-size"
className="min-h-11 w-full rounded-md border border-border bg-background px-3 text-sm"
<Select
value={profile.parent_group_size ?? ''}
onChange={(event) =>
onValueChange={(next) =>
updateProfile(
'parent_group_size',
(event.target.value || null) as AnnualReportProfile['parent_group_size'],
next as AnnualReportProfile['parent_group_size'],
)
}
>
<option value="">{t('choose')}</option>
<option value="small">{t('group_small')}</option>
<option value="large">{t('group_large')}</option>
</select>
<SelectTrigger id="ar-group-size">
<SelectValue placeholder={t('choose')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="small">{t('group_small')}</SelectItem>
<SelectItem value="large">{t('group_large')}</SelectItem>
</SelectContent>
</Select>
</div>
<BooleanQuestion
id="ar-consolidated"
@@ -446,7 +454,7 @@ export function AnnualReportStudio({
type="number"
min={0}
max={100}
className="min-h-11"
value={profile.building_revenue_share_pct ?? ''}
onChange={(event) =>
updateProfile(
@@ -478,19 +486,20 @@ export function AnnualReportStudio({
)}
<div className="flex justify-end">
<Button className="min-h-11" onClick={() => void saveProfile()} disabled={saving}>
<Button onClick={() => void saveProfile()} disabled={saving}>
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
{t('save_scope')}
</Button>
</div>
</CardContent>
</Card>
</div>
</section>
<Card>
<CardHeader>
<CardTitle className="text-base">{t('checks_title')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<section>
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">{t('checks_title')}</h3>
<div className="h-px flex-1 bg-border/60" />
</div>
<div className="space-y-4 px-1 pt-2">
{blockingIssues.length === 0 ? (
<div className="flex items-center gap-2 text-sm text-success">
<CheckCircle2 className="h-4 w-4" /> {t('no_blockers')}
@@ -509,8 +518,8 @@ export function AnnualReportStudio({
</ul>
)}
{digitalOnlyIssues.length > 0 && (
<div className="space-y-2 rounded-md border border-warning/40 bg-warning/5 p-3">
<p className="text-sm font-medium">{t('digital_checks_title')}</p>
<div className="space-y-2">
<p className="text-[12.5px] font-medium leading-5 text-attn">{t('digital_checks_title')}</p>
<ul className="space-y-2">
{digitalOnlyIssues.map((issue) => (
<li key={issue.code} className="text-sm text-muted-foreground">
@@ -520,10 +529,10 @@ export function AnnualReportStudio({
</ul>
</div>
)}
<div className="flex flex-wrap justify-end gap-3 border-t border-border pt-4">
<div className="flex flex-wrap justify-end gap-3 border-t border-border/60 pt-4">
<Button
variant="outline"
className="min-h-11"
onClick={() => void confirmSignerRoster()}
disabled={saving || Boolean(profile.signer_roster_confirmed_at)}
>
@@ -534,30 +543,31 @@ export function AnnualReportStudio({
</Button>
<Button
variant="outline"
className="min-h-11"
onClick={() => void confirmNarrative()}
disabled={saving || hasUnsavedNarrative || Boolean(profile.narrative_confirmed_at)}
>
<CheckCircle2 className="mr-2 h-4 w-4" />
{profile.narrative_confirmed_at ? t('content_confirmed') : t('confirm_content')}
</Button>
<Button variant="outline" className="min-h-11" onClick={() => void createVersion('snapshot')} disabled={creatingVersion !== null}>
<Button variant="outline" onClick={() => void createVersion('snapshot')} disabled={creatingVersion !== null}>
{creatingVersion === 'snapshot' ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <FileClock className="mr-2 h-4 w-4" />}
{t('create_snapshot')}
</Button>
<Button className="min-h-11" onClick={() => void createVersion('finalize')} disabled={creatingVersion !== null || blockingIssues.length > 0 || hasUnsavedNarrative}>
<Button onClick={() => void createVersion('finalize')} disabled={creatingVersion !== null || blockingIssues.length > 0 || hasUnsavedNarrative}>
{creatingVersion === 'finalize' ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <LockKeyhole className="mr-2 h-4 w-4" />}
{t('lock_version')}
</Button>
</div>
</CardContent>
</Card>
</div>
</section>
<Card>
<CardHeader>
<CardTitle className="text-base">{t('versions_title')}</CardTitle>
</CardHeader>
<CardContent>
<section>
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">{t('versions_title')}</h3>
<div className="h-px flex-1 bg-border/60" />
</div>
<div className="px-1 pt-2">
{versions.length === 0 ? (
<p className="text-sm text-muted-foreground">{t('versions_empty')}</p>
) : (
@@ -572,7 +582,7 @@ export function AnnualReportStudio({
<Badge variant={version.status === 'registered' ? 'success' : version.status === 'draft' ? 'outline' : 'secondary'}>
{t(`status_${version.status}`)}
</Badge>
<Button className="min-h-11" variant="outline" size="sm" asChild>
<Button variant="outline" size="sm" asChild>
<a href={`/api/bookkeeping/fiscal-periods/${periodId}/arsredovisning/pdf?version=${version.id}`} target="_blank" rel="noopener noreferrer">
{t('open_pdf')}
</a>
@@ -582,8 +592,8 @@ export function AnnualReportStudio({
))}
</div>
)}
</CardContent>
</Card>
</div>
</section>
</div>
)
}
+90 -78
View File
@@ -18,11 +18,17 @@
import { useCallback, useEffect, useState } from 'react'
import { useTranslations } from 'next-intl'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { useToast } from '@/components/ui/use-toast'
import { formatDate } from '@/lib/utils'
import {
@@ -421,25 +427,26 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
}
>
{/* Steg: Granska & validera */}
<Card>
<CardHeader>
<CardTitle className="text-base">Digital inlämning: granska &amp; validera (iXBRL)</CardTitle>
<p className="text-sm text-muted-foreground">
Bolagsverket tar emot årsredovisningen som iXBRL (XHTML). Dokumentet nedan är
exakt det som lämnas in: granska det som den slutliga presentationen.
</p>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<section>
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">Digital inlämning: granska &amp; validera (iXBRL)</h3>
<div className="h-px flex-1 bg-border/60" />
</div>
<p className="px-1 text-sm text-muted-foreground">
Bolagsverket tar emot årsredovisningen som iXBRL (XHTML). Dokumentet nedan är
exakt det som lämnas in: granska det som den slutliga presentationen.
</p>
<div className="space-y-4 px-1 pt-4 text-sm">
<div className="flex flex-wrap gap-3">
<Button className="min-h-11" variant="outline" onClick={() => setShowPreview((value) => !value)}>
<Button variant="outline" onClick={() => setShowPreview((value) => !value)}>
{showPreview ? 'Dölj förhandsgranskning' : 'Förhandsgranska iXBRL'}
</Button>
<Button className="min-h-11" variant="outline" asChild>
<Button variant="outline" asChild>
<a href={downloadUrl}>
<FileDown className="mr-2 h-4 w-4" /> Ladda ner tekniskt iXBRL-underlag
</a>
</Button>
<Button className="min-h-11" onClick={() => void handleValidate()} disabled={validating}>
<Button onClick={() => void handleValidate()} disabled={validating}>
{validating ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
@@ -486,20 +493,21 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
)}
</div>
)}
</CardContent>
</Card>
</div>
</section>
{/* Steg: Skicka in */}
<Card>
<CardHeader>
<CardTitle className="text-base">Skicka in till Bolagsverket</CardTitle>
<p className="text-sm text-muted-foreground">
Årsredovisningen laddas upp till företagets eget utrymme hos Bolagsverket.
Undertecknaren får ett e-postmeddelande och signerar fastställelseintyget med
e-legitimation hos Bolagsverket: först då är årsredovisningen inlämnad.
</p>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<section>
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">Skicka in till Bolagsverket</h3>
<div className="h-px flex-1 bg-border/60" />
</div>
<p className="px-1 text-sm text-muted-foreground">
Årsredovisningen laddas upp till företagets eget utrymme hos Bolagsverket.
Undertecknaren får ett e-postmeddelande och signerar fastställelseintyget med
e-legitimation hos Bolagsverket: först då är årsredovisningen inlämnad.
</p>
<div className="space-y-4 px-1 pt-4 text-sm">
{extensionActive === null && (
<p className="text-muted-foreground">
<Loader2 className="inline h-4 w-4 animate-spin mr-2" />
@@ -515,7 +523,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
pappersinlämning per post, eller aktivera integrationen efter avtal,
organisationscertifikat och godkänt acceptanstest.
</p>
<Button className="min-h-11" variant="outline" asChild>
<Button variant="outline" asChild>
<a
href="https://bolagsverket.se/foretag/aktiebolag/arsredovisningforaktiebolag.759.html"
target="_blank"
@@ -544,12 +552,9 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
<div className="space-y-1.5">
<Label htmlFor="di-version">Undertecknad årsredovisningsversion</Label>
<select
id="di-version"
className="min-h-11 w-full rounded-md border border-border bg-background px-3 text-sm"
<Select
value={selectedVersionId}
onChange={(event) => {
const versionId = event.target.value
onValueChange={(versionId) => {
setSelectedVersionId(versionId)
const signer = versions.find((version) => version.id === versionId)
?.certificate_signer
@@ -560,18 +565,22 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
}
}}
>
<option value="">Välj undertecknad version</option>
{versions
.filter(
(version) =>
version.status === 'signed' && version.digital_filing_eligible,
)
.map((version) => (
<option key={version.id} value={version.id}>
Version {version.version_number}: skapad {formatDate(version.created_at)}
</option>
))}
</select>
<SelectTrigger id="di-version">
<SelectValue placeholder="Välj undertecknad version" />
</SelectTrigger>
<SelectContent>
{versions
.filter(
(version) =>
version.status === 'signed' && version.digital_filing_eligible,
)
.map((version) => (
<SelectItem key={version.id} value={version.id}>
Version {version.version_number}: skapad {formatDate(version.created_at)}
</SelectItem>
))}
</SelectContent>
</Select>
{versions.every(
(version) =>
version.status !== 'signed' || !version.digital_filing_eligible,
@@ -593,7 +602,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
<Label htmlFor="di-avsandare-pnr">Ditt personnummer (avsändare)</Label>
<Input
id="di-avsandare-pnr"
className="min-h-11"
inputMode="numeric"
placeholder="ÅÅÅÅMMDDNNNN eller ÅÅMMDD-NNNN"
value={avsandarePnr}
@@ -604,7 +613,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
<Label htmlFor="di-pnr">Undertecknarens personnummer</Label>
<Input
id="di-pnr"
className="min-h-11"
inputMode="numeric"
placeholder="ÅÅÅÅMMDDNNNN eller ÅÅMMDD-NNNN"
value={pnr}
@@ -615,7 +624,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
<Label htmlFor="di-fornamn">Undertecknarens förnamn</Label>
<Input
id="di-fornamn"
className="min-h-11"
value={fornamn}
onChange={(event) => setFornamn(event.target.value)}
readOnly={Boolean(selectedVersionId)}
@@ -625,7 +634,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
<Label htmlFor="di-efternamn">Undertecknarens efternamn</Label>
<Input
id="di-efternamn"
className="min-h-11"
value={efternamn}
onChange={(event) => setEfternamn(event.target.value)}
readOnly={Boolean(selectedVersionId)}
@@ -633,23 +642,26 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
</div>
<div className="space-y-1.5">
<Label htmlFor="di-roll">Roll</Label>
<select
id="di-roll"
className="min-h-11 w-full rounded-md border border-border bg-background px-3 text-sm"
<Select
value={roll}
onChange={(event) => setRoll(event.target.value)}
onValueChange={setRoll}
disabled={Boolean(selectedVersionId)}
>
<option>Styrelseledamot</option>
<option>Styrelseordförande</option>
<option>Verkställande direktör</option>
</select>
<SelectTrigger id="di-roll">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Styrelseledamot">Styrelseledamot</SelectItem>
<SelectItem value="Styrelseordförande">Styrelseordförande</SelectItem>
<SelectItem value="Verkställande direktör">Verkställande direktör</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label htmlFor="di-epost">Undertecknarens e-post</Label>
<Input
id="di-epost"
className="min-h-11"
type="email"
placeholder="namn@foretag.se"
value={epost}
@@ -663,7 +675,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
</p>
{registryInformation && (
<div className="rounded-md border border-border bg-muted/20 p-4 text-xs">
<div className="border-t border-border/60 pt-3 text-xs">
<p className="font-medium">Grunduppgifter från Bolagsverket</p>
<p className="mt-1 text-muted-foreground">
{registryInformation.namn}
@@ -689,12 +701,12 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
)}
{avtal && (
<div className="rounded-lg border border-border p-4 space-y-3">
<div className="space-y-3 border-y border-border/60 py-4">
<p className="font-medium">Villkor för eget utrymme hos Bolagsverket</p>
<p className="text-muted-foreground whitespace-pre-wrap text-xs max-h-48 overflow-y-auto">
{avtal.text}
</p>
<label className="flex min-h-11 cursor-pointer items-start gap-3 text-sm">
<label className="flex cursor-pointer items-start gap-3 text-sm">
<input
type="checkbox"
className="mt-0.5"
@@ -737,7 +749,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
<div className="flex flex-wrap gap-3">
<Button
className="min-h-11"
onClick={() => void handleSubmit()}
disabled={
submitting ||
@@ -755,7 +767,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
</Button>
{utfall && utfall.length > 0 && !utfallHasErrors && (
<Button
className="min-h-11"
variant="outline"
onClick={() => void handleSubmit({ ignoreWarnings: true })}
disabled={submitting}
@@ -766,16 +778,16 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
</div>
{kvittens && (
<div className="rounded-lg border border-border p-4 space-y-2">
<div className="space-y-2 border-t border-border/60 pt-4">
<div className="flex items-center gap-2">
<ShieldCheck className="h-4 w-4" />
<ShieldCheck className="h-4 w-4 text-success" />
<p className="font-medium">Uppladdad till eget utrymme</p>
</div>
<p className="text-muted-foreground text-xs">
Undertecknaren har fått e-post från Bolagsverket och signerar
fastställelseintyget där. Ärendet startar först efter signering.
</p>
<Button className="min-h-11" variant="outline" size="sm" asChild>
<Button variant="outline" size="sm" asChild>
<a href={kvittens.url} target="_blank" rel="noopener noreferrer">
<ExternalLink className="mr-2 h-4 w-4" /> Signera hos Bolagsverket
</a>
@@ -784,22 +796,22 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
)}
</>
)}
</CardContent>
</Card>
</div>
</section>
{/* Steg: Status */}
{extensionActive === true && (
<Card>
<CardHeader>
<CardTitle className="text-base">Inlämningsstatus</CardTitle>
<p className="text-sm text-muted-foreground">
Status uppdateras automatiskt via händelseaviseringar från Bolagsverket.
</p>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<section>
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">Inlämningsstatus</h3>
<div className="h-px flex-1 bg-border/60" />
</div>
<p className="px-1 text-sm text-muted-foreground">
Status uppdateras automatiskt via händelseaviseringar från Bolagsverket.
</p>
<div className="space-y-4 px-1 pt-4 text-sm">
<div className="flex justify-end">
<Button
className="min-h-11"
variant="outline"
size="sm"
disabled={pollingEvents}
@@ -859,7 +871,7 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
)}
</div>
{submission.bolagsverket_url && submission.status === 'uploaded' && (
<Button className="min-h-11" variant="outline" size="sm" asChild>
<Button variant="outline" size="sm" asChild>
<a href={submission.bolagsverket_url} target="_blank" rel="noopener noreferrer">
<ExternalLink className="mr-1 h-3.5 w-3.5" /> Signera
</a>
@@ -868,8 +880,8 @@ export function DigitalInlamning({ periodId }: { periodId: string }) {
</div>
)
})}
</CardContent>
</Card>
</div>
</section>
)}
</div>
{INLAMNING_COMING_SOON && (