Add user stats page and refactor user navigation; replace modal with dedicated page for user statistics, enhancing routing and UI consistency. Update localization for new status keys in both English and Russian.

This commit is contained in:
2025-12-10 11:11:17 +03:00
parent 06bcb6ee51
commit 7323e80dcb
7 changed files with 335 additions and 184 deletions

116
CLAUDE.MD Normal file
View File

@@ -0,0 +1,116 @@
## Overview
This document summarizes the recent changes around submissions/users pages and records guardrails to avoid similar issues in the future.
We:
- Reworked submissions and user stats UIs to use real routes/pages instead of modals.
- Added compact progress overview for participants.
- Introduced deep-linked details pages for submissions and users.
- Fixed Chakra UI dialog misuse and type/translation issues.
## Routing & Page Structure
### Do
- **Define all routes centrally** in `src/__data__/urls.ts` and `src/dashboard.ts`:
- Add both the **URL builder** (e.g. `submissionDetails(userId, submissionId)`) and the **`:param` path**.
- Wrap pages in `PageWrapper` in `dashboard.tsx`.
- **Use real pages for complex views** (details, stats) instead of large modals:
- Submissions details: `SubmissionDetailsPage` with URL `/submissions/:userId/:submissionId`.
- User stats: `UserStatsPage` with URL `/users/:userId`.
- **Pass IDs via URL**, not only component state:
- Use route params for `userId`, `submissionId`, etc.
- For “return and keep selection”, encode it as a query param (e.g. `?userId=...`) and read it on the list page.
### Dont
- **Dont hardcode paths in components** (e.g. `'/submissions/...'`); always use `URLs.*` helpers.
- **Dont rely solely on local React state for deep links**:
- If a view must be shareable/bookmarkable or restorable on reload, it must be addressable by URL.
## Chakra UI Dialogs & Layout
### Do
- **Use dialog subcomponents only inside a dialog root**:
- If you use `DialogBody`, `DialogContent`, etc., they must be wrapped in `<DialogRoot>`.
- For **standalone pages**, use plain layout components:
- `Box`, `Heading`, `VStack`, `Grid`, `Progress`, etc.
- No `Dialog*` components on normal routed pages.
### Dont
- **Dont import or use `DialogBody`, `DialogContent`, `DialogHeader`, etc. on regular pages**:
- This causes `useDialogStyles returned 'undefined'` runtime errors.
- **Dont mix modal patterns and page patterns**:
- Either a true modal (`DialogRoot` + `DialogContent`) over an existing page,
- Or a full page route with normal layout — not both at the same time.
## Data Safety & Types
### Do
- Assume backend fields can be **either object or ID string**, per `ChallengeSubmission` types:
- Example safe access in submissions:
- Guard before reading `user.nickname` or `task.title`.
- Derive strings like:
- `const nickname = typeof rawUser === 'object' && 'nickname' in rawUser ? rawUser.nickname ?? '' : typeof rawUser === 'string' ? rawUser : ''`.
- Normalize strings before calling `.toLowerCase()`:
- `const normalized = (value ?? '').toLowerCase()`.
- When filtering/searching, **never call string methods on possibly `undefined` or non-object values**.
### Dont
- **Dont cast blindly** (`as ChallengeUser`) and then access `.nickname` or `.title` without guards.
- **Dont call `.toLowerCase()` directly on untrusted values** from API or union-typed fields.
## “Back” Navigation & State Restoration
### Do
- For **details pages that should restore list state**:
- Encode the necessary selection into the URL when navigating _to_ details.
- Example: `SubmissionDetailsPage` returns to `URLs.submissions` with `?userId=...`, and `SubmissionsPage` reads `userId` from `useSearchParams` to preselect the user.
- Prefer **semantic back actions** over bare `navigate(-1)` when the previous page/state is known:
- Use `navigate(URLs.submissions + '?userId=...')` or `navigate(URLs.users)` when appropriate.
### Dont
- **Dont rely on `navigate(-1)`** when:
- The previous page might not be the canonical list page,
- You need a specific state (e.g. selected user) restored.
## i18n / Locales
### Do
- **Keep `ru.json` and `en.json` in sync** for any new keys:
- When adding a key under `challenge.admin.*` in one file, add the corresponding entry in the other.
- For **status enums**, ensure all possible values have translations:
- `challenge.admin.users.stats.status.*` must cover all values of `taskStat.status`.
- `challenge.admin.submissions.status.*` must cover all submission statuses.
- Use **consistent key naming patterns**:
- Example: `challenge.admin.users.stats.status.accepted`, `...status.needs_revision`, etc.
### Dont
- **Dont introduce new `t('...')` keys in code without adding them to both locale files**.
- **Dont reuse unrelated keys** just to avoid adding translations — create clear, specific keys.
## UI Patterns for High-Density Overviews
### Do
- For high-density screens (e.g. 100 participants at once):
- Use **compact cards or rows** with:
- Truncated names (`truncate`),
- Thin `Progress` bars,
- Minimal text (percentage + small counters).
- Sort by progress to surface lagging participants.
### Dont
- **Dont use wide tables** when many rows must fit on one screen; prefer grids or narrow rows with fixed-width text columns and flexible progress area.
## When Adding New Features
Before merging:
- **Check routing**:
- New URL added to `URLs`.
- Route wired in `dashboard.tsx`.
- **Check data safety**:
- No unchecked property access on union/nullable types.
- **Check i18n**:
- New keys exist in both `ru.json` and `en.json`.
- **Check Chakra usage**:
- No `Dialog*` components outside a proper `<DialogRoot>` _or_ on standalone pages.