// Arbetsledarvyn: organisationens alla ärenden med status och statistik. // Endast arbetsledare/admin (servern verifierar behörigheten). Ett ärende // kan hämtas till den här enheten — händelserna flätas ihop konfliktfritt // med eventuell lokal kopia. import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import type { Arende } from "@/felsokning/domain"; import { hamtaAnvandare, hamtaOversikt, plattformAktiv, plattformFetch, plattformKonto, type OversiktsRad, type PlattformAnvandare, } from "@/felsokning/plattform"; import { nyLoggPost } from "@/felsokning/domain"; import { flataIhop } from "@/felsokning/synk"; import { useFelsokning } from "@/felsokning/store"; import { formateraTid } from "@/felsokning/projektioner"; import { FelsokningSkal, Panel, StorKnapp } from "@/felsokning/ui"; import { tidDatum, tidKlockslag } from "@/felsokning/format"; // Omfördelning: arbetsledaren pekar ut ny ansvarig tekniker — loggas som // append-only-händelsen ansvarig_satt i ärendets logg (organisationsintern, // delas aldrig i kund-/partnervyer). function Omfordela({ rad, vidKlar }: { rad: OversiktsRad; vidKlar: () => void }) { const [oppen, setOppen] = useState(false); const [personer, setPersoner] = useState([]); const konto = plattformKonto(); if (!oppen) { return ( { setOppen(true); hamtaAnvandare().then(setPersoner).catch(() => setPersoner([])); }} > Omfördela ); } return (

Ny ansvarig tekniker

{personer.map((person) => ( ))}
); } export default function Oversikt() { const navigate = useNavigate(); const { arenden, laggInArende, sammanfoga } = useFelsokning(); const [rader, setRader] = useState(null); const [fel, setFel] = useState(""); const konto = plattformKonto(); const behorig = plattformAktiv() && (konto?.roll === "arbetsledare" || konto?.roll === "admin"); useEffect(() => { if (!behorig) return; hamtaOversikt() .then(setRader) .catch(() => setFel("Kunde inte hämta översikten — kontrollera inloggningen.")); }, [behorig]); if (!behorig) { return (

Översikten kräver arbetsledar- eller administratörsbehörighet på plattformen.

); } // Hämtar ärendets händelser och materialiserar det lokalt (flätning om // en lokal kopia redan finns), sedan öppnas arbetsytan. const oppna = async (rad: OversiktsRad) => { try { const res = await plattformFetch(`/api/arenden/${rad.id}/handelser`); if (!res.ok) throw new Error(`Fel ${res.status}`); const { handelser } = (await res.json()) as { handelser: { id: string; tidpunkt: string; anvandare: string; handelse: Arende["handelser"][number]["handelse"] }[]; }; const fjarr = handelser.map((h) => ({ ...h, tidpunkt: new Date(h.tidpunkt).toISOString() })); const lokal = arenden[rad.id]; if (lokal) { sammanfoga(rad.id, flataIhop(lokal.handelser, fjarr)); } else { laggInArende({ id: rad.id, nummer: rad.nummer, skapad: new Date(rad.skapad).toISOString(), delningskod: rad.delningskod ?? undefined, metodikId: rad.metodik_id ?? undefined, handelser: fjarr, }); } navigate(`/felsokning/arende/${rad.id}`); } catch { setFel("Kunde inte hämta ärendet."); } }; const pagaende = rader?.filter((r) => !r.avslutat) ?? []; const avslutade = rader?.filter((r) => r.avslutat) ?? []; const medelLedtidMs = avslutade.length > 0 ? avslutade.reduce( (summa, r) => summa + (r.forsta && r.senaste ? new Date(r.senaste).getTime() - new Date(r.forsta).getTime() : 0), 0, ) / avslutade.length : 0; return ( {konto?.organisation}} > {fel &&

{fel}

} {!rader && !fel &&

Hämtar …

} {rader && ( <>

{rader.length}

Totalt

{pagaende.length}

Pågående

{avslutade.length}

Avslutade

{avslutade.length > 0 && (

Genomsnittlig ledtid för avslutade: {formateraTid(medelLedtidMs)}

)}
{rader.length === 0 &&

Inga ärenden i organisationen ännu.

} {rader.map((rad) => (
Ärende #{rad.nummer} {rad.avslutat ? "Avslutat" : "Pågående"}

{rad.objekt ?? "Okänt objekt"}

{rad.felbeskrivning &&

”{rad.felbeskrivning}”

}

Ansvarig: {rad.ansvarig ?? rad.skapare ?? "—"} {` · ${rad.antal_handelser} händelser`} {rad.senaste ? ` · senast ${tidDatum(rad.senaste)} ${tidKlockslag(rad.senaste)}` : ""}

oppna(rad)}> Öppna ärendet {!rad.avslutat && hamtaOversikt().then(setRader)} />}
))} )}
); }