fix(ui): project status convention, linked-order chip, diacritics, lazy dashboard

- PROJECT_STATUS added to documentStatus.ts (aktivni=info, dokonceny=success,
  zruseny=error — app convention); Projects/ProjectDetail local maps deleted;
  status Select derives its MenuItems from the shared map.
- ProjectDetail linked-order status now rendered via the received-order map
  (was the project map with zero key overlap → raw DB token).
- Cancelled label unified across received/issued orders in documentStatus.ts.
- 'Chyba pripojeni' → 'Chyba připojení' (Dashboard, AuthContext).
- Dashboard lazy-loaded like every other route — its 7 subcomponents leave
  the Login critical path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-07-04 03:22:26 +02:00
parent 9c192e79e7
commit a274a49e90
6 changed files with 72 additions and 70 deletions

View File

@@ -12,8 +12,8 @@ import { lazyWithReload } from "./utils/lazyWithReload";
import MuiProvider from "./ui/MuiProvider"; import MuiProvider from "./ui/MuiProvider";
import { LoadingState } from "./ui"; import { LoadingState } from "./ui";
import Login from "./pages/Login"; import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
const Dashboard = lazyWithReload(() => import("./pages/Dashboard"));
const Odin = lazyWithReload(() => import("./pages/Odin")); const Odin = lazyWithReload(() => import("./pages/Odin"));
const Users = lazyWithReload(() => import("./pages/Users")); const Users = lazyWithReload(() => import("./pages/Users"));
const Attendance = lazyWithReload(() => import("./pages/Attendance")); const Attendance = lazyWithReload(() => import("./pages/Attendance"));

View File

@@ -253,7 +253,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { success: false, error: data.error }; return { success: false, error: data.error };
} catch { } catch {
const errorMsg = const errorMsg =
"Chyba pripojeni. Zkontrolujte prosim pripojeni k internetu a zkuste to znovu."; "Chyba připojení. Zkontrolujte prosím připojení k internetu a zkuste to znovu.";
setError(errorMsg); setError(errorMsg);
return { success: false, error: errorMsg }; return { success: false, error: errorMsg };
} }
@@ -311,7 +311,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setError(data.error); setError(data.error);
return { success: false, error: data.error }; return { success: false, error: data.error };
} catch { } catch {
const errorMsg = "Chyba pripojeni."; const errorMsg = "Chyba připojení.";
setError(errorMsg); setError(errorMsg);
return { success: false, error: errorMsg }; return { success: false, error: errorMsg };
} }

View File

