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 { LoadingState } from "./ui";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
const Dashboard = lazyWithReload(() => import("./pages/Dashboard"));
const Odin = lazyWithReload(() => import("./pages/Odin"));
const Users = lazyWithReload(() => import("./pages/Users"));
const Attendance = lazyWithReload(() => import("./pages/Attendance"));

View File

@@ -253,7 +253,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { success: false, error: data.error };
} catch {
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);
return { success: false, error: errorMsg };
}
@@ -311,7 +311,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setError(data.error);
return { success: false, error: data.error };
} catch {
const errorMsg = "Chyba pripojeni.";
const errorMsg = "Chyba připojení.";
setError(errorMsg);
return { success: false, error: errorMsg };
}

View File

@@ -1,6 +1,6 @@
/**
* 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` +
* `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" },
v_realizaci: { label: "V realizaci", color: "warning" },
dokoncena: { label: "Dokončená", color: "success" },
stornovana: { label: "Stornována", color: "error" },
stornovana: { label: "Stornovaná", color: "error" },
};
/** ISSUED ORDER / objednávka vydaná (issued_orders). */
@@ -75,6 +75,17 @@ export const RECEIVED_INVOICE_STATUS: Record<string, StatusMeta> = {
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)
* 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");
} catch (e) {
alert.error(e instanceof Error ? e.message : "Chyba pripojeni");
alert.error(e instanceof Error ? e.message : "Chyba připojení");
} finally {
setPunching(false);
}

View File

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

View File

@@ -38,29 +38,20 @@ import {
type TabDef,
type DataColumn,
} from "../ui";
import {
PROJECT_STATUS,
statusLabel,
statusColor,
statusOptions,
} from "../lib/documentStatus";
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.
const STATUS_TABS: TabDef[] = [
{ value: "", label: "Všechny" },
...Object.entries(STATUS_LABELS).map(([value, label]) => ({ value, label })),
];
const STATUS_TABS: TabDef[] = statusOptions(PROJECT_STATUS, {
value: "",
label: "Všechny",
});
interface Project {
id: number;
@@ -316,8 +307,8 @@ export default function Projects() {
sortKey: "status",
render: (p) => (
<StatusChip
label={STATUS_LABELS[p.status] || p.status}
color={STATUS_COLORS[p.status] || "default"}
label={statusLabel(PROJECT_STATUS, p.status)}
color={statusColor(PROJECT_STATUS, p.status)}
/>
),
},
@@ -394,9 +385,10 @@ export default function Projects() {
const rowSx = (p: Project) => {
if (p.status === "dokonceny") {
return {
backgroundColor: "rgba(var(--mui-palette-info-mainChannel) / 0.12)",
backgroundColor: "rgba(var(--mui-palette-success-mainChannel) / 0.12)",
"&:hover": {
backgroundColor: "rgba(var(--mui-palette-info-mainChannel) / 0.18)",
backgroundColor:
"rgba(var(--mui-palette-success-mainChannel) / 0.18)",
},
};
}