117 lines
5.3 KiB
Markdown
117 lines
5.3 KiB
Markdown
## 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.
|
||
|
||
### Don’t
|
||
- **Don’t hardcode paths in components** (e.g. `'/submissions/...'`); always use `URLs.*` helpers.
|
||
- **Don’t 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.
|
||
|
||
### Don’t
|
||
- **Don’t import or use `DialogBody`, `DialogContent`, `DialogHeader`, etc. on regular pages**:
|
||
- This causes `useDialogStyles returned 'undefined'` runtime errors.
|
||
- **Don’t 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**.
|
||
|
||
### Don’t
|
||
- **Don’t cast blindly** (`as ChallengeUser`) and then access `.nickname` or `.title` without guards.
|
||
- **Don’t 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.
|
||
|
||
### Don’t
|
||
- **Don’t 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.
|
||
|
||
### Don’t
|
||
- **Don’t introduce new `t('...')` keys in code without adding them to both locale files**.
|
||
- **Don’t 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.
|
||
|
||
### Don’t
|
||
- **Don’t 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.
|
||
|
||
|