Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ Django 6 + Django REST Framework — REST API pro všechny operace. Kód je rozd

**Python konvence:**
- Formátování: Black (`line-length = 100`)
- Typování: mypy — veškerý nový kód musí mít typové anotace, mypy nesmí hlásit chyby
- Dead code: vulture — nepoužívané symboly jsou chybou
- Typování: mypy — veškerý nový kód musí mít typové anotace, mypy nesmí hlásit chyby; pozor: `mypy.ini` má `exclude = tests`, E2E kroky tedy CI typově nehlídá (konvence pro ně platí dál)
- Dead code: vulture — nepoužívané symboly jsou chybou; vulture není zapojený v CI, spouští se ručně (bez whitelistu hlásí šum z migrací)
- Závislosti: Pipenv (`Pipfile` + `Pipfile.lock`) — nikdy `pip install` přímo

### Frontend
Expand All @@ -109,7 +109,8 @@ React 19 SPA v [frontend/src/](frontend/src/). Webpack dev server na portu 3000
- Routing: TanStack Router (`frontend/src/router.tsx`, URL konstanty v `frontend/src/APP_URLS.ts`)
- Server state: TanStack Query (React Query) — veškerá komunikace s API
- CSS: vanilla-extract (type-safe CSS-in-JS, soubory `*.css.ts`)
- UI: Reactstrap (Bootstrap 5 wrappery) + FontAwesome PRO ikony
- UI: Mantine 9 (`@mantine/core`, `form`, `hooks`, `notifications`, `spotlight`) + FontAwesome PRO ikony
- Dark mode: barevné schéma (světlý/tmavý/systém) přes Mantine, přepínač v navbaru; FOUC řeší init skript `admin/static/admin/color-scheme-init.js`
- Fuzzy search: Fuse.js
- Grafy: Recharts

Expand Down
32 changes: 32 additions & 0 deletions admin/static/admin/color-scheme-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Inicializace barevneho schematu jeste PRED prvnim vykreslenim stranky (zabrana FOUC
// u dark uzivatelu). Musi zustat maly synchronni ES5 skript bez modulu — bezi driv,
// nez se nacte React aplikace i Mantine.
//
// Semantika musi presne odpovidat Mantine (@mantine/core):
// - stejny localStorage klic "mantine-color-scheme" (pouziva ho localStorageColorSchemeManager),
// - "auto" se ridi systemovym nastavenim (prefers-color-scheme),
// - nastavuje se atribut data-mantine-color-scheme (na nej cili Mantine CSS)
// + inline style color-scheme (kvuli nativnim scrollbarum/form controls bez FOUC).
(function () {
try {
// povolene ulozene hodnoty — cokoliv jineho (poskozena/rucne prepsana hodnota
// v localStorage) musi spadnout na chovani "auto"
var KNOWN_SCHEMES = ["light", "dark", "auto"];

Check failure on line 14 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected var, use let or const instead.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnP&open=AZ7kEcFL8N9AxeIulhnP&pullRequest=384
var stored = window.localStorage.getItem("mantine-color-scheme");

Check warning on line 15 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnR&open=AZ7kEcFL8N9AxeIulhnR&pullRequest=384

Check failure on line 15 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected var, use let or const instead.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnQ&open=AZ7kEcFL8N9AxeIulhnQ&pullRequest=384
var scheme = KNOWN_SCHEMES.indexOf(stored) !== -1 ? stored : "auto";

Check warning on line 16 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnU&open=AZ7kEcFL8N9AxeIulhnU&pullRequest=384

Check warning on line 16 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnT&open=AZ7kEcFL8N9AxeIulhnT&pullRequest=384

Check failure on line 16 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected var, use let or const instead.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnS&open=AZ7kEcFL8N9AxeIulhnS&pullRequest=384

// "auto" = podle aktualniho systemoveho schematu
var resolved =

Check failure on line 19 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected var, use let or const instead.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnV&open=AZ7kEcFL8N9AxeIulhnV&pullRequest=384
scheme === "auto"
? window.matchMedia("(prefers-color-scheme: dark)").matches

Check warning on line 21 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnW&open=AZ7kEcFL8N9AxeIulhnW&pullRequest=384
? "dark"
: "light"

Check warning on line 23 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnX&open=AZ7kEcFL8N9AxeIulhnX&pullRequest=384
: scheme;

document.documentElement.setAttribute("data-mantine-color-scheme", resolved);

Check failure on line 26 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `.dataset` over `setAttribute(…)`.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnY&open=AZ7kEcFL8N9AxeIulhnY&pullRequest=384
document.documentElement.style.colorScheme = resolved;
} catch (e) {
// localStorage/matchMedia nemusi byt dostupne (private mode apod.) — ticha
// degradace, schema pak nastavi az Mantine po startu aplikace
}

Check warning on line 31 in admin/static/admin/color-scheme-init.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=rodlukas_UP-admin&issues=AZ7kEcFL8N9AxeIulhnZ&open=AZ7kEcFL8N9AxeIulhnZ&pullRequest=384
})();
15 changes: 13 additions & 2 deletions admin/static/admin/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,19 @@
}
}

