diff --git a/docs/MVP.md b/docs/MVP.md index bd3c3a4..dbec8fc 100644 --- a/docs/MVP.md +++ b/docs/MVP.md @@ -41,7 +41,7 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st - **Händelseloggen är enda sanningskällan.** `src/felsokning/domain.ts` definierar händelsetyperna; poster läggs endast till. - **Alla vyer är projektioner.** `src/felsokning/projektioner.ts` — brief, tidsfördelning, överlämningstext och kundrapport är rena funktioner av loggen och kan alltid regenereras. Testerna i `src/felsokning/__tests__/` låser detta. - **Metodikmotorn är deterministisk.** `src/felsokning/metodik.ts` — nästa steg härleds ur vad som redan dokumenterats. Det är här den framtida AI:n ansluter, utan att logg eller projektioner ändras. -- **Fabriksverktygs-UI.** `src/felsokning/ui.tsx` — hög kontrast, minst 56 px höga knappar, få val per skärm. +- **Industriellt verkstads-UI (ETKA-inspirerat).** `src/felsokning/ui.tsx` — plana ljusgrå ytor (#ECECEC/#F7F7F7), skarpa kanter, djup marinblå som primärfärg, tät typografi (11–15 px), rektangulära knappar (max 4 px radie), verktygsrad ~44 px. Ärendesidan har klassisk trekolumnslayout på skrivbord: navigationsträd (vyer + metodikstegens status) till vänster, arbetsyta i mitten, kontextpanel (teknisk information, tillförlitlighet, teknisk rekommendation) till höger; en kolumn med flikrad på smala skärmar. ## Medvetna avgränsningar diff --git a/src/felsokning/DelatArendeVy.tsx b/src/felsokning/DelatArendeVy.tsx index 78a38b5..e3a14ba 100644 --- a/src/felsokning/DelatArendeVy.tsx +++ b/src/felsokning/DelatArendeVy.tsx @@ -40,31 +40,31 @@ export function DelatArendeVy({ rubrik={b.objekt?.beskrivning ?? "Ärende"} hoger={ {avslutat ? "Avslutat" : "🟢 Felsökning pågår"} } > -

+

{notis}

{b.felbeskrivning && ( -

”{b.felbeskrivning}”

+

”{b.felbeskrivning}”

)} {b.utfordaKontroller.map((k, i) => ( -

✔ {k.text}

+

✔ {k.text}

))} - {!avslutat && pagaende &&

🔄 {pagaende}

} + {!avslutat && pagaende &&

🔄 {pagaende}

} {b.ejKontrollerat.slice(avslutat ? 0 : 1).map((e, i) => ( -

⏳ {e}

+

⏳ {e}

))}
@@ -73,8 +73,8 @@ export function DelatArendeVy({
{bilder.map((bild, i) => (
- {bild.beskrivning} -
{bild.beskrivning}
+ {bild.beskrivning} +
{bild.beskrivning}
))}
@@ -83,15 +83,15 @@ export function DelatArendeVy({ {matvarden.length > 0 && ( - +
{matvarden.map((p) => { const h = p.handelse; if (h.typ !== "matvarde") return null; return ( - - - + + @@ -105,8 +105,8 @@ export function DelatArendeVy({ {kundposter.map((post) => ( -

- {tidKlockslag(post.tidpunkt)}{" "} +

+ {tidKlockslag(post.tidpunkt)}{" "} {handelseRubrik(post)}

))} @@ -114,20 +114,20 @@ export function DelatArendeVy({ {!avslutat && pagaende && ( -

{pagaende}

+

{pagaende}

)} -

{b.totalArbetstid}

+

{b.totalArbetstid}

{tidsfordelningsRader(arende, nu).map((r) => ( -

+

{r.label}: {r.tid}

))}
-

+

Ärende #{arende.nummer} · startat {tidDatum(arende.skapad)} · genererad ur ärendets händelselogg

diff --git a/src/felsokning/Mikrofon.tsx b/src/felsokning/Mikrofon.tsx index 719ca62..693058d 100644 --- a/src/felsokning/Mikrofon.tsx +++ b/src/felsokning/Mikrofon.tsx @@ -47,16 +47,16 @@ export function MikrofonKnapp({ paText }: { paText: (text: string) => void }) { type="button" onClick={() => (lyssnar ? stoppa() : starta())} aria-label={lyssnar ? "Stoppa inspelning" : "Starta röstinmatning"} - className={`min-h-11 min-w-11 rounded-full border-2 px-3 text-lg font-bold transition-colors ${ + className={`min-h-8 min-w-11 rounded border px-3 text-[14px] font-semibold transition-colors ${ lyssnar - ? "animate-pulse border-red-500 bg-red-600 text-white" - : "border-zinc-600 bg-zinc-800 text-zinc-100 hover:border-amber-400" + ? "animate-pulse border-[#6E1414] bg-[#8B1A1A] text-white" + : "border-[#ADADAD] bg-white text-[#1A1A1A] hover:border-[#00437A]" }`} > {lyssnar ? "■" : "🎤"} {lyssnar && ( - + ● Inspelning pågår{interim ? ` — ”${interim}”` : "…"} )} diff --git a/src/felsokning/__tests__/installningar.test.ts b/src/felsokning/__tests__/installningar.test.ts index 55e4612..5aa20e0 100644 --- a/src/felsokning/__tests__/installningar.test.ts +++ b/src/felsokning/__tests__/installningar.test.ts @@ -17,10 +17,10 @@ describe("organisationsinställningar", () => { it("okända värden filtreras bort och ordningen följer standardlistan", () => { const inst = normalisera({ - objekttyper: ["Hydraulik", "Påhittad typ", "Fordon"], + objekttyper: ["Hydraulik", "Påhittad typ", "Personbil"], identifieringsmetoder: ["VIN"], }); - expect(inst.objekttyper).toEqual(["Fordon", "Hydraulik"]); + expect(inst.objekttyper).toEqual(["Personbil", "Hydraulik"]); expect(inst.identifieringsmetoder).toEqual(["VIN"]); }); diff --git a/src/felsokning/__tests__/projektioner.test.ts b/src/felsokning/__tests__/projektioner.test.ts index 42df53b..3a0e34c 100644 --- a/src/felsokning/__tests__/projektioner.test.ts +++ b/src/felsokning/__tests__/projektioner.test.ts @@ -18,7 +18,7 @@ function byggArende(poster: [string, Handelse][]): Arende { const OBJEKT: Handelse = { typ: "objekt_identifierat", objekt: { - typ: "Fordon", + typ: "Personbil", identifierare: "ABC123", identifieringsmetod: "Registreringsnummer", beskrivning: "Volvo XC60 D4 2019", diff --git a/src/felsokning/demo.ts b/src/felsokning/demo.ts index d4da003..9cfcc39 100644 --- a/src/felsokning/demo.ts +++ b/src/felsokning/demo.ts @@ -57,9 +57,9 @@ export function byggDemoArende(nummer: number, anvandare = "Anna"): Arende { post(0, { typ: "objekt_identifierat", objekt: { - typ: "Fordon", + typ: "Personbil", identifierare: "ABC123", - identifieringsmetod: "Registreringsnummer", + identifieringsmetod: "Regnr", beskrivning: "Volvo XC60 D4 2019", kund: "Anders Svensson", }, diff --git a/src/felsokning/domain.ts b/src/felsokning/domain.ts index 1eed0c2..8f20a7c 100644 --- a/src/felsokning/domain.ts +++ b/src/felsokning/domain.ts @@ -32,7 +32,7 @@ export const TIDKATEGORI_LABEL: Record = { export interface Objekt { typ: string; // t.ex. "Fordon", "Industrimaskin" identifierare: string; // reg.nr, VIN, serienummer … - identifieringsmetod: string; // "Registreringsnummer", "VIN", "Serienummer", "Manuell inmatning" + identifieringsmetod: string; // "Regnr", "VIN", "Serienummer", "Manuell inmatning" beskrivning: string; // t.ex. "Volvo XC60 D4 2019" kund?: string; } diff --git a/src/felsokning/installningar.ts b/src/felsokning/installningar.ts index c92451a..7c94439 100644 --- a/src/felsokning/installningar.ts +++ b/src/felsokning/installningar.ts @@ -7,7 +7,7 @@ import { plattformAktiv, plattformFetch, plattformToken } from "./plattform"; export const ALLA_OBJEKTTYPER = [ - "Fordon", + "Personbil", "Lastbil/Buss", "Entreprenadmaskin", "Industrimaskin", @@ -17,7 +17,7 @@ export const ALLA_OBJEKTTYPER = [ ]; export const ALLA_IDENTIFIERINGSMETODER = [ - "Registreringsnummer", + "Regnr", "VIN", "Serienummer", "Maskinnummer", diff --git a/src/felsokning/ui.tsx b/src/felsokning/ui.tsx index f8813ee..41419ee 100644 --- a/src/felsokning/ui.tsx +++ b/src/felsokning/ui.tsx @@ -1,6 +1,9 @@ // Delade UI-komponenter för Guidad Felsökning. -// Designfilosofi: professionellt fabriksverktyg — hög kontrast, stora -// träffytor (handskvänligt), få val per skärm, inga distraktioner. +// Designfilosofi: industriellt verkstadsverktyg i ETKA-tradition — +// plana ytor, skarpa kanter, tät informationslayout, dämpad palett +// (ljusgrå ytor, djup marinblå som primärfärg), inga dekorationer. +// Verktygsrad ~44 px, rektangulära knappar (max 4 px radie), kompakta +// formulär med vänsterställda etiketter. import { Link } from "react-router-dom"; import { useRef, type ReactNode } from "react"; @@ -8,33 +11,54 @@ import type { Tillforlitlighet } from "./domain"; import { TILLFORLITLIGHET_LABEL } from "./domain"; import { MikrofonKnapp } from "./Mikrofon"; +// Industriell palett (inspirerad av tyska verkstadssystem): +// bakgrund #ECECEC · paneler #F7F7F7 · kanter #C6C6C6 +// primär (djup blå) #00437A · markerad rad #D6E4F2 +// varning #9A6700 · fel #8B1A1A · ok #1E6B34 + +const TYPSNITT = "'Inter', 'IBM Plex Sans', 'Segoe UI', Roboto, system-ui, sans-serif"; + export function FelsokningSkal({ rubrik, tillbaka, hoger, + bred, children, }: { rubrik: string; tillbaka?: { till: string; text: string }; hoger?: ReactNode; + bred?: boolean; children: ReactNode; }) { + const maxBredd = bred ? "max-w-[1240px]" : "max-w-3xl"; return ( -
-
-
-
+
+ {/* Verktygsrad: låg, mörk marinblå — breadcrumb till vänster, + status till höger. Inget stort sidhuvud. */} +
+
+
{tillbaka && ( - - ← {tillbaka.text} - + <> + + ← {tillbaka.text} + + / + )} -

{rubrik}

+

{rubrik}

{hoger}
-
{children}
+
{children}
); } @@ -56,16 +80,16 @@ export function StorKnapp({ }) { const farg = variant === "primar" - ? "bg-amber-400 text-zinc-950 hover:bg-amber-300 active:bg-amber-500" + ? "border border-[#003561] bg-[#00437A] text-white hover:bg-[#005A9E] active:bg-[#003561]" : variant === "fara" - ? "bg-red-600 text-white hover:bg-red-500 active:bg-red-700" - : "bg-zinc-800 text-zinc-50 border-2 border-zinc-600 hover:bg-zinc-700 active:bg-zinc-600"; + ? "border border-[#6E1414] bg-[#8B1A1A] text-white hover:bg-[#A32020] active:bg-[#6E1414]" + : "border border-[#ADADAD] bg-white text-[#1A1A1A] hover:bg-[#EDF2F7] hover:border-[#00437A] active:bg-[#D6E4F2]"; return ( @@ -74,15 +98,19 @@ export function StorKnapp({ export function Panel({ rubrik, children }: { rubrik?: string; children: ReactNode }) { return ( -
- {rubrik &&

{rubrik}

} +
+ {rubrik && ( +

+ {rubrik} +

+ )} {children}
); } export function NivaBadge({ niva }: { niva: Tillforlitlighet }) { - return {TILLFORLITLIGHET_LABEL[niva]}; + return {TILLFORLITLIGHET_LABEL[niva]}; } export function TextFalt({ @@ -107,11 +135,11 @@ export function TextFalt({ const vardeRef = useRef(varde); vardeRef.current = varde; const klass = - "w-full rounded-lg border-2 border-zinc-600 bg-zinc-950 px-4 py-3 text-lg text-zinc-50 placeholder-zinc-500 focus:border-amber-400 focus:outline-none"; + "w-full rounded border border-[#ADADAD] bg-white px-2.5 py-1.5 text-[13px] text-[#1A1A1A] placeholder-[#9AA0A6] focus:border-[#00437A] focus:outline-none focus:ring-1 focus:ring-[#00437A]"; return ( -
} > -
{h.beskrivning} +
{h.beskrivning} {h.varde} {h.enhet ? ` ${h.enhet}` : ""}