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:
@@ -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"));
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"—"
|
"—"
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user