@@ -1,6 +1,6 @@
/** /**
* Single source of truth for document status labels (Czech) + chip colors * Single source of truth for document status labels (Czech) + chip colors
* (MUI semantic) across the Offers / Invoices / Orders modules. * (MUI semantic) across the Offers / Invoices / Orders / Projects modules.
* *
* Previously every list AND detail page defined its own `STATUS_LABELS` + * Previously every list AND detail page defined its own `STATUS_LABELS` +
* `STATUS_COLORS` maps. They drifted — most notably the issued-invoice status * `STATUS_COLORS` maps. They drifted — most notably the issued-invoice status
@@ -46,7 +46,7 @@ export const ORDER_STATUS: Record<string, StatusMeta> = {
prijata: { label: "Přijatá", color: "info" }, prijata: { label: "Přijatá", color: "info" },
v_realizaci: { label: "V realizaci", color: "warning" }, v_realizaci: { label: "V realizaci", color: "warning" },
dokoncena: { label: "Dokončená", color: "success" }, dokoncena: { label: "Dokončená", color: "success" },
stornovana: { label: "Stornována", color: "error" }, stornovana: { label: "Stornovaná", color: "error" },
}; };
/** ISSUED ORDER / objednávka vydaná (issued_orders). */ /** ISSUED ORDER / objednávka vydaná (issued_orders). */
@@ -75,6 +75,17 @@ export const RECEIVED_INVOICE_STATUS: Record<string, StatusMeta> = {
paid: { label: "Uhrazena", color: "success" }, paid: { label: "Uhrazena", color: "success" },
}; };
/**
* PROJECT (projects). Czech-keyed like customer orders. Colors follow the
* app-wide convention: open/in-progress = info, done = success,
* cancelled = error.
*/
export const PROJECT_STATUS: Record<string, StatusMeta> = {
aktivni: { label: "Aktivní", color: "info" },
dokonceny: { label: "Dokončený", color: "success" },
zruseny: { label: "Zrušený", color: "error" },
};
/** /**
* Display label for a document's official number. Drafts (deferred numbering) * Display label for a document's official number. Drafts (deferred numbering)
* have a NULL/empty number until they are finalized — show "Koncept" instead * have a NULL/empty number until they are finalized — show "Koncept" instead

View File

@@ -124,7 +124,7 @@ export default function Dashboard() {
}); });
alert.success(result?.message || "Docházka zaznamenána"); alert.success(result?.message || "Docházka zaznamenána");
} catch (e) { } catch (e) {
alert.error(e instanceof Error ? e.message : "Chyba pripojeni"); alert.error(e instanceof Error ? e.message : "Chyba připojení");
} finally { } finally {
setPunching(false); setPunching(false);
} }

View File

@@ -31,24 +31,15 @@ import {
PageEnter, PageEnter,
headerActionsSx, headerActionsSx,
} from "../ui"; } from "../ui";
import {
PROJECT_STATUS,
ORDER_STATUS,
statusLabel,
statusColor,
} from "../lib/documentStatus";
const API_BASE = "/api/admin"; const API_BASE = "/api/admin";
const STATUS_LABELS: Record<string, string> = {
aktivni: "Aktivní",
dokonceny: "Dokončený",
zruseny: "Zrušený",
};
const STATUS_COLORS: Record<
string,
"default" | "success" | "error" | "warning" | "info"
> = {
aktivni: "success",
dokonceny: "info",
zruseny: "default",
};
interface User { interface User {
id: number; id: number;
name: string; name: string;
@@ -335,8 +326,8 @@ export default function ProjectDetail() {
</Box> </Box>
</Typography> </Typography>
<StatusChip <StatusChip
label={STATUS_LABELS[project.status] || project.status} label={statusLabel(PROJECT_STATUS, project.status)}
color={STATUS_COLORS[project.status] || "default"} color={statusColor(PROJECT_STATUS, project.status)}
/> />
</Box> </Box>
</Box> </Box>
@@ -418,9 +409,11 @@ export default function ProjectDetail() {
onChange={(v) => updateForm("status", v)} onChange={(v) => updateForm("status", v)}
disabled={!canEdit} disabled={!canEdit}
> >
<MenuItem value="aktivni">Aktivní</MenuItem> {Object.entries(PROJECT_STATUS).map(([value, s]) => (
<MenuItem value="dokonceny">Dokončený</MenuItem> <MenuItem key={value} value={value}>
<MenuItem value="zruseny">Zrušený</MenuItem> {s.label}
</MenuItem>
))}
</Select> </Select>
</Field> </Field>
<Field label="Datum zahájení"> <Field label="Datum zahájení">
@@ -556,30 +549,36 @@ export default function ProjectDetail() {
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
Objednávka Objednávka
</Typography> </Typography>
<Typography variant="body2"> <Typography
variant="body2"
component="div"
sx={{
display: "flex",
alignItems: "center",
gap: 1,
flexWrap: "wrap",
}}
>
{project.order_id ? ( {project.order_id ? (
<Box <>
component={RouterLink} <Box
to={`/orders/${project.order_id}`} component={RouterLink}
sx={{ to={`/orders/${project.order_id}`}
color: "primary.main", sx={{
textDecoration: "none", color: "primary.main",
"&:hover": { textDecoration: "underline" }, textDecoration: "none",
}} "&:hover": { textDecoration: "underline" },
> }}
{project.order_number} >
{project.order_number}
</Box>
{project.order_status && ( {project.order_status && (
<Box <StatusChip
component="span" label={statusLabel(ORDER_STATUS, project.order_status)}
sx={{ color: "text.secondary", ml: 1 }} color={statusColor(ORDER_STATUS, project.order_status)}
> />
(
{STATUS_LABELS[project.order_status] ||
project.order_status}
)
</Box>
)} )}
</Box> </>
) : ( ) : (
"—" "—"
)} )}

View File

@@ -38,29 +38,20 @@ import {
type TabDef, type TabDef,
type DataColumn, type DataColumn,
} from "../ui"; } from "../ui";
import {
PROJECT_STATUS,
statusLabel,
statusColor,
statusOptions,
} from "../lib/documentStatus";
const API_BASE = "/api/admin"; const API_BASE = "/api/admin";
const STATUS_LABELS: Record<string, string> = {
aktivni: "Aktivní",
dokonceny: "Dokončený",
zruseny: "Zrušený",
};
const STATUS_COLORS: Record<
string,
"default" | "success" | "error" | "warning" | "info"
> = {
aktivni: "success",
dokonceny: "info",
zruseny: "default",
};
// Status filter tabs (mirrors the offers page): "" = all. // Status filter tabs (mirrors the offers page): "" = all.
const STATUS_TABS: TabDef[] = [ const STATUS_TABS: TabDef[] = statusOptions(PROJECT_STATUS, {
{ value: "", label: "Všechny" }, value: "",
...Object.entries(STATUS_LABELS).map(([value, label]) => ({ value, label })), label: "Všechny",
]; });
interface Project { interface Project {
id: number; id: number;
@@ -316,8 +307,8 @@ export default function Projects() {
sortKey: "status", sortKey: "status",
render: (p) => ( render: (p) => (
<StatusChip <StatusChip
label={STATUS_LABELS[p.status] || p.status} label={statusLabel(PROJECT_STATUS, p.status)}
color={STATUS_COLORS[p.status] || "default"} color={statusColor(PROJECT_STATUS, p.status)}
/> />
), ),
}, },
@@ -394,9 +385,10 @@ export default function Projects() {
const rowSx = (p: Project) => { const rowSx = (p: Project) => {
if (p.status === "dokonceny") { if (p.status === "dokonceny") {
return { return {
backgroundColor: "rgba(var(--mui-palette-info-mainChannel) / 0.12)", backgroundColor: "rgba(var(--mui-palette-success-mainChannel) / 0.12)",
"&:hover": { "&:hover": {
backgroundColor: "rgba(var(--mui-palette-info-mainChannel) / 0.18)", backgroundColor:
"rgba(var(--mui-palette-success-mainChannel) / 0.18)",
}, },
}; };
} }