feat(ui): sidebar icon refresh + unified detail-page headers
Sidebar (user-approved design, docs/superpowers/specs/2026-06-12-sidebar- icons-design.md): all 31 menu items get unique contextual stroke glyphs in rounded-square tiles tinted per section (Docházka blue, Kniha jízd teal, Administrativa amber, Sklad violet, Systém slate; new teal/violet/slate palette entries with channel tokens in both schemes). Replaces the old set where 13 items shared a glyph (3x people, 3x sliders, ...) and Faktury was a dollar sign. Odin stays the only solid + animated tile. Uniqueness pinned by navdata-icons.test.ts (tsconfig.test.json gains jsx for the .tsx import). Headers: ProjectDetail and OrderDetail now match the Offer/Invoice shell — number rendered in secondary color at regular weight next to the bold entity word, and flexWrap on both left header boxes so the title drops below Zpět on phones instead of overflowing the viewport (the reported mobile bug on received orders). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
52
docs/superpowers/specs/2026-06-12-sidebar-icons-design.md
Normal file
52
docs/superpowers/specs/2026-06-12-sidebar-icons-design.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Sidebar icon refresh — design
|
||||||
|
|
||||||
|
**Date:** 2026-06-12 · **Status:** approved (visual companion session, 3 screens)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
All 31 sidebar items have icons, but 13 share a glyph with another item
|
||||||
|
(3× "people", 3× "sliders", 2× grid/bar-chart/box/history-clock) and Faktury
|
||||||
|
uses a dollar sign in an app that bills in Kč/EUR. Duplicated glyphs defeat
|
||||||
|
the purpose of icons as unique visual anchors.
|
||||||
|
|
||||||
|
## Decisions (user-approved)
|
||||||
|
|
||||||
|
1. **Full refresh** of all items (option B over targeted fix).
|
||||||
|
2. **Tile language (option B of 3 rendered directions):** every item's glyph
|
||||||
|
sits in a rounded-square tile (border-radius 32%, echoing OdinMark), as a
|
||||||
|
**soft color wash — one muted hue per menu section**:
|
||||||
|
Přehled `primary` (red) · Docházka `info` (blue) · Kniha jízd `teal` ·
|
||||||
|
Administrativa `warning` (amber) · Sklad `violet` · Systém `slate`.
|
||||||
|
3. **Odin stays unique:** the only **solid** (gradient) tile and the only
|
||||||
|
animated icon. No animation anywhere else. Red stays reserved for
|
||||||
|
Odin/primary.
|
||||||
|
4. **Glyph set** approved on the full-set screen: receipt for Faktury,
|
||||||
|
business card for Zákazníci, truck for Dodavatelé, stamp for Schvalování,
|
||||||
|
ledger ± for Správa bilancí, route/pin/car for Kniha jízd, barcode for
|
||||||
|
Inventura, shelf rack for Lokace, arrows into/out of a box for
|
||||||
|
Příjmy/Výdeje, warehouse hall for Sklad přehled, tag/cart/folder for
|
||||||
|
Nabídky/Objednávky/Projekty, gauge for Přehled, paper plane for Žádosti,
|
||||||
|
timesheet for Moje historie (docházka), shapes for Kategorie, chart frame
|
||||||
|
for Reporty, magnifier-over-list for Audit log. Kept concepts (now unique
|
||||||
|
in the set): clock (Záznam docházky), calendar (Plán prací), people
|
||||||
|
(Uživatelé), gear (Nastavení), box (Položky).
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
- `theme.ts`: add `teal`, `violet`, `slate` palette entries (light + dark
|
||||||
|
mains, full main/light/dark/contrastText so cssVariables emits `*Channel`
|
||||||
|
tokens) + TS module augmentation.
|
||||||
|
- `navData.tsx`: `MenuSection.hue` (palette key), new glyph SVGs
|
||||||
|
(stroke, 24-viewBox, width 2, round caps), `MenuItem.rawIcon` flag for
|
||||||
|
Odin (its icon ships its own tile).
|
||||||
|
- `SidebarNav.tsx`: renders the tile (26 px, radius 32%, `bgcolor:
|
||||||
|
rgba(<hue>.mainChannel / 0.12)`, glyph color `<hue>.main`, svg 16 px)
|
||||||
|
around every non-`rawIcon` item. No hardcoded hex; washes are
|
||||||
|
channel-alpha so both schemes work.
|
||||||
|
- Pinning test `navdata-icons.test.ts`: every non-raw icon renders to unique
|
||||||
|
static markup (no two items may ever share a glyph again).
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Animation for non-Odin icons (explicitly rejected), page-level icons,
|
||||||
|
mobile drawer changes (it reuses SidebarNav).
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.31",
|
"version": "2.4.32",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.31",
|
"version": "2.4.32",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.102.0",
|
"@anthropic-ai/sdk": "^0.102.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.31",
|
"version": "2.4.32",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/server.js",
|
"main": "dist/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
51
src/__tests__/navdata-icons.test.ts
Normal file
51
src/__tests__/navdata-icons.test.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
|
import { isValidElement, type ReactElement } from "react";
|
||||||
|
import { menuSections, type MenuItem } from "../admin/ui/navData";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pins the sidebar icon-refresh contract (2026-06-12 design): every menu
|
||||||
|
* item has a glyph, and no two items share one — duplicated glyphs were the
|
||||||
|
* whole problem the refresh fixed (3× "people", 3× "sliders", …). Odin's
|
||||||
|
* rawIcon (OdinMark, MUI-styled + animated) is exempt from rendering here
|
||||||
|
* but still must exist and stay the ONLY rawIcon.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const allItems: Array<MenuItem & { section: string }> = menuSections.flatMap(
|
||||||
|
(s) => s.items.map((item) => ({ section: s.label, ...item })),
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("sidebar nav icons", () => {
|
||||||
|
it("every item has an icon and a section hue", () => {
|
||||||
|
for (const section of menuSections) {
|
||||||
|
expect(section.hue, `section ${section.label}`).toBeTruthy();
|
||||||
|
}
|
||||||
|
for (const item of allItems) {
|
||||||
|
expect(isValidElement(item.icon), `${item.section}/${item.label}`).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Odin is the only rawIcon (its own tile + animation stay unique)", () => {
|
||||||
|
const raw = allItems.filter((i) => i.rawIcon);
|
||||||
|
expect(raw.map((i) => i.label)).toEqual(["Odin"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("no two items share a glyph", () => {
|
||||||
|
const rendered = allItems
|
||||||
|
.filter((i) => !i.rawIcon)
|
||||||
|
.map((item) => ({
|
||||||
|
key: `${item.section}/${item.label}`,
|
||||||
|
markup: renderToStaticMarkup(item.icon as ReactElement),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const seen = new Map<string, string>();
|
||||||
|
for (const { key, markup } of rendered) {
|
||||||
|
const dupe = seen.get(markup);
|
||||||
|
expect(dupe, `${key} shares its glyph with ${dupe}`).toBeUndefined();
|
||||||
|
seen.set(markup, key);
|
||||||
|
}
|
||||||
|
expect(seen.size).toBe(rendered.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -389,7 +389,17 @@ export default function OrderDetail() {
|
|||||||
mb: 3,
|
mb: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
{/* flexWrap: the title must drop below the Zpět button on phones
|
||||||
|
instead of overflowing the viewport (same as Offer/Invoice
|
||||||
|
detail headers). */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 2,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
to="/orders"
|
to="/orders"
|
||||||
@@ -399,9 +409,22 @@ export default function OrderDetail() {
|
|||||||
>
|
>
|
||||||
Zpět
|
Zpět
|
||||||
</Button>
|
</Button>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1.5 }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1.5,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography variant="h4">
|
<Typography variant="h4">
|
||||||
Objednávka {order.order_number}
|
Objednávka
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: "text.secondary", ml: 1, fontWeight: 400 }}
|
||||||
|
>
|
||||||
|
{order.order_number}
|
||||||
|
</Box>
|
||||||
</Typography>
|
</Typography>
|
||||||
<StatusChip
|
<StatusChip
|
||||||
label={statusLabel(ORDER_STATUS, order.status)}
|
label={statusLabel(ORDER_STATUS, order.status)}
|
||||||
|
|||||||
@@ -298,7 +298,17 @@ export default function ProjectDetail() {
|
|||||||
mb: 3,
|
mb: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
{/* flexWrap: the title must drop below the Zpět button on phones
|
||||||
|
instead of being squeezed beside it (mirrors Offer/Invoice
|
||||||
|
detail headers). */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 2,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
to="/projects"
|
to="/projects"
|
||||||
@@ -308,9 +318,22 @@ export default function ProjectDetail() {
|
|||||||
>
|
>
|
||||||
Zpět
|
Zpět
|
||||||
</Button>
|
</Button>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1.5 }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1.5,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography variant="h4">
|
<Typography variant="h4">
|
||||||
Projekt {project.project_number}
|
Projekt
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: "text.secondary", ml: 1, fontWeight: 400 }}
|
||||||
|
>
|
||||||
|
{project.project_number}
|
||||||
|
</Box>
|
||||||
</Typography>
|
</Typography>
|
||||||
<StatusChip
|
<StatusChip
|
||||||
label={STATUS_LABELS[project.status] || project.status}
|
label={STATUS_LABELS[project.status] || project.status}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { createTheme, type Theme } from "@mui/material/styles";
|
import {
|
||||||
|
createTheme,
|
||||||
|
type Theme,
|
||||||
|
type PaletteColor,
|
||||||
|
type PaletteColorChannel,
|
||||||
|
} from "@mui/material/styles";
|
||||||
|
|
||||||
const FONT_BODY = "'Plus Jakarta Sans', system-ui, sans-serif";
|
const FONT_BODY = "'Plus Jakarta Sans', system-ui, sans-serif";
|
||||||
const FONT_HEADING = "'Urbanist', sans-serif";
|
const FONT_HEADING = "'Urbanist', sans-serif";
|
||||||
@@ -33,6 +38,26 @@ export const theme = createTheme({
|
|||||||
warning: { main: "#b45309", contrastText: "#fff" },
|
warning: { main: "#b45309", contrastText: "#fff" },
|
||||||
error: { main: "#b91c1c", contrastText: "#fff" },
|
error: { main: "#b91c1c", contrastText: "#fff" },
|
||||||
info: { main: "#1d4ed8", contrastText: "#fff" },
|
info: { main: "#1d4ed8", contrastText: "#fff" },
|
||||||
|
// Sidebar section hues (full objects so cssVariables emits *Channel
|
||||||
|
// tokens — the nav tiles use channel-alpha washes).
|
||||||
|
teal: {
|
||||||
|
main: "#0e8a7c",
|
||||||
|
light: "#3aa99c",
|
||||||
|
dark: "#0a675d",
|
||||||
|
contrastText: "#fff",
|
||||||
|
},
|
||||||
|
violet: {
|
||||||
|
main: "#6a4cb4",
|
||||||
|
light: "#8a6fd0",
|
||||||
|
dark: "#54399a",
|
||||||
|
contrastText: "#fff",
|
||||||
|
},
|
||||||
|
slate: {
|
||||||
|
main: "#5c6470",
|
||||||
|
light: "#7d8694",
|
||||||
|
dark: "#454c56",
|
||||||
|
contrastText: "#fff",
|
||||||
|
},
|
||||||
background: { default: "#f4f3f1", paper: "#ffffff" },
|
background: { default: "#f4f3f1", paper: "#ffffff" },
|
||||||
text: { primary: "#1a1a1a", secondary: "#555555" },
|
text: { primary: "#1a1a1a", secondary: "#555555" },
|
||||||
divider: "rgba(0,0,0,0.1)",
|
divider: "rgba(0,0,0,0.1)",
|
||||||
@@ -46,6 +71,25 @@ export const theme = createTheme({
|
|||||||
warning: { main: "#f59e0b", contrastText: "#1a1a1a" },
|
warning: { main: "#f59e0b", contrastText: "#1a1a1a" },
|
||||||
error: { main: "#ef4444", contrastText: "#1a1a1a" },
|
error: { main: "#ef4444", contrastText: "#1a1a1a" },
|
||||||
info: { main: "#3b82f6", contrastText: "#1a1a1a" },
|
info: { main: "#3b82f6", contrastText: "#1a1a1a" },
|
||||||
|
// Sidebar section hues — brighter mains so the glyphs read on dark.
|
||||||
|
teal: {
|
||||||
|
main: "#2dd4bf",
|
||||||
|
light: "#5eead4",
|
||||||
|
dark: "#14b8a6",
|
||||||
|
contrastText: "#1a1a1a",
|
||||||
|
},
|
||||||
|
violet: {
|
||||||
|
main: "#a78bfa",
|
||||||
|
light: "#c4b5fd",
|
||||||
|
dark: "#8b5cf6",
|
||||||
|
contrastText: "#1a1a1a",
|
||||||
|
},
|
||||||
|
slate: {
|
||||||
|
main: "#94a3b8",
|
||||||
|
light: "#cbd5e1",
|
||||||
|
dark: "#64748b",
|
||||||
|
contrastText: "#1a1a1a",
|
||||||
|
},
|
||||||
background: { default: "#0f0f0f", paper: "#1a1a1a" },
|
background: { default: "#0f0f0f", paper: "#1a1a1a" },
|
||||||
text: { primary: "#ffffff", secondary: "#a0a0a0" },
|
text: { primary: "#ffffff", secondary: "#a0a0a0" },
|
||||||
divider: "rgba(255,255,255,0.08)",
|
divider: "rgba(255,255,255,0.08)",
|
||||||
@@ -239,6 +283,24 @@ export const fonts = {
|
|||||||
mono: FONT_MONO,
|
mono: FONT_MONO,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Custom sidebar-section palette entries (teal/violet/slate above). The
|
||||||
|
// channel intersection matters: theme.vars.palette.<hue>.mainChannel is how
|
||||||
|
// the nav tiles build their alpha washes.
|
||||||
|
type NavPaletteColor = PaletteColor & PaletteColorChannel;
|
||||||
|
|
||||||
|
declare module "@mui/material/styles" {
|
||||||
|
interface Palette {
|
||||||
|
teal: NavPaletteColor;
|
||||||
|
violet: NavPaletteColor;
|
||||||
|
slate: NavPaletteColor;
|
||||||
|
}
|
||||||
|
interface PaletteOptions {
|
||||||
|
teal?: PaletteOptions["primary"];
|
||||||
|
violet?: PaletteOptions["primary"];
|
||||||
|
slate?: PaletteOptions["primary"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type IconBadgeColor =
|
export type IconBadgeColor =
|
||||||
| "default"
|
| "default"
|
||||||
| "primary"
|
| "primary"
|
||||||
|
|||||||
@@ -88,9 +88,8 @@ export default function SidebarNav({ onNavigate, onLogout }: SidebarNavProps) {
|
|||||||
sx={{
|
sx={{
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
mb: 0.25,
|
mb: 0.25,
|
||||||
py: 0.6,
|
py: 0.45,
|
||||||
color: "text.secondary",
|
color: "text.secondary",
|
||||||
"& svg": { width: 18, height: 18 },
|
|
||||||
"&.Mui-selected, &.Mui-selected:hover": {
|
"&.Mui-selected, &.Mui-selected:hover": {
|
||||||
bgcolor: "background.paper",
|
bgcolor: "background.paper",
|
||||||
color: "primary.main",
|
color: "primary.main",
|
||||||
@@ -99,8 +98,29 @@ export default function SidebarNav({ onNavigate, onLogout }: SidebarNavProps) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon sx={{ minWidth: 32, color: "inherit" }}>
|
<ListItemIcon sx={{ minWidth: 36, color: "inherit" }}>
|
||||||
{item.icon}
|
{item.rawIcon ? (
|
||||||
|
item.icon
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={(t) => ({
|
||||||
|
width: 26,
|
||||||
|
height: 26,
|
||||||
|
// Echoes OdinMark's rounded-square tile; only Odin
|
||||||
|
// is solid + animated, these are quiet washes.
|
||||||
|
borderRadius: "32%",
|
||||||
|
flexShrink: 0,
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
bgcolor: `rgba(${t.vars!.palette[section.hue].mainChannel} / 0.12)`,
|
||||||
|
color: t.vars!.palette[section.hue].main,
|
||||||
|
"& svg": { width: 16, height: 16 },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={item.label}
|
primary={item.label}
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { type ReactNode } from "react";
|
import { type ReactNode } from "react";
|
||||||
import OdinMark from "../components/odin/OdinMark";
|
import OdinMark from "../components/odin/OdinMark";
|
||||||
|
|
||||||
|
/** Palette key driving a section's tile wash + glyph color (see theme.ts). */
|
||||||
|
export type NavHue =
|
||||||
|
| "primary"
|
||||||
|
| "info"
|
||||||
|
| "teal"
|
||||||
|
| "warning"
|
||||||
|
| "violet"
|
||||||
|
| "slate";
|
||||||
|
|
||||||
export interface MenuItem {
|
export interface MenuItem {
|
||||||
path: string;
|
path: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -10,33 +19,51 @@ export interface MenuItem {
|
|||||||
matchAlso?: string[];
|
matchAlso?: string[];
|
||||||
matchExclude?: string[];
|
matchExclude?: string[];
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
/** Icon ships its own container (OdinMark) — SidebarNav skips the tile. */
|
||||||
|
rawIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuSection {
|
export interface MenuSection {
|
||||||
label: string;
|
label: string;
|
||||||
|
hue: NavHue;
|
||||||
items: MenuItem[];
|
items: MenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One stroke glyph per item, every glyph unique within the set (pinned by
|
||||||
|
* navdata-icons.test.ts). 24-viewBox, stroke 2, round caps — rendered at
|
||||||
|
* 16px inside SidebarNav's 26px section-hue tile.
|
||||||
|
*/
|
||||||
|
function glyph(children: ReactNode) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const menuSections: MenuSection[] = [
|
export const menuSections: MenuSection[] = [
|
||||||
{
|
{
|
||||||
label: "Přehled",
|
label: "Přehled",
|
||||||
|
hue: "primary",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
label: "Přehled",
|
label: "Přehled",
|
||||||
end: true,
|
end: true,
|
||||||
icon: (
|
// tachometr — dashboard
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<circle cx="12" cy="13" r="8.5" />
|
||||||
stroke="currentColor"
|
<path d="M12 13l4-4.5" />
|
||||||
strokeWidth="2"
|
</>,
|
||||||
>
|
|
||||||
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
||||||
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
||||||
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
||||||
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -44,78 +71,66 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Odin",
|
label: "Odin",
|
||||||
permission: "ai.use",
|
permission: "ai.use",
|
||||||
end: true,
|
end: true,
|
||||||
icon: <OdinMark size={22} />,
|
icon: <OdinMark size={26} />,
|
||||||
|
rawIcon: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Docházka",
|
label: "Docházka",
|
||||||
|
hue: "info",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
path: "/attendance",
|
path: "/attendance",
|
||||||
label: "Záznam",
|
label: "Záznam",
|
||||||
permission: "attendance.record",
|
permission: "attendance.record",
|
||||||
end: true,
|
end: true,
|
||||||
icon: (
|
// píchačky — hodiny
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<circle cx="12" cy="12" r="9" />
|
<circle cx="12" cy="12" r="9" />
|
||||||
<polyline points="12 7 12 12 15 15" />
|
<polyline points="12 7 12 12 15 15" />
|
||||||
</svg>
|
</>,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/attendance/history",
|
path: "/attendance/history",
|
||||||
label: "Moje historie",
|
label: "Moje historie",
|
||||||
permission: "attendance.history",
|
permission: "attendance.history",
|
||||||
icon: (
|
// výkaz s hodinami — timesheet
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M14 3H5a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h6" />
|
||||||
stroke="currentColor"
|
<line x1="7" y1="8" x2="13" y2="8" />
|
||||||
strokeWidth="2"
|
<line x1="7" y1="12" x2="11" y2="12" />
|
||||||
>
|
<circle cx="16.5" cy="16" r="4.5" />
|
||||||
<polyline points="12 8 12 12 14 14" />
|
<polyline points="16.5 14 16.5 16 18 17" />
|
||||||
<path d="M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5" />
|
</>,
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/attendance/requests",
|
path: "/attendance/requests",
|
||||||
label: "Žádosti",
|
label: "Žádosti",
|
||||||
permission: "attendance.record",
|
permission: "attendance.record",
|
||||||
icon: (
|
// odeslaná žádost — vlaštovka
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M22 2L11 13" />
|
||||||
stroke="currentColor"
|
<path d="M22 2l-7 20-4-9-9-4z" />
|
||||||
strokeWidth="2"
|
</>,
|
||||||
>
|
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
||||||
<polyline points="14 2 14 8 20 8" />
|
|
||||||
<line x1="12" y1="18" x2="12" y2="12" />
|
|
||||||
<line x1="9" y1="15" x2="15" y2="15" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/attendance/approval",
|
path: "/attendance/approval",
|
||||||
label: "Schvalování",
|
label: "Schvalování",
|
||||||
permission: "attendance.approve",
|
permission: "attendance.approve",
|
||||||
icon: (
|
// razítko
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M9.5 12V9.8C9.5 8.4 8 7.8 8 6a4 4 0 0 1 8 0c0 1.8-1.5 2.4-1.5 3.8V12" />
|
||||||
stroke="currentColor"
|
<path d="M5 16a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v2H5z" />
|
||||||
strokeWidth="2"
|
<line x1="5" y1="21" x2="19" y2="21" />
|
||||||
>
|
</>,
|
||||||
<path d="M9 12l2 2 4-4" />
|
|
||||||
<circle cx="12" cy="12" r="10" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -124,147 +139,108 @@ export const menuSections: MenuSection[] = [
|
|||||||
permission: "attendance.manage",
|
permission: "attendance.manage",
|
||||||
matchPrefix: "/attendance/admin",
|
matchPrefix: "/attendance/admin",
|
||||||
matchAlso: ["/attendance/create", "/attendance/location"],
|
matchAlso: ["/attendance/create", "/attendance/location"],
|
||||||
icon: (
|
// osoba + fajfka
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<circle cx="9" cy="8" r="3.5" />
|
||||||
stroke="currentColor"
|
<path d="M3 20a6 6 0 0 1 12 0" />
|
||||||
strokeWidth="2"
|
<path d="M15.5 11.5l2 2 4-4" />
|
||||||
>
|
</>,
|
||||||
<line x1="4" y1="21" x2="4" y2="14" />
|
|
||||||
<line x1="4" y1="10" x2="4" y2="3" />
|
|
||||||
<line x1="12" y1="21" x2="12" y2="12" />
|
|
||||||
<line x1="12" y1="8" x2="12" y2="3" />
|
|
||||||
<line x1="20" y1="21" x2="20" y2="16" />
|
|
||||||
<line x1="20" y1="12" x2="20" y2="3" />
|
|
||||||
<line x1="1" y1="14" x2="7" y2="14" />
|
|
||||||
<line x1="9" y1="8" x2="15" y2="8" />
|
|
||||||
<line x1="17" y1="16" x2="23" y2="16" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/attendance/balances",
|
path: "/attendance/balances",
|
||||||
label: "Správa bilancí",
|
label: "Správa bilancí",
|
||||||
permission: "attendance.balances",
|
permission: "attendance.balances",
|
||||||
icon: (
|
// kniha plus/mínus — ruční korekce bilancí
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="4" y="3" width="16" height="18" rx="2" />
|
||||||
stroke="currentColor"
|
<line x1="9" y1="8.5" x2="15" y2="8.5" />
|
||||||
strokeWidth="2"
|
<line x1="12" y1="5.5" x2="12" y2="11.5" />
|
||||||
>
|
<line x1="9" y1="16" x2="15" y2="16" />
|
||||||
<line x1="18" y1="20" x2="18" y2="10" />
|
</>,
|
||||||
<line x1="12" y1="20" x2="12" y2="4" />
|
|
||||||
<line x1="6" y1="20" x2="6" y2="14" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/plan-work",
|
path: "/plan-work",
|
||||||
label: "Plán prací",
|
label: "Plán prací",
|
||||||
permission: ["attendance.manage", "attendance.record"],
|
permission: ["attendance.manage", "attendance.record"],
|
||||||
icon: (
|
// kalendář s plánovacím blokem
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="3" y="4" width="18" height="17" rx="2" />
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
|
||||||
<line x1="16" y1="2" x2="16" y2="6" />
|
<line x1="16" y1="2" x2="16" y2="6" />
|
||||||
<line x1="8" y1="2" x2="8" y2="6" />
|
<line x1="8" y1="2" x2="8" y2="6" />
|
||||||
<line x1="3" y1="10" x2="21" y2="10" />
|
<line x1="3" y1="9.5" x2="21" y2="9.5" />
|
||||||
</svg>
|
<path d="M8 14h3v3H8z" />
|
||||||
|
</>,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Kniha jízd",
|
label: "Kniha jízd",
|
||||||
|
hue: "teal",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
path: "/trips",
|
path: "/trips",
|
||||||
label: "Záznam",
|
label: "Záznam",
|
||||||
permission: "trips.record",
|
permission: "trips.record",
|
||||||
end: true,
|
end: true,
|
||||||
icon: (
|
// trasa A→B
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<circle cx="5" cy="19" r="2" />
|
||||||
stroke="currentColor"
|
<circle cx="19" cy="5" r="2" />
|
||||||
strokeWidth="2"
|
<path d="M5 17C5 11 19 13 19 7" />
|
||||||
>
|
</>,
|
||||||
<circle cx="5" cy="18" r="3" />
|
|
||||||
<circle cx="19" cy="18" r="3" />
|
|
||||||
<path d="M5 18V12L8 5h8l3 7v6" />
|
|
||||||
<path d="M10 18h4" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/trips/history",
|
path: "/trips/history",
|
||||||
label: "Moje historie",
|
label: "Moje historie",
|
||||||
permission: "trips.history",
|
permission: "trips.history",
|
||||||
icon: (
|
// navštívená místa — pin
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M12 21s-6-5.2-6-10a6 6 0 0 1 12 0c0 4.8-6 10-6 10z" />
|
||||||
stroke="currentColor"
|
<circle cx="12" cy="11" r="2.5" />
|
||||||
strokeWidth="2"
|
</>,
|
||||||
>
|
|
||||||
<polyline points="12 8 12 12 14 14" />
|
|
||||||
<path d="M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/trips/admin",
|
path: "/trips/admin",
|
||||||
label: "Správa",
|
label: "Správa",
|
||||||
permission: "trips.manage",
|
permission: "trips.manage",
|
||||||
icon: (
|
// schránka s fajfkou — kniha jízd
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="5" y="4" width="14" height="17" rx="2" />
|
||||||
stroke="currentColor"
|
<path d="M9 2h6v4H9z" />
|
||||||
strokeWidth="2"
|
<path d="M9 13l2 2 4-4" />
|
||||||
>
|
</>,
|
||||||
<line x1="4" y1="21" x2="4" y2="14" />
|
|
||||||
<line x1="4" y1="10" x2="4" y2="3" />
|
|
||||||
<line x1="12" y1="21" x2="12" y2="12" />
|
|
||||||
<line x1="12" y1="8" x2="12" y2="3" />
|
|
||||||
<line x1="20" y1="21" x2="20" y2="16" />
|
|
||||||
<line x1="20" y1="12" x2="20" y2="3" />
|
|
||||||
<line x1="1" y1="14" x2="7" y2="14" />
|
|
||||||
<line x1="9" y1="8" x2="15" y2="8" />
|
|
||||||
<line x1="17" y1="16" x2="23" y2="16" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/vehicles",
|
path: "/vehicles",
|
||||||
label: "Vozidla",
|
label: "Vozidla",
|
||||||
permission: "vehicles.manage",
|
permission: "vehicles.manage",
|
||||||
icon: (
|
// auto
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M5 17H3v-4l2-5h11l3 5h2v4h-2" />
|
||||||
stroke="currentColor"
|
<circle cx="7.5" cy="17" r="2" />
|
||||||
strokeWidth="2"
|
<circle cx="16.5" cy="17" r="2" />
|
||||||
>
|
<line x1="9.5" y1="17" x2="14.5" y2="17" />
|
||||||
<rect x="1" y="3" width="15" height="13" rx="2" />
|
</>,
|
||||||
<path d="M16 8h4l3 3v5h-7V8z" />
|
|
||||||
<circle cx="5.5" cy="18.5" r="2.5" />
|
|
||||||
<circle cx="18.5" cy="18.5" r="2.5" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Administrativa",
|
label: "Administrativa",
|
||||||
|
hue: "warning",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
path: "/offers",
|
path: "/offers",
|
||||||
@@ -272,16 +248,12 @@ export const menuSections: MenuSection[] = [
|
|||||||
permission: "offers.view",
|
permission: "offers.view",
|
||||||
matchPrefix: "/offers",
|
matchPrefix: "/offers",
|
||||||
matchExclude: ["/offers/customers", "/offers/templates"],
|
matchExclude: ["/offers/customers", "/offers/templates"],
|
||||||
icon: (
|
// cenovka
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M20.6 13.4L11 3H4v7l9.6 10.4a2 2 0 0 0 2.8 0l4.2-4.2a2 2 0 0 0 0-2.8z" />
|
||||||
stroke="currentColor"
|
<circle cx="8" cy="8" r="1.5" />
|
||||||
strokeWidth="2"
|
</>,
|
||||||
>
|
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
||||||
<polyline points="14 2 14 8 20 8" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -289,17 +261,13 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Objednávky",
|
label: "Objednávky",
|
||||||
permission: "orders.view",
|
permission: "orders.view",
|
||||||
matchPrefix: "/orders",
|
matchPrefix: "/orders",
|
||||||
icon: (
|
// košík
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<circle cx="9" cy="20" r="1.6" />
|
||||||
stroke="currentColor"
|
<circle cx="17" cy="20" r="1.6" />
|
||||||
strokeWidth="2"
|
<path d="M2 3h3l2.7 12h10.6l2.7-9H6" />
|
||||||
>
|
</>,
|
||||||
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z" />
|
|
||||||
<line x1="3" y1="6" x2="21" y2="6" />
|
|
||||||
<path d="M16 10a4 4 0 0 1-8 0" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -307,16 +275,13 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Faktury",
|
label: "Faktury",
|
||||||
permission: "invoices.view",
|
permission: "invoices.view",
|
||||||
matchPrefix: "/invoices",
|
matchPrefix: "/invoices",
|
||||||
icon: (
|
// účtenka
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M6 2h12v20l-3-2-3 2-3-2-3 2z" />
|
||||||
stroke="currentColor"
|
<line x1="9" y1="7" x2="15" y2="7" />
|
||||||
strokeWidth="2"
|
<line x1="9" y1="11" x2="15" y2="11" />
|
||||||
>
|
</>,
|
||||||
<line x1="12" y1="1" x2="12" y2="23" />
|
|
||||||
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -324,60 +289,44 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Projekty",
|
label: "Projekty",
|
||||||
permission: "projects.view",
|
permission: "projects.view",
|
||||||
matchPrefix: "/projects",
|
matchPrefix: "/projects",
|
||||||
icon: (
|
// složka (NAS projektové složky)
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<path d="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />,
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
|
||||||
<line x1="8" y1="21" x2="16" y2="21" />
|
|
||||||
<line x1="12" y1="17" x2="12" y2="21" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/offers/customers",
|
path: "/offers/customers",
|
||||||
label: "Zákazníci",
|
label: "Zákazníci",
|
||||||
permission: "customers.view",
|
permission: "customers.view",
|
||||||
icon: (
|
// vizitka
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||||
stroke="currentColor"
|
<circle cx="8.5" cy="10.5" r="2.2" />
|
||||||
strokeWidth="2"
|
<path d="M5 16.5a3.5 3.5 0 0 1 7 0" />
|
||||||
>
|
<line x1="14.5" y1="9.5" x2="19" y2="9.5" />
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
<line x1="14.5" y1="13.5" x2="19" y2="13.5" />
|
||||||
<circle cx="9" cy="7" r="4" />
|
</>,
|
||||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Sklad",
|
label: "Sklad",
|
||||||
|
hue: "violet",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
path: "/warehouse",
|
path: "/warehouse",
|
||||||
label: "Přehled",
|
label: "Přehled",
|
||||||
permission: "warehouse.view",
|
permission: "warehouse.view",
|
||||||
end: true,
|
end: true,
|
||||||
icon: (
|
// skladová hala
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M3 21V9l9-5 9 5v12" />
|
||||||
stroke="currentColor"
|
<path d="M7 21v-7h10v7" />
|
||||||
strokeWidth="2"
|
<line x1="7" y1="17.5" x2="17" y2="17.5" />
|
||||||
strokeLinecap="round"
|
</>,
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
||||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
|
||||||
<line x1="12" y1="22.08" x2="12" y2="12" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -385,17 +334,13 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Položky",
|
label: "Položky",
|
||||||
permission: "warehouse.view",
|
permission: "warehouse.view",
|
||||||
matchPrefix: "/warehouse/items",
|
matchPrefix: "/warehouse/items",
|
||||||
icon: (
|
// krabice
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
||||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
||||||
<line x1="12" y1="22.08" x2="12" y2="12" />
|
<line x1="12" y1="22.08" x2="12" y2="12" />
|
||||||
</svg>
|
</>,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -403,17 +348,13 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Příjmy",
|
label: "Příjmy",
|
||||||
permission: "warehouse.operate",
|
permission: "warehouse.operate",
|
||||||
matchPrefix: "/warehouse/receipts",
|
matchPrefix: "/warehouse/receipts",
|
||||||
icon: (
|
// šipka do krabice
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="4" y="12" width="16" height="9" rx="1" />
|
||||||
stroke="currentColor"
|
<polyline points="8 6 12 10 16 6" />
|
||||||
strokeWidth="2"
|
<line x1="12" y1="2" x2="12" y2="10" />
|
||||||
>
|
</>,
|
||||||
<polyline points="17 11 12 6 7 11" />
|
|
||||||
<line x1="12" y1="6" x2="12" y2="18" />
|
|
||||||
<path d="M5 19h14" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -421,17 +362,13 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Výdeje",
|
label: "Výdeje",
|
||||||
permission: "warehouse.operate",
|
permission: "warehouse.operate",
|
||||||
matchPrefix: "/warehouse/issues",
|
matchPrefix: "/warehouse/issues",
|
||||||
icon: (
|
// šipka z krabice ven
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="4" y="13" width="16" height="8" rx="1" />
|
||||||
stroke="currentColor"
|
<polyline points="8 6 12 2 16 6" />
|
||||||
strokeWidth="2"
|
<line x1="12" y1="2" x2="12" y2="11" />
|
||||||
>
|
</>,
|
||||||
<polyline points="7 13 12 18 17 13" />
|
|
||||||
<line x1="12" y1="18" x2="12" y2="6" />
|
|
||||||
<path d="M5 5h14" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -439,18 +376,12 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Rezervace",
|
label: "Rezervace",
|
||||||
permission: "warehouse.operate",
|
permission: "warehouse.operate",
|
||||||
matchPrefix: "/warehouse/reservations",
|
matchPrefix: "/warehouse/reservations",
|
||||||
icon: (
|
// krabice se záložkou
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="3" y="8" width="13" height="12" rx="1" />
|
||||||
stroke="currentColor"
|
<path d="M16 3h5v9l-2.5-2L16 12z" />
|
||||||
strokeWidth="2"
|
</>,
|
||||||
>
|
|
||||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
|
||||||
<circle cx="9" cy="7" r="4" />
|
|
||||||
<line x1="19" y1="8" x2="19" y2="14" />
|
|
||||||
<line x1="22" y1="11" x2="16" y2="11" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -458,16 +389,15 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Inventura",
|
label: "Inventura",
|
||||||
permission: "warehouse.inventory",
|
permission: "warehouse.inventory",
|
||||||
matchPrefix: "/warehouse/inventory",
|
matchPrefix: "/warehouse/inventory",
|
||||||
icon: (
|
// čárový kód — fyzický sken zásob
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<line x1="4" y1="6" x2="4" y2="18" />
|
||||||
stroke="currentColor"
|
<line x1="8" y1="6" x2="8" y2="18" />
|
||||||
strokeWidth="2"
|
<line x1="12" y1="6" x2="12" y2="18" />
|
||||||
>
|
<line x1="17" y1="6" x2="17" y2="18" />
|
||||||
<path d="M9 11l3 3L22 4" />
|
<line x1="20" y1="6" x2="20" y2="18" />
|
||||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
|
</>,
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -475,17 +405,12 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Reporty",
|
label: "Reporty",
|
||||||
permission: "warehouse.view",
|
permission: "warehouse.view",
|
||||||
end: true,
|
end: true,
|
||||||
icon: (
|
// graf v rámu
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||||
stroke="currentColor"
|
<polyline points="7 14 11 10 14 13 17 8" />
|
||||||
strokeWidth="2"
|
</>,
|
||||||
>
|
|
||||||
<line x1="18" y1="20" x2="18" y2="10" />
|
|
||||||
<line x1="12" y1="20" x2="12" y2="4" />
|
|
||||||
<line x1="6" y1="20" x2="6" y2="14" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -493,23 +418,14 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Kategorie",
|
label: "Kategorie",
|
||||||
permission: "warehouse.manage",
|
permission: "warehouse.manage",
|
||||||
matchPrefix: "/warehouse/categories",
|
matchPrefix: "/warehouse/categories",
|
||||||
icon: (
|
// tvary — třídění do kategorií
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<circle cx="7.5" cy="7.5" r="3.5" />
|
||||||
stroke="currentColor"
|
<rect x="13.5" y="4" width="7" height="7" rx="1" />
|
||||||
strokeWidth="2"
|
<path d="M7.5 13.5l3.5 7h-7z" />
|
||||||
>
|
<rect x="13.5" y="13.5" width="7" height="7" rx="3.5" />
|
||||||
<line x1="4" y1="21" x2="4" y2="14" />
|
</>,
|
||||||
<line x1="4" y1="10" x2="4" y2="3" />
|
|
||||||
<line x1="12" y1="21" x2="12" y2="12" />
|
|
||||||
<line x1="12" y1="8" x2="12" y2="3" />
|
|
||||||
<line x1="20" y1="21" x2="20" y2="16" />
|
|
||||||
<line x1="20" y1="12" x2="20" y2="3" />
|
|
||||||
<line x1="1" y1="14" x2="7" y2="14" />
|
|
||||||
<line x1="9" y1="8" x2="15" y2="8" />
|
|
||||||
<line x1="17" y1="16" x2="23" y2="16" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -517,18 +433,16 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Lokace",
|
label: "Lokace",
|
||||||
permission: "warehouse.manage",
|
permission: "warehouse.manage",
|
||||||
matchPrefix: "/warehouse/locations",
|
matchPrefix: "/warehouse/locations",
|
||||||
icon: (
|
// regál s krabicemi
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M3 21V4" />
|
||||||
stroke="currentColor"
|
<path d="M21 21V4" />
|
||||||
strokeWidth="2"
|
<line x1="3" y1="12" x2="21" y2="12" />
|
||||||
>
|
<line x1="3" y1="21" x2="21" y2="21" />
|
||||||
<rect x="3" y="3" width="7" height="7" rx="1" />
|
<rect x="7" y="8" width="4" height="4" />
|
||||||
<rect x="14" y="3" width="7" height="7" rx="1" />
|
<rect x="13" y="17" width="4" height="4" />
|
||||||
<rect x="3" y="14" width="7" height="7" rx="1" />
|
</>,
|
||||||
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -536,41 +450,34 @@ export const menuSections: MenuSection[] = [
|
|||||||
label: "Dodavatelé",
|
label: "Dodavatelé",
|
||||||
permission: "warehouse.manage",
|
permission: "warehouse.manage",
|
||||||
matchPrefix: "/warehouse/suppliers",
|
matchPrefix: "/warehouse/suppliers",
|
||||||
icon: (
|
// kamion
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<rect x="1" y="6" width="13" height="10" rx="1" />
|
||||||
stroke="currentColor"
|
<path d="M14 9h4l3 4v3h-7" />
|
||||||
strokeWidth="2"
|
<circle cx="6" cy="18.5" r="2" />
|
||||||
>
|
<circle cx="17.5" cy="18.5" r="2" />
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
</>,
|
||||||
<circle cx="9" cy="7" r="4" />
|
|
||||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Systém",
|
label: "Systém",
|
||||||
|
hue: "slate",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
path: "/users",
|
path: "/users",
|
||||||
label: "Uživatelé",
|
label: "Uživatelé",
|
||||||
permission: "users.view",
|
permission: "users.view",
|
||||||
icon: (
|
// lidé — uživatelské účty
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||||||
<circle cx="9" cy="7" r="4" />
|
<circle cx="9" cy="7" r="4" />
|
||||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||||
</svg>
|
</>,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -583,35 +490,27 @@ export const menuSections: MenuSection[] = [
|
|||||||
"settings.system",
|
"settings.system",
|
||||||
"settings.templates",
|
"settings.templates",
|
||||||
],
|
],
|
||||||
icon: (
|
// ozubené kolo
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<circle cx="12" cy="12" r="3" />
|
<circle cx="12" cy="12" r="3" />
|
||||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||||
</svg>
|
</>,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/audit-log",
|
path: "/audit-log",
|
||||||
label: "Audit log",
|
label: "Audit log",
|
||||||
permission: "settings.audit",
|
permission: "settings.audit",
|
||||||
icon: (
|
// lupa nad záznamy
|
||||||
<svg
|
icon: glyph(
|
||||||
viewBox="0 0 24 24"
|
<>
|
||||||
fill="none"
|
<path d="M4 5h12" />
|
||||||
stroke="currentColor"
|
<path d="M4 10h6" />
|
||||||
strokeWidth="2"
|
<path d="M4 15h5" />
|
||||||
>
|
<circle cx="15" cy="14" r="4.5" />
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
<line x1="18.2" y1="17.2" x2="21.5" y2="20.5" />
|
||||||
<polyline points="14 2 14 8 20 8" />
|
</>,
|
||||||
<line x1="16" y1="13" x2="8" y2="13" />
|
|
||||||
<line x1="16" y1="17" x2="8" y2="17" />
|
|
||||||
<polyline points="10 9 9 9 8 9" />
|
|
||||||
</svg>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": ["ES2022"],
|
"lib": ["ES2022"],
|
||||||
"types": ["node", "vitest/globals"],
|
"types": ["node", "vitest/globals"],
|
||||||
|
"jsx": "react-jsx",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user