/* Stejné hexy jako pozadí SPA — token `vars.bg.page` v frontend/src/theme/tokens.ts
(light #e9eef5, dark dark-9 #141414), aby pozadí při mountu Reactu neskočilo.
Tento soubor nemůže TS tokeny importovat, hodnoty drž v synchronu ručně. */
body {
background-color: #ecf0f5;
background-color: #e9eef5;
font-family: "Segoe UI", Arial, sans-serif;
text-align: center;
}

html[data-mantine-color-scheme="dark"] body {
background-color: #141414;
}

html[data-mantine-color-scheme="dark"] .loader:empty {
border-color: rgba(255, 255, 255, 0.1);
border-left-color: #f8fafc;
}
4 changes: 2 additions & 2 deletions admin/static/admin/site.webmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"theme_color": "#1f2b3c",
"background_color": "#1f2b3c",
"display": "standalone",
"start_url": "/?utm_source=a2hs"
}
13 changes: 12 additions & 1 deletion admin/templates/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="robots" content="noindex, nofollow"/>
<!-- Barva odpovida hornimu okraji tmaveho navbar gradientu v Main.css.ts (#1f2b3c -> #223247).
Navbar je fixne tmavy v obou motivech, takze i theme-color drzime jednotne, aby na mobilnim
Chrome / PWA nevznikal viditelny sev mezi status barem a navbarem.
Stejnou barvu (#1f2b3c) ma i background_color v site.webmanifest (JSON neumi komentare,
proto je rozhodnuti zdokumentovane zde): manifest je staticky a neumi reagovat na barevne
schema, tmavy splash ale sedi k theme_color/status baru i fixne tmavemu navbaru v obou
motivech. Drivejsi bila (#ffffff) zpusobovala u dark-mode PWA uzivatelu bily zablesk;
kratky tmavy splash u svetleho motivu je mensi zlo nez bily zablesk u tmaveho. -->
<meta name="theme-color" content="#1f2b3c"/>
<!-- Synchronni (blokujici) skript zamerne bez async/defer - barevne schema se musi
nastavit pred prvnim vykreslenim, jinak u dark uzivatelu blikne svetle pozadi. -->
<script src="{% static "admin/color-scheme-init.js" %}"></script>

<!-- FAVICONY -->
<link rel="apple-touch-icon" sizes="180x180" href="{% static "admin/apple-touch-icon.png" %}">
Expand All @@ -11,4 +23,3 @@
<link rel="manifest" href="{% static "admin/site.webmanifest" %}">
<link rel="mask-icon" href="{% static "admin/safari-pinned-tab.svg" %}" color="#6c757d">
<meta name="msapplication-TileColor" content="#6c757d"/>
<meta name="theme-color" content="#ffffff"/>
38 changes: 38 additions & 0 deletions docs/superpowers/specs/2026-05-05-darkmode-toggle-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Design: Dark mode toggle v nastavení

> **Odchylka implementace:** přepínač nakonec není `SegmentedControl` na stránce Nastavení,
> ale dropdown menu v navbaru (`ColorSchemeToggle.tsx`, `data-qa="color_scheme_toggle"`).
> Důvod: navbar je dostupný odkudkoliv v aplikaci, uživatel nemusí kvůli změně schématu
> navštěvovat Nastavení — lepší UX. Zbytek specifikace (hodnoty, ikony, localStorage přes
> Mantine) platí beze změny.

## Shrnutí

Přidání přepínače barevného schématu (světlý / tmavý / systém) na stránku Nastavení. Volba se automaticky ukládá do localStorage přes Mantine.

## Umístění

Nová karta "Vzhled aplikace" v `Settings.tsx`, vložená jako samostatný řádek pod stávajícím `SimpleGrid` (kurzy + stavy účasti), nad blokem s verzí aplikace (`footerBlock`).

## Komponenta

- `useMantineColorScheme()` z Mantine → `colorScheme`, `setColorScheme`
- `SegmentedControl` se třemi hodnotami:
- `"auto"` → "Systém"
- `"light"` → "Světlý"
- `"dark"` → "Tmavý"
- Ikony: FontAwesome (monitor / slunce / měsíc) z `@rodlukas/fontawesome-pro-solid-svg-icons`
- Layout: label "Barevné schéma" vlevo + `SegmentedControl` vpravo — stejný pattern jako `configRow` / `configRowLabel` / `configRowControl`

## localStorage

Mantine ukládá volbu automaticky do `localStorage['mantine-color-scheme']` — žádný vlastní kód pro ukládání není potřeba. Výchozí hodnota při prvním spuštění je `"auto"` (odpovídá systémovému nastavení), což odpovídá stávajícímu `defaultColorScheme="auto"` v `MantineProvider`.

## CSS

Nová třída `appearanceSection` v `Settings.css.ts` — stejný vizuální styl jako `footerBlock` (border, box-shadow, border-radius, background s `light-dark()`).

## Soubory ke změně

- `frontend/src/pages/Settings.tsx` — přidání sekce s `SegmentedControl`
- `frontend/src/pages/Settings.css.ts` — přidání stylu `appearanceSection`
200 changes: 200 additions & 0 deletions docs/superpowers/specs/2026-05-05-design-polish-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Design Polish Plan – ÚPadmin

Kompletní přehled problémů nalezených průchodem celé aplikace (light + dark mode, desktop + mobile). Skupiny jsou seřazeny podle priority.

---

## Skupina A — Dark mode bugs (hardcoded light-only barvy)

Tři konkrétní místa v kódu, kde se používají Mantine pastelové shade-1/2 barvy, které jsou navrženy pouze pro light mode. V dark mode jsou téměř neviditelné nebo vizuálně špatné.

### A1 – Bank.tsx: titulek zelený/červený (řádek 165)

**Soubor:** `frontend/src/components/Bank.tsx`

```tsx
// PROBLÉM:
bg={isLackOfMoney ? "red.1" : "green.1"}
// green.1 a red.1 jsou velmi světlé pastelové barvy = v dark mode text splývá s pozadím
```

**Fix:** Nahradit Mantine `bg` prop za `style={{ backgroundColor: "light-dark(...)" }}` nebo použít CSS třídu v `Bank.css.ts`.

```tsx
// v Bank.css.ts přidat:
export const bankTitleOk = style({
backgroundColor: "light-dark(var(--mantine-color-green-1), var(--mantine-color-green-9))",
})
export const bankTitleWarning = style({
backgroundColor: "light-dark(var(--mantine-color-red-1), var(--mantine-color-red-9))",
})
// + odpovídající text color pro obě varianty
```

### A2 – Bank.tsx: dnešní řádek transakce (řádek 103)

**Soubor:** `frontend/src/components/Bank.tsx`

```tsx
// PROBLÉM:
<Table.Tr key={id} bg={isToday(date) ? "yellow.1" : undefined}>
// yellow.1 = světle žlutá, v dark mode téměř neviditelná / nevhodná
```

**Fix:** Inline style nebo CSS třída s `light-dark(var(--mantine-color-yellow-1), var(--mantine-color-yellow-9))`.

### A3 – DashboardDay.tsx: záhlaví dnešního dne (řádek 133)

**Soubor:** `frontend/src/components/DashboardDay.tsx`

```tsx
// PROBLÉM:
bg={isToday(getDate()) ? "blue.2" : undefined}
// blue.2 je velmi světlá modrá = v dark mode příliš jasná/nevhodná
```

**Fix:** CSS třída s `light-dark(var(--mantine-color-blue-1), var(--mantine-color-blue-9))`, nebo přidat podmíněnou třídu v `DashboardDay.css.ts`:

```ts
export const dashboardDayDateToday = style({
backgroundColor: "light-dark(var(--mantine-color-indigo-1), var(--mantine-color-indigo-9))",
})
```

---

## Skupina B — Design: Zájemci (Applications) — záhlaví kurzů

**Soubory:** `frontend/src/pages/Applications.tsx`, `frontend/src/pages/Applications.css.ts`

**Problém:** Záhlaví sekce kurzu (`courseHeadingItem`) používá plnou barvu kurzu jako background celého pásu s tmavým overlay. Výsledek je vizuálně velmi těžký, "Bootstrap 3 accordion panel" styl, působí zastarale.

**Aktuální stav:**
```ts
courseHeadingItem: {
backgroundColor: `${applicationsVars.courseBackground} !important`,
backgroundImage: "linear-gradient(rgb(15 23 42 / 0.2), rgb(15 23 42 / 0.2))",
padding: "0.5rem 1rem",
}
// bílý text, barva kurzu jako plné pozadí celého pásu
```

**Navrhovaný fix:** Odlehčit záhlaví — místo plnobarevného pásu použít:
- Bílé/dark-7 pozadí (`light-dark(#fff, dark-7)`) se subtilní levou barevnou čarou (4px border-left v barvě kurzu)
- Kurz badge zůstane (component `CourseName`/`CourseCircle`), ale nebude dominovat celá šířka
- Text tmavý (ne bílý)

```ts
// nový styl courseHeadingItem:
export const courseHeadingItem = style({
display: "flex",
alignItems: "center",
gap: "0.6rem",
padding: "0.65rem 1rem",
backgroundColor: "light-dark(#f8fafc, var(--mantine-color-dark-6))",
borderBottom: "1px solid light-dark(#dbe3ed, var(--mantine-color-dark-4))",
borderLeft: `4px solid ${applicationsVars.courseBackground}`,
})
// courseHeading text: dark color, žádný textShadow
// courseHeadingBadge: upravit pro nový kontext
```

---

## Skupina C — Rozestupy a padding (drobné, ale viditelné nedostatky)

### C1 – Hlavní nadpisy stránek (Heading komponenta)

**Soubor:** `frontend/src/components/Heading.tsx`

`my="md"` je 16px (Mantine `md` = 16px). Nadpisy stránek (H1) mají příliš malý spodní rozestup od obsahu stránky — vizuálně se obsah tísní hned pod nadpis.

**Fix:** Změnit `my="md"` na `mt="md" mb="lg"` (bottom 24px) nebo nastavit na `mt={12} mb={20}`.

### C2 – Dashboard: H1 nadpisy bez vizuální separace od karet

**Soubor:** `frontend/src/pages/Dashboard.tsx`

"Dnešní lekce" a "Bankovní účet" jsou H1 nadpisy, ale obojí jsou na jedné stránce jako dva samostatné sloupce. Nadpisy jsou příliš velké (h1 = 1.375rem + 1.5vw = ~40px na 1400px) pro "sekční" nadpisy.

**Fix:** Snížit na `order={2}` nebo přidat vlastní CSS `fontSize` override pro tyto sekční nadpisy (ne globální h1).

### C3 – Diary stránka: záhlaví bez vizuální oddělení od obsahu

**Soubor:** `frontend/src/pages/Diary.tsx`, `frontend/src/pages/Diary.css.ts`

Záhlaví "Týden 4. 5. – 8. 5." je velké a centered, pak hned pod ním jsou karty dní bez padding-top. Přidat `mb` pod záhlaví nebo `mt` nad karty.

**Konkrétní:** přidat `marginBottom: "1rem"` na záhlaví diáře (nebo `gap` na kontejner).

### C4 – Statistics: sekce "Rozsah lekcí" — malý rozestup nad filter tlačítky

**Soubor:** `frontend/src/pages/Statistics.css.ts`

`sectionTightTop` má `marginTop: "0.2rem"` — příliš malý rozestup pod titulkem sekce.

**Fix:** Zvýšit na `0.5rem`.

---

## Skupina D — Komponenta Card a lekce: dark mode (medium priority)

### D1 – Card.css.ts: lectureFuture a lecturePrepaid barvy

**Soubor:** `frontend/src/pages/Card.css.ts`

```ts
lectureFuture: "light-dark(#fff8dd, var(--mantine-color-yellow-9))"
lecturePrepaid: "light-dark(#ddf6e4, var(--mantine-color-green-9))"
```

`yellow-9` a `green-9` jsou v dark mode velmi tmavé (tmavě hnědá/tmavě zelená). Lépe by seděly `yellow-8` a `green-8`, nebo vlastní tmavší pastelové barvy.

**Fix:**
```ts
lectureFuture: "light-dark(#fff8dd, color-mix(in srgb, var(--mantine-color-yellow-9) 60%, var(--mantine-color-dark-7) 40%))"
```
Nebo jednoduše: `var(--mantine-color-yellow-8)` / `var(--mantine-color-green-8)`.

### D2 – DashboardDay / Card courseHeadingItem

**Soubor:** `frontend/src/pages/Card.css.ts`

Stejný pattern jako Zájemci (`courseHeadingItem` s plnou barvou kurzu). Na klientské kartě to slouží jako záhlaví každé lekce — zde je to vhodné (identifikuje kurz barvou), ale dark mode overlay může být příliš tmavý.

**Fix:** Snížit dark overlay intenzitu:
```ts
backgroundImage: "linear-gradient(rgb(15 23 42 / 0.15), rgb(15 23 42 / 0.15))"
// místo 0.28 / 0.2
```

---

## Skupina E — Drobná polish

### E1 – Bank title: text color v dark mode

Při opravě A1 zajistit, že text titulku banky (`bankTitleText`) má správný kontrast pro obě varianty (zelená/červená, light/dark).

### E2 – Odhlásit button v light mode

Tlačítko "Odhlásit" v navbaru má v light mode `variant` který ho renderuje jako šedý/outlined button. Vizuálně vypadá lehce inconsistentně vedle tmavého navbaru. Zkontrolovat variantu a případně použít `variant="light"` s explicitním `color="gray"` pro lepší kontrast na tmavém pozadí navbaru (navbar je vždy tmavý).

**Soubor:** `frontend/src/components/Menu.tsx` — zkontrolovat `Odhlásit` button props.

### E3 – PrepaidCounters dark mode

Rychle ověřit, zda `PrepaidCounters` komponenta (zobrazuje předplacené lekce) má správné barvy v dark mode.

---

## Souhrnná priorita implementace

| # | Problém | Soubory | Effort |
|---|---------|---------|--------|
| 1 | A1–A3: dark mode barvy (Bank + DashboardDay) | Bank.tsx, Bank.css.ts, DashboardDay.tsx, DashboardDay.css.ts | nízký |
| 2 | B: Zájemci headers redesign | Applications.tsx, Applications.css.ts | střední |
| 3 | C1–C4: Rozestupy | Heading.tsx, Dashboard.tsx, Diary.css.ts, Statistics.css.ts | nízký |
| 4 | D1: lectureFuture/Prepaid dark mode | Card.css.ts | nízký |
| 5 | D2: courseHeadingItem overlay | Card.css.ts, DashboardDay.css.ts | nízký |
| 6 | E1–E3: Drobná polish | Bank.css.ts, Menu.tsx | nízký |
Loading
Loading