Garantiregimer, försäkringsvillkor och felanmälan i ärendet

---- Garantier (ALVA-SPEC-040) -----------------------------------------

Skiljelinjen mellan LAGSTADGAT och AVTALAT bär allt annat, och den
förväxlas dagligen åt bägge håll. Överraskande lite av det verkstaden
kallar garanti är lagstadgat:

  nybilsgaranti      avtalad. Ingen lag i EU eller USA kräver den.
  rostskyddsgaranti  avtalad. Ingen lag kräver den, någonstans.
  lackgaranti        avtalad.
  drivlinegaranti    avtalad.
  avgasgaranti       LAGSTADGAD i bägge, och den enda som är det.

Att kalla rostskyddet lagstadgat är fel; att kalla avgasgarantin en
tillverkarutfästelse är fel åt andra hållet, och det andra felet kostar
kunden pengar — en katalysator på en sexårig bil offereras rutinmässigt
som en betald reparation när den inte är det.

Varje lagstadgad post bär källa och kontrolldatum: 2019/771 (2 år,
säljaren — inte tillverkaren), MVBER 461/2010 förlängd till 2028,
715/2007 (5 år/100 000 km), 2024/1257 (8 år/160 000 km, batteri 80 %
vid 5 år och 72 % vid 8), CAA § 207 (2/24 000 och 8/80 000 miles),
Magnuson–Moss § 2302(c), CARB (3/50 000 och 7/70 000), ACC II (70 % SoH
vid 8 år/100 000 miles).

Bedömningen skiljer INOM, UTANFÖR och OKLART. Saknas underlag blir det
oklart, aldrig utanför — att påstå att en garanti gått ut när underlaget
inte räcker är det enda felet här som kostar kunden pengar.

---- Försäkring (ALVA-SPEC-041) ----------------------------------------

Modulen avgör ingenting och kan inte avgöra något. Vad som gäller
bestäms av kundens eget försäkringsbrev och villkoren vid tecknandet —
inte av vad ett bolag skriver i dag och inte av vad vi noterat. Testerna
låser att resultatet inte innehåller något fält som liknar ett utfall,
och att ett fordon utanför samtliga noterade gränser ändå inte får ett
nej: registret kan vara ofullständigt och produkten en annan.

Det som står överst är de sju villkoren som avgör en maskinskadefråga.
Ålder och sträcka är två av dem; de fem andra avgör oftare. Registret
med If, Folksam, Trygg-Hansa, Svedea, WaterCircles och Hedvig ligger
under, med källa och avläsningsdatum per post, och märks som föråldrat
efter 180 dagar i stället för att se aktuellt ut.

---- Felanmälan i ärendet (ALVA-PROC-0050) -----------------------------

En anmälningsknapp som bara finns i en portalvy nås aldrig av den som
står med händerna i en bil. Den ligger därför i varje ärende, och tar
med sitt sammanhang självt: ärende, metodik, plattformsversion och
spår-id. Teknikern minns inte plattformsversionen, och att fråga efter
den är att lägga över vårt problem på verkstaden.

Sammanhanget HÄRLEDS och kan inte smugglas: ett regnr i anropet släpps
inte igenom. En supportanmälan är inte ett skäl att flytta
personuppgifter till ett annat system. Bevisat i integrationstestet.

Anmälan är oföränderlig och statusen en projektion av inläggen — samma
skäl som för fakturan: en anmälan vars historia kan skrivas om är inte
ett underlag när någon frågar hur länge felet var känt. Verkstaden
svarar i sitt eget ärende; status sätts av supporten med egen nyckel.

---- Historikkontrollen -------------------------------------------------

Kravet fanns redan: klienten öppnar inte metodiken förrän
pre-diagnostiken är besvarad, och ett nej kräver skriven motivering.
Det var dock aldrig BEVISAT att spärren håller. Genomgången prövar nu
att metodiken inte går att nå medan historikfrågan står obesvarad — i
det enda ögonblick det går att pröva, eftersom svaret därefter redan är
avgivet.

429 enhetstester · 157 integrationskontroller mot riktig Postgres ·
genomgången 4/4 · portalspärren 16/16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-06 14:56:48 +00:00
parent 6790b60182
commit a94c228cab
22 changed files with 2160 additions and 5 deletions
@@ -0,0 +1,189 @@
// ALVA-SPEC-041 · Försäkringsvillkor.
//
// Vyn får aldrig se ut som ett besked. Den är byggd så att det som står
// överst är FRÅGORNA — det som faktiskt avgör en maskinskadefråga — och
// registret därunder, som det underlag det är.
//
// Ordningen är inte kosmetisk. Läggs tabellen först läses den som ett
// svar, och de fem villkor som avgör oftare än åldern gör hamnar under
// vikningen.
import { useState } from "react";
import {
AVGORANDE_VILLKOR,
BOLAG,
FORBEHALL,
bedom,
foraldrad,
spann,
} from "../../../../services/gemensam/forsakring.mjs";
import { Block, Etikett, FARG, Rubrik, Tabell, Textfalt } from "@/alva/komponenter";
import { Ram } from "./Ram";
interface Produkt {
namn: string;
maxAlderAr: number;
maxMil: number;
}
interface Bolag {
id: string;
namn: string;
marknad: string;
produkter: Produkt[];
kalla: string;
kontrollerad: string;
}
interface Villkor {
id: string;
rubrik: string;
fraga: string;
text: string;
}
export default function Forsakring() {
const [alder, setAlder] = useState("");
const [mil, setMil] = useState("");
const tal = (v: string) => (v.trim() === "" || Number.isNaN(Number(v)) ? undefined : Number(v));
const utfall = bedom({ alderAr: tal(alder), mil: tal(mil) }) as {
inom: { bolag: string; produkt: string; foraldrad: boolean }[];
utanfor: { bolag: string; produkt: string }[];
oklart: { bolag: string; produkt: string }[];
nastaSteg: string;
};
const s = spann() as { alderAr: { minsta: number; storsta: number }; mil: { minsta: number; storsta: number } };
const angivet = tal(alder) !== undefined || tal(mil) !== undefined;
return (
<Ram portal>
<div className="mx-auto max-w-[1040px] px-6 py-12">
<Etikett>Reasoning aid</Etikett>
<div className="mt-2 mb-2">
<Rubrik niva={1}>Insurance conditions</Rubrik>
</div>
<p className="mb-8 max-w-[680px] text-[14px] leading-[22px]" style={{ color: FARG.steel }}>
ALVA does not decide whether a claim is covered, and cannot. Cover follows the
customer&rsquo;s own policy and the terms in force when it was taken out. What this page gives
you is the shape of the question and what the register recorded, so the conversation starts
from something better than a guess.
</p>
<Block rubrik="What actually decides it" beteckning="ALVA-SPEC-041">
<p className="mb-4 max-w-[680px] text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
Age and distance are two of seven. The other five decide more claims than the two that get
asked about first.
</p>
<Tabell
kolumner={["Condition", "The question", "Why it matters"]}
rader={(AVGORANDE_VILLKOR as Villkor[]).map((v) => [
<span key="r" className="font-semibold">
{v.rubrik}
</span>,
<span key="f" className="text-[13px]">
{v.fraga}
</span>,
<span key="t" className="text-[12px] leading-[19px]" style={{ color: FARG.steel }}>
{v.text}
</span>,
])}
/>
</Block>
<Block rubrik="Where the vehicle sits against the register" beteckning="ALVA-SPEC-041">
<div className="grid max-w-[420px] gap-4 sm:grid-cols-2">
<Textfalt label="Vehicle age (years)" varde={alder} andra={setAlder} platshallare="6" />
<Textfalt label="Odometer (mil)" varde={mil} andra={setMil} platshallare="9 400" />
</div>
{!angivet ? (
<p className="mt-6 text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
Recorded limits range from {s.alderAr.minsta}{s.alderAr.storsta} years and{" "}
{s.mil.minsta.toLocaleString("sv-SE")}{s.mil.storsta.toLocaleString("sv-SE")} mil, depending
on insurer and product tier.
</p>
) : (
<div className="mt-6 grid gap-4">
{[
["Within recorded limits", utfall.inom],
["Beyond recorded limits", utfall.utanfor],
["Cannot be placed on what you entered", utfall.oklart],
].map(([rubrik, lista]) => {
const rader = lista as { bolag: string; produkt: string; foraldrad?: boolean }[];
return (
<div key={rubrik as string}>
<Etikett>{rubrik as string}</Etikett>
{rader.length === 0 ? (
<p className="mt-2 text-[13px]" style={{ color: FARG.steel }}>
</p>
) : (
<ul className="mt-2 grid gap-2">
{rader.map((r) => (
<li key={`${r.bolag}${r.produkt}`} className="text-[13px]">
<span className="font-semibold">{r.bolag}</span> · {r.produkt}
{r.foraldrad && (
<span className="ml-2 text-[11px] uppercase tracking-[0.08em]" style={{ color: FARG.steel }}>
register entry out of date
</span>
)}
</li>
))}
</ul>
)}
</div>
);
})}
{/* Står även när fordonet ligger utanför varje noterad
gräns. Registret kan vara ofullständigt, produkten en
annan, och villkoren vid tecknandet andra. */}
<p className="border-t pt-4 text-[13px] leading-[21px]" style={{ borderColor: FARG.lightSteel }}>
{utfall.nastaSteg}
</p>
</div>
)}
</Block>
<Block rubrik="Register" beteckning="ALVA-SPEC-041">
<Tabell
kolumner={["Insurer", "Product", "Age", "Distance", "Read"]}
rader={(BOLAG as Bolag[]).flatMap((b) =>
b.produkter.map((p) => [
<span key="b" className="font-semibold">
{b.namn}
</span>,
<span key="p" className="text-[13px]">
{p.namn}
</span>,
<span key="a" className="font-mono text-[12px]">
{p.maxAlderAr} yr
</span>,
<span key="m" className="font-mono text-[12px]">
{p.maxMil.toLocaleString("sv-SE")} mil
</span>,
<span
key="k"
className="font-mono text-[11px]"
style={{ color: foraldrad(b) ? FARG.graphite : FARG.steel }}
>
{b.kontrollerad}
{foraldrad(b) ? " — out of date" : ""}
</span>,
]),
)}
/>
<p className="mt-4 max-w-[680px] text-[12px] leading-[19px]" style={{ color: FARG.steel }}>
The register covers the insurers read so far and is not a market survey. Smaller insurers
and non-Swedish markets have to be added by the organization, with a source and a date
an entry without those two is not usable as a starting point for anything.
</p>
</Block>
<Block rubrik="Basis" beteckning="ALVA-SPEC-041">
<p className="max-w-[680px] text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
{FORBEHALL}
</p>
</Block>
</div>
</Ram>
);
}
+164
View File
@@ -0,0 +1,164 @@
// ALVA-SPEC-040 · Garantiregimer.
//
// Vyn har ett jobb och det är inte att vara fullständig: den ska göra
// skillnaden mellan LAGSTADGAT och AVTALAT omöjlig att missa, och lyfta
// de två regler som avgör om verkstaden får utföra arbetet alls.
//
// Ordningen är därför inte alfabetisk och inte kronologisk. Skyddet för
// oberoende reparation står först, eftersom det är det som ifrågasätts —
// ibland av kunden, som blivit tillsagd något annat.
import { useState } from "react";
import {
FORBEHALL,
JURISDIKTIONER,
KONTROLLERAD,
regimerFor,
skyddadReparation,
} from "../../../../services/gemensam/garantier.mjs";
import { Block, Etikett, FARG, Rubrik, Tabell } from "@/alva/komponenter";
import { Ram } from "./Ram";
interface Regim {
id: string;
jurisdiktion: string;
grund: "lag" | "avtal";
bärare: string;
rubrik: string;
kortform: string;
källa: string;
källänk: string | null;
text: string;
anmärkning: string;
avvikelser: { marknad: string; text: string }[];
}
const BARARE: Record<string, string> = {
säljaren: "Seller",
tillverkaren: "Manufacturer",
};
export default function Garantier() {
const [jurisdiktion, setJurisdiktion] = useState("EU");
const regimer = regimerFor(jurisdiktion) as Regim[];
const lag = regimer.filter((r) => r.grund === "lag");
const avtal = regimer.filter((r) => r.grund === "avtal");
const skydd = skyddadReparation(jurisdiktion) as Regim | null;
return (
<Ram portal>
<div className="mx-auto max-w-[1040px] px-6 py-12">
<Etikett>Regulatory</Etikett>
<div className="mt-2 mb-2">
<Rubrik niva={1}>Warranty regimes</Rubrik>
</div>
<p className="mb-8 max-w-[680px] text-[14px] leading-[22px]" style={{ color: FARG.steel }}>
What the law requires as a floor, and what the manufacturer merely promises. The two are
confused daily on the shop floor, and the confusion runs in both directions a corrosion
warranty treated as statutory, an emissions warranty treated as a favour.
</p>
{/* Jurisdiktionen först: allt nedanför beror på den, och ett svar
för fel marknad är sämre än inget svar. */}
<div className="mb-8">
<Etikett>Jurisdiction</Etikett>
<div className="mt-2 flex flex-wrap gap-2">
{(JURISDIKTIONER as { id: string; namn: string }[]).map((j) => {
const vald = j.id === jurisdiktion;
return (
<button
key={j.id}
type="button"
onClick={() => setJurisdiktion(j.id)}
aria-pressed={vald}
className="border px-4 py-2 text-[12px] font-semibold uppercase tracking-[0.08em]"
style={{
background: vald ? FARG.graphite : FARG.white,
color: vald ? FARG.white : FARG.steel,
borderColor: vald ? FARG.graphite : FARG.lightSteel,
}}
>
{j.namn}
</button>
);
})}
</div>
</div>
{skydd && (
<Block rubrik="Servicing here does not void the warranty" beteckning={skydd.id}>
<p className="max-w-[680px] text-[14px] leading-[23px]">{skydd.text}</p>
<p className="mt-4 max-w-[680px] text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
{skydd.anmärkning}
</p>
<p className="mt-4 font-mono text-[11px]" style={{ color: FARG.steel }}>
{skydd.källa}
</p>
</Block>
)}
<Block rubrik="Statutory — applies regardless of any warranty booklet" beteckning="ALVA-SPEC-040">
<Tabell
kolumner={["Regime", "Period", "Liable", "Source"]}
rader={lag.map((r) => [
<span key="r">
<span className="font-semibold">{r.rubrik}</span>
<span className="mt-2 block text-[12px] leading-[19px]" style={{ color: FARG.steel }}>
{r.text}
</span>
{r.anmärkning && (
<span className="mt-2 block text-[12px] leading-[19px]" style={{ color: FARG.graphite }}>
{r.anmärkning}
</span>
)}
{r.avvikelser.map((a) => (
<span key={a.marknad} className="mt-2 block text-[12px] leading-[19px]" style={{ color: FARG.steel }}>
{a.marknad}: {a.text}
</span>
))}
</span>,
<span key="p" className="font-mono text-[12px]">
{r.kortform}
</span>,
<span key="b" className="text-[12px]" style={{ color: FARG.steel }}>
{BARARE[r.bärare] ?? r.bärare}
</span>,
<span key="k" className="font-mono text-[11px]" style={{ color: FARG.steel }}>
{r.källa}
</span>,
])}
/>
</Block>
<Block rubrik="Contractual — the manufacturer's own promise" beteckning="ALVA-SPEC-040">
<p className="mb-4 max-w-[680px] text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
None of the following has any statutory basis in the EU or the United States. Terms are set
by the manufacturer and vary by brand, market and model year, so the ranges below describe
what occurs on the market not what any given brand undertakes. Read the vehicle&rsquo;s own
warranty terms before quoting a period to a customer.
</p>
<Tabell
kolumner={["Warranty", "Typical", "Note"]}
rader={avtal.map((r) => [
<span key="r" className="font-semibold">
{r.rubrik}
</span>,
<span key="p" className="text-[12px]" style={{ color: FARG.steel }}>
{r.kortform}
</span>,
<span key="n" className="text-[12px] leading-[19px]" style={{ color: FARG.steel }}>
{r.text} {r.anmärkning}
</span>,
])}
/>
</Block>
<Block rubrik="Basis" beteckning={`Verified ${KONTROLLERAD}`}>
<p className="max-w-[680px] text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
{FORBEHALL}
</p>
</Block>
</div>
</Ram>
);
}
+3
View File
@@ -21,6 +21,9 @@ const PORTAL = [
{ till: "/alva/portal/analys", text: "Analysis" },
{ till: "/alva/portal/kunskapskallor", text: "Knowledge sources" },
{ till: "/alva/portal/integration", text: "Integration" },
{ till: "/alva/portal/garantier", text: "Warranty" },
{ till: "/alva/portal/forsakring", text: "Insurance" },
{ till: "/alva/portal/support", text: "Support" },
{ till: "/alva/portal/fakturor", text: "Invoices" },
{ till: "/felsokning", text: "Diagnostics" },
];
+157
View File
@@ -0,0 +1,157 @@
// ALVA-PROC-0050 · Supportärenden.
//
// Listan över organisationens anmälningar. Själva anmälandet sker inte
// här utan i ärendet där felet uppstod — se Felanmalan i felsökningsvyn.
// En anmälningsknapp som bara finns i en portalvy nås aldrig av den som
// står med händerna i en bil.
import { useEffect, useState } from "react";
import { STATUSAR, TYPER } from "../../../../services/gemensam/support.mjs";
import { hamtaSupport, plattformAktiv, type Supportarende } from "@/felsokning/plattform";
import { Block, Demonstration, Etikett, FARG, Rubrik, Statusmärke, Tabell } from "@/alva/komponenter";
import { Ram } from "./Ram";
const STATUSAV: Record<string, "passed" | "pending" | "failed" | "not_applicable"> = {
mottagen: "pending",
under_arbete: "pending",
atgardad: "passed",
stangd: "not_applicable",
};
const STATUSTEXT: Record<string, string> = {
mottagen: "Received",
under_arbete: "In progress",
atgardad: "Resolved",
stangd: "Closed",
};
// Vad som visas när ingen plattform är konfigurerad. Exemplet är märkt.
const EXEMPEL: Supportarende[] = [
{
id: "demo-1",
beteckning: "ALVA-SUP-0001",
arende_id: "ALVA-CASE-91821",
typ: "felanmalan",
rubrik: "Measurement step will not accept a value from the pressure tester",
beskrivning:
"The check asks for a reading in bar. Entering 2,4 is rejected as invalid; entering 2.4 is accepted. The workshop uses a comma.",
sammanhang: { metodik: "vibration", steg: "4", plattform: "ALVA 1.0" },
status: "atgardad",
skapad: "2026-08-04T09:12:00Z",
anmald_av: "Anna",
inlagg: [],
},
];
export default function Support() {
const skarpt = plattformAktiv();
const [arenden, setArenden] = useState<Supportarende[] | null>(null);
const [fel, setFel] = useState("");
useEffect(() => {
if (!skarpt) return;
let avbruten = false;
hamtaSupport()
.then((a) => !avbruten && setArenden(a))
.catch((orsak) => !avbruten && setFel(orsak instanceof Error ? orsak.message : String(orsak)));
return () => {
avbruten = true;
};
}, [skarpt]);
// Misslyckad hämtning visar felet, aldrig exemplet: data som ser äkta
// ut men inte är det är värre än ett synligt fel.
const lista = skarpt ? arenden : EXEMPEL;
return (
<Ram portal>
<div className="mx-auto max-w-[1040px] px-6 py-12">
<Etikett>Support</Etikett>
<div className="mt-2 mb-2">
<Rubrik niva={1}>Reported issues</Rubrik>
</div>
<p className="mb-8 max-w-[680px] text-[14px] leading-[22px]" style={{ color: FARG.steel }}>
Every case carries a report button. A report filed from the case brings its own context
methodology, step, platform version, trace id so the first reply can be an answer rather
than a question.
</p>
{!skarpt && (
<Demonstration>
The entry below is an example. Connected to a platform instance this page lists the
organization&rsquo;s own reports.
</Demonstration>
)}
<Block rubrik="Reports" beteckning="ALVA-PROC-0050">
{fel ? (
<p className="text-[13px]" style={{ color: FARG.steel }}>
Reports could not be retrieved: {fel}
</p>
) : !lista ? (
<p className="text-[13px]" style={{ color: FARG.steel }}>
Retrieving reports.
</p>
) : lista.length === 0 ? (
<p className="text-[13px]" style={{ color: FARG.steel }}>
Nothing has been reported.
</p>
) : (
<Tabell
kolumner={["Reference", "Kind", "Subject", "Case", "Status"]}
rader={lista.map((s) => [
<span key="b" className="font-mono text-[12px]">
{s.beteckning}
</span>,
<span key="t" className="text-[12px]" style={{ color: FARG.steel }}>
{(TYPER as { id: string; rubrik: string }[]).find((t) => t.id === s.typ)?.rubrik ?? s.typ}
</span>,
<span key="r">
<span className="font-semibold">{s.rubrik}</span>
<span className="mt-2 block text-[12px] leading-[19px]" style={{ color: FARG.steel }}>
{Object.entries(s.sammanhang ?? {})
.map(([n, v]) => `${n}: ${v}`)
.join(" · ")}
</span>
</span>,
<span key="a" className="font-mono text-[11px]" style={{ color: FARG.steel }}>
{s.arende_id ?? "—"}
</span>,
<span key="s" className="flex flex-wrap items-center gap-2">
<Statusmärke status={STATUSAV[s.status] ?? "pending"} />
<span className="text-[11px] uppercase tracking-[0.08em]" style={{ color: FARG.steel }}>
{STATUSTEXT[s.status] ?? s.status}
</span>
</span>,
])}
/>
)}
</Block>
<Block rubrik="States" beteckning="ALVA-PROC-0050">
<p className="mb-4 max-w-[680px] text-[13px] leading-[21px]" style={{ color: FARG.steel }}>
A report is never edited. Replies and state changes are separate entries, and the state is
derived from them so the record can still answer how long a fault was known about.
</p>
<Tabell
kolumner={["State", "Meaning"]}
rader={(STATUSAR as string[]).map((s) => [
<span key="s" className="font-semibold">
{STATUSTEXT[s] ?? s}
</span>,
<span key="m" className="text-[12px]" style={{ color: FARG.steel }}>
{s === "mottagen"
? "Filed. This is the state a report has the moment it exists — nothing has to be written for it to be true."
: s === "under_arbete"
? "Support has taken it on."
: s === "atgardad"
? "Fixed, and the fix is out."
: "Closed. A closed report can be reopened — which happens when the fault turns out not to be gone."}
</span>,
])}
/>
</Block>
</div>
</Ram>
);
}
@@ -2567,10 +2567,116 @@ function RapportFlik({
Genererad ur ärendets händelselogg · Observationer och mätvärden redovisas utan slutsatser som saknar stöd.
</p>
<Ritningsstampel arende={arende} />
<Felanmalan arende={arende} metodik={metodik ?? null} />
</>
);
}
/**
* Felanmälan — i ärendet, inte någon annanstans.
*
* En anmälningsknapp som bara finns i en portalvy nås aldrig av den som
* står med händerna i en bil. Den ligger därför i varje ärende, och tar
* med sitt sammanhang självt: ärende, metodik, öppet steg och
* plattformsversion. Teknikern minns inte plattformsversionen, och att
* fråga efter den är att lägga över vårt problem på verkstaden.
*
* Anmälan lagras aldrig i ärendets händelselogg. Loggen är protokollet
* över vad som gjordes med FORDONET; att ett fel i vårt system anmäldes
* hör inte dit, och skulle följa med in i en rapport som ska hålla i en
* tvist.
*/
function Felanmalan({ arende, metodik }: { arende: Arende; metodik: Metodik | null }) {
const [oppen, setOppen] = useState(false);
const [typ, setTyp] = useState("felanmalan");
const [rubrik, setRubrik] = useState("");
const [text, setText] = useState("");
const [utfall, setUtfall] = useState<{ ok?: string; fel?: string }>({});
const [skickar, setSkickar] = useState(false);
const skicka = async () => {
setSkickar(true);
setUtfall({});
try {
const { anmalSupport, plattformAktiv } = await import("@/felsokning/plattform");
if (!plattformAktiv()) {
setUtfall({ fel: "Ingen plattform är konfigurerad — anmälan kan inte skickas härifrån." });
return;
}
const { beteckning } = await anmalSupport({
typ,
rubrik,
beskrivning: text,
arendeId: arende.id,
sammanhang: {
metodikId: metodik?.id ?? "",
plattformsversion: PLATTFORMSVERSION,
klient: typeof navigator === "undefined" ? "" : navigator.userAgent,
},
});
setUtfall({ ok: beteckning });
setRubrik("");
setText("");
} catch (orsak) {
setUtfall({ fel: orsak instanceof Error ? orsak.message : String(orsak) });
} finally {
setSkickar(false);
}
};
if (!oppen) {
return (
<p className="mt-4 text-center">
<button
type="button"
onClick={() => setOppen(true)}
className="text-[12px] uppercase tracking-wide text-[#4A5560] underline"
>
Rapportera ett problem med systemet
</button>
</p>
);
}
return (
<Panel rubrik="Rapportera ett problem med systemet">
<p className="mb-2 text-[12px] text-[#707070]">
Ärende, metodik och plattformsversion följer med automatiskt. Inget om fordonet eller kunden
skickas.
</p>
<div className="mb-2 grid grid-cols-3 gap-2">
{[
["felanmalan", "Fel"],
["fraga", "Fråga"],
["forbattring", "Förbättring"],
].map(([id, etikett]) => (
<StorKnapp key={id} variant={typ === id ? "primar" : "sekundar"} onClick={() => setTyp(id)}>
{etikett}
</StorKnapp>
))}
</div>
<TextFalt label="Vad gäller det?" varde={rubrik} satt={setRubrik} platshallare="Kort rubrik" />
<TextFalt
label="Vad hände, och vad väntade du dig?"
varde={text}
satt={setText}
platshallare="Den som svarar ser inte din skärm."
flerRad
/>
{utfall.ok && <p className="mt-2 text-[13px] text-[#1E6B34]">Anmäld som {utfall.ok}.</p>}
{utfall.fel && <p className="mt-2 text-[13px] text-[#8B1A1A]">{utfall.fel}</p>}
<div className="mt-2 grid grid-cols-2 gap-2">
<StorKnapp onClick={skicka} disabled={skickar || rubrik.trim().length < 5 || text.trim().length < 20}>
{skickar ? "Skickar" : "Skicka anmälan"}
</StorKnapp>
<StorKnapp variant="sekundar" onClick={() => setOppen(false)}>
Stäng
</StorKnapp>
</div>
</Panel>
);
}
/**
* Ritningsstämpel.
*