Skip to content

Unify admin page layouts#225

Merged
nemvince merged 6 commits into
mainfrom
unify-admin-page-layouts
May 30, 2026
Merged

Unify admin page layouts#225
nemvince merged 6 commits into
mainfrom
unify-admin-page-layouts

Conversation

@KoZsombat
Copy link
Copy Markdown
Contributor

@KoZsombat KoZsombat commented May 28, 2026

Summary by CodeRabbit

  • New Features

    • Reusable stat cards across admin dashboards (users, timetables, doorlock) and dashboard counts.
  • UI Improvements

    • Users/roles pages: live search, refresh, pagination, debounced router-driven search, and improved user edit dialog labels.
    • Timetable/substitutions: enhanced grids/badges and layout refinements.
    • Doorlock pages: device/card dashboards, actions, clearer empty/error states.
  • Localization

    • Expanded EN/HU translations: pagination, account/roles/users, substitutions (period), doorlock sections, and import wording (OMAN) plus new common.previous.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

Localizes admin UI (EN & HU), adds a reusable StatCard component, and updates admin pages—users, roles, doorlock, timetable, substitutions—with translated strings, stats cards, search/filter, refreshed layouts, and badge/grid displays.

Changes

Admin Panel Localization and Dashboard Enhancement

Layer / File(s) Summary
Translation catalog updates for admin UI
apps/iris/public/locales/en/translation.json, apps/iris/public/locales/hu/translation.json
English and Hungarian translation catalogs updated with keys for navigation (common.previous), timetable import wording (OMAN), timetable management labels (filter/refresh/counts), substitutions (period, notAvailable), users management, expanded roles labels, and doorlock sections (doorlockCards, doorlockDevices, doorlockDashboard).
Shared StatCard component
apps/iris/src/components/admin/stat-card.tsx
New reusable StatCard component that renders an icon, label, numeric value, and loading skeleton; used across admin pages.
User management localization and page wiring
apps/iris/src/components/admin/user-dialog.tsx, apps/iris/src/components/admin/users-table.tsx, apps/iris/src/routes/_private/admin/users.tsx
Localizes UserDialog and UsersTable labels/messages/buttons; users route now validates and drives page/search via router, debounces input navigation, uses a queryKey including page/search, provides refresh, and renders StatCards for pagination stats.
Role management with search and statistics
apps/iris/src/routes/_private/admin/roles.tsx
Adds client-side search state, memoized filteredRoles and derived stats, search/refresh/create controls, and stat cards with conditional loading/error rendering; passes filteredRoles to the table.
Doorlock admin pages localization and StatCard integration
apps/iris/src/routes/_private/admin/doorlock/cards.tsx, apps/iris/src/routes/_private/admin/doorlock/devices.tsx, apps/iris/src/routes/_private/admin/doorlock/index.tsx
Localizes titles, descriptions, table headers, buttons, toast messages, and status texts; replaces local StatCard implementations with the shared StatCard; updates imports/icons and error/loading handling; adds/uses computed stats and adjusts query invalidation on mutations.
Timetable management with statistics and improved layout
apps/iris/src/routes/_private/admin/timetable/manage.tsx, apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
Adds StatCard-based dashboard (total/current/upcoming), restructures header/layout, wraps timetable table in a horizontally scrollable bordered container; substitutions table renders lessons/cohorts as responsive badge grids and truncates teacher names.
Substitution lesson display with period label and badge styling
apps/iris/src/components/subs.tsx
Cohort badges variant changed to outline; lesson period display now appends period value and localized substitution.period label.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • filcdev/filc#218: Related substitution UI changes in apps/iris/src/components/subs.tsx.
  • filcdev/filc#187: Related timetable import dialog and schema/route handling used by the updated import strings.
  • filcdev/filc#219: Related admin timetable management refactors and timetables query/list behavior.

Suggested labels

feature, iris - web

Suggested reviewers

  • busyLambda
  • nemvince
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Unify admin page layouts' directly aligns with the main objective of the pull request, which consolidates admin UI patterns across multiple pages using shared components, consistent internationalization, and unified styling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot requested a review from busyLambda May 28, 2026 15:24
@KoZsombat KoZsombat linked an issue May 28, 2026 that may be closed by this pull request
@KoZsombat
Copy link
Copy Markdown
Contributor Author

@coderabbitai work

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

@KoZsombat: Sure! I'll start reviewing the PR right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@KoZsombat KoZsombat removed the request for review from busyLambda May 28, 2026 15:26
@coderabbitai coderabbitai Bot requested a review from busyLambda May 28, 2026 15:35
@coderabbitai coderabbitai Bot added feature New feature or request iris - web Work to be done on iris, the web labels May 28, 2026
@KoZsombat
Copy link
Copy Markdown
Contributor Author

@coderabbitai work

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

@KoZsombat: Sure! I'll start reviewing the PR right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/iris/src/routes/_private/admin/roles.tsx (1)

24-45: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Persist role search in route search params instead of local state.

The new filter is route-level UI state but it is stored only in component state, so it is not deep-linkable or restorable on reload.

As per coding guidelines, “Keep search-param-driven page state in TanStack Router when the page already uses it for filters or selection.”

Also applies to: 68-74

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/routes/_private/admin/roles.tsx` around lines 24 - 45, The
search term is stored in local state (search / setSearch) which prevents
deep-linking and restoring on reload; replace the local useState usage with a
route search-param backed value via your TanStack Router search API so the term
is read from and written to the URL instead of component state. Specifically,
remove the useState('') for search, read the initial term from the router search
params and update the router search param in your input change handler, keep the
same filteredRoles useMemo logic (still referencing allRoles and the term) and
ensure any other occurrences mentioned (the similar block at lines 68-74) are
migrated the same way; update dependencies and any unit/UI tests that asserted
local state to use the router-backed param.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/iris/src/components/admin/user-dialog.tsx`:
- Around line 63-75: The patch response handler in the mutation currently shows
an error toast when !res.ok but still returns res.json(), allowing React Query
to treat it as success and run onSuccess (which shows a success toast, calls
queryClient.invalidateQueries and onOpenChange). Change the handler in
user-dialog.tsx so that after awaiting res.json() on a non-ok response you throw
an Error (including status or body message) instead of returning it; this will
cause the mutation to reject and trigger onError rather than onSuccess. Ensure
you reference the same mutation response handler where onError, onSuccess,
queryClient.invalidateQueries({ queryKey: queryKeys.usersAll() }), and
onOpenChange(false) are defined so the success flow is only executed for OK
responses.

In `@apps/iris/src/routes/_private/admin/doorlock/cards.tsx`:
- Around line 141-142: The code only invalidates queryKeys.doorlock.cards()
after card mutations, leaving dashboard aggregates stale; update the mutation
success logic to invalidate all affected doorlock query families — e.g., call
queryClient.invalidateQueries for queryKeys.doorlock.cards() and
queryKeys.doorlock.dashboard() (and any other doorlock families such as
queryKeys.doorlock.stats() if present), or use a family-wide invalidation like
queryClient.invalidateQueries({ queryKey: queryKeys.doorlock() }) so both the
cards list and dashboard aggregates are refreshed; apply the same change at the
other occurrence around setDialogOpen(false).

In `@apps/iris/src/routes/_private/admin/doorlock/devices.tsx`:
- Line 119: The mutation success handlers currently call
queryClient.invalidateQueries({ queryKey: queryKeys.doorlock.devices() }) but
forget to invalidate the doorlock overview/metrics cache; update the
create/update/delete mutation onSuccess handlers to also call
queryClient.invalidateQueries for the doorlock stats query (e.g.,
queryClient.invalidateQueries({ queryKey: queryKeys.doorlock.stats() }) or the
appropriate queryKeys.doorlock.<overview|metrics>() name used in the codebase)
so both devices() and the doorlock stats query family are invalidated (apply the
same change to the other handler at the other location noted).

In `@apps/iris/src/routes/_private/admin/doorlock/index.tsx`:
- Line 41: The code marks stats as loaded when the query errored because
isLoading is computed as !(stats || statsQuery.isError); change the logic to use
the query's loading state and only treat missing stats as "still loading" when
there is no error: set isLoading to something like statsQuery.isLoading ||
(!stats && !statsQuery.isError) (and apply the same update to the similar
loading computations in the block handling lines 65-82); locate the isLoading
declaration and the other loading/ready computations that reference stats and
statsQuery and replace the expression accordingly.

In `@apps/iris/src/routes/_private/admin/timetable/manage.tsx`:
- Line 337: The map over timetables uses a duplicated null-coalescing
expression; instead use the already-defined const data = timetablesQuery.data ??
[] for consistency. Replace (timetablesQuery.data ?? []).map((item) => { ... })
with data.map((item) => { ... }) so all consumers (including the stats logic
that uses data) reference the same array and avoid repeating the null check.
Ensure timetablesQuery and data are in scope where used.

In `@apps/iris/src/routes/_private/admin/users.tsx`:
- Around line 73-83: The users list currently keeps filter/pagination in local
React state (search, page with setSearch/setPage) and updates them in the Input
onChange handler; move this state into TanStack Router search params so the URL
reflects and persists filters/pagination: replace local search and page state
with router-driven search params (initialize values from the router params on
render), update the Input onChange to write the new search param and reset the
page param to 1, and update any pagination handlers to update the page param
instead of calling setPage; locate usages of search, setSearch, page, setPage,
and the Input onChange in users.tsx to make these changes and ensure the route
reads params on mount and uses them for queries and UI state.

---

Outside diff comments:
In `@apps/iris/src/routes/_private/admin/roles.tsx`:
- Around line 24-45: The search term is stored in local state (search /
setSearch) which prevents deep-linking and restoring on reload; replace the
local useState usage with a route search-param backed value via your TanStack
Router search API so the term is read from and written to the URL instead of
component state. Specifically, remove the useState('') for search, read the
initial term from the router search params and update the router search param in
your input change handler, keep the same filteredRoles useMemo logic (still
referencing allRoles and the term) and ensure any other occurrences mentioned
(the similar block at lines 68-74) are migrated the same way; update
dependencies and any unit/UI tests that asserted local state to use the
router-backed param.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 51962ded-5523-4d49-bdd1-cc2a96135336

📥 Commits

Reviewing files that changed from the base of the PR and between cb432f5 and 504465a.

📒 Files selected for processing (13)
  • apps/iris/public/locales/en/translation.json
  • apps/iris/public/locales/hu/translation.json
  • apps/iris/src/components/admin/stat-card.tsx
  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/components/admin/users-table.tsx
  • apps/iris/src/components/subs.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/roles.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/components/admin/users-table.tsx
  • apps/iris/src/routes/_private/admin/roles.tsx
  • apps/iris/src/components/subs.tsx
  • apps/iris/src/components/admin/stat-card.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/components/admin/users-table.tsx
  • apps/iris/src/routes/_private/admin/roles.tsx
  • apps/iris/src/components/subs.tsx
  • apps/iris/src/components/admin/stat-card.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/components/admin/users-table.tsx
  • apps/iris/src/routes/_private/admin/roles.tsx
  • apps/iris/src/components/subs.tsx
  • apps/iris/src/components/admin/stat-card.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
apps/iris/src/routes/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/routes/**/*.tsx: Follow route composition patterns from apps/iris/src/routes/_private/admin/news/system-messages.tsx: use createFileRoute(...) at the top, then permission gating, then queries and mutations grouped near the component that owns them
Keep search-param-driven page state in TanStack Router when the page already uses it for filters or selection. Do not fork that state into unrelated local state

Files:

  • apps/iris/src/routes/_private/admin/roles.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
apps/iris/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

Treat apps/iris/src/route-tree.gen.ts as generated; change source files under apps/iris/src/routes instead

Files:

  • apps/iris/src/routes/_private/admin/roles.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
apps/iris/src/components/**/*dialog.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-dialog-form.instructions.md)

apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>
Reuse validation schemas from apps/iris/src/utils/form-schemas.ts when available. If a schema becomes shared by multiple dialogs, move it there instead of copying validation logic
form.reset takes raw values, not { values: ... }. Because form.reset and form.setFieldValue are not stable dependencies, do not add them to useEffect arrays when synchronizing dialog state
Reuse or extend shared dialog prop types such as admin.types.ts and doorlock.types.ts instead of defining near-duplicate props in each dialog
Keep submit side effects together: mutation success should close the dialog, invalidate the relevant query keys, and surface translated success or failure feedback
New labels, button text, placeholders, and empty states belong in t(...) and the locale files, even if older dialogs still have hardcoded strings

Files:

  • apps/iris/src/components/admin/user-dialog.tsx
🔇 Additional comments (11)
apps/iris/public/locales/en/translation.json (1)

14-14: LGTM!

Also applies to: 116-118, 127-127, 137-137, 187-191, 238-239, 360-381, 384-384, 407-412, 484-545

apps/iris/public/locales/hu/translation.json (1)

104-106: LGTM!

Also applies to: 115-115, 125-125, 175-179, 187-187, 238-239, 360-381, 384-384, 407-412, 484-545

apps/iris/src/components/subs.tsx (2)

57-60: LGTM!


71-72: ⚡ Quick win

Confirm lesson.period.period is a valid field
In apps/iris/src/components/subs.tsx (lines 71-72), the JSX renders lesson.period.period; confirm the lesson.period/Period TypeScript (and underlying API/schema) defines a numeric period property, or update the code to the correct field name/type.

apps/iris/src/components/admin/stat-card.tsx (1)

5-30: LGTM!

apps/iris/src/components/admin/users-table.tsx (1)

3-100: LGTM!

apps/iris/src/routes/_private/admin/timetable/manage.tsx (2)

262-273: LGTM!


299-315: Prevent “0” stat values from rendering during timetable loading (apps/iris/src/routes/_private/admin/timetable/manage.tsx, lines 299-315): the StatCards are rendered outside the loading check, so while timetablesQuery.isLoading is true the UI shows 0 for all stats even though the table below shows a skeleton. If StatCard supports an isLoading/loading prop, pass timetablesQuery.isLoading; otherwise render StatCard skeletons/placeholder values in this section to match the table’s loading state.

apps/iris/src/routes/_private/admin/timetable/substitutions.tsx (3)

418-426: LGTM!


428-442: LGTM!


445-457: LGTM!

Comment thread apps/iris/src/components/admin/user-dialog.tsx
Comment thread apps/iris/src/routes/_private/admin/doorlock/cards.tsx
Comment thread apps/iris/src/routes/_private/admin/doorlock/devices.tsx
Comment thread apps/iris/src/routes/_private/admin/doorlock/index.tsx Outdated
Comment thread apps/iris/src/routes/_private/admin/timetable/manage.tsx Outdated
Comment thread apps/iris/src/routes/_private/admin/users.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/iris/src/routes/_private/admin/users.tsx`:
- Around line 31-41: The effect currently triggers on mount and resets page to 1
unconditionally and inputValue can drift when the URL changes; change the logic
so the debounced navigate only runs when the typed input actually differs from
the current router search (i.e., guard the setTimeout with if (inputValue !==
search)), and add a separate effect to sync inputValue from the router when the
url search param changes (useEffect(() => setInputValue(search), [search])) so
external navigation updates the input box without causing the mount-time page
reset; refer to inputValue, setInputValue, useEffect, navigate and the router
search param 'search' when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 79a13b20-24d5-41bc-bc8a-841a2f9757dd

📥 Commits

Reviewing files that changed from the base of the PR and between 504465a and 72c610d.

📒 Files selected for processing (6)
  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
apps/iris/src/components/**/*dialog.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-dialog-form.instructions.md)

apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>
Reuse validation schemas from apps/iris/src/utils/form-schemas.ts when available. If a schema becomes shared by multiple dialogs, move it there instead of copying validation logic
form.reset takes raw values, not { values: ... }. Because form.reset and form.setFieldValue are not stable dependencies, do not add them to useEffect arrays when synchronizing dialog state
Reuse or extend shared dialog prop types such as admin.types.ts and doorlock.types.ts instead of defining near-duplicate props in each dialog
Keep submit side effects together: mutation success should close the dialog, invalidate the relevant query keys, and surface translated success or failure feedback
New labels, button text, placeholders, and empty states belong in t(...) and the locale files, even if older dialogs still have hardcoded strings

Files:

  • apps/iris/src/components/admin/user-dialog.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/components/admin/user-dialog.tsx
  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
apps/iris/src/routes/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/routes/**/*.tsx: Follow route composition patterns from apps/iris/src/routes/_private/admin/news/system-messages.tsx: use createFileRoute(...) at the top, then permission gating, then queries and mutations grouped near the component that owns them
Keep search-param-driven page state in TanStack Router when the page already uses it for filters or selection. Do not fork that state into unrelated local state

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
apps/iris/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

Treat apps/iris/src/route-tree.gen.ts as generated; change source files under apps/iris/src/routes instead

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
  • apps/iris/src/routes/_private/admin/timetable/manage.tsx
  • apps/iris/src/routes/_private/admin/doorlock/devices.tsx
  • apps/iris/src/routes/_private/admin/doorlock/index.tsx
  • apps/iris/src/routes/_private/admin/doorlock/cards.tsx
🔇 Additional comments (5)
apps/iris/src/routes/_private/admin/doorlock/cards.tsx (1)

142-142: LGTM!

Also applies to: 162-162

apps/iris/src/routes/_private/admin/doorlock/devices.tsx (1)

120-120: LGTM!

Also applies to: 140-140

apps/iris/src/routes/_private/admin/doorlock/index.tsx (1)

41-41: LGTM!

apps/iris/src/routes/_private/admin/timetable/manage.tsx (1)

337-337: LGTM!

apps/iris/src/components/admin/user-dialog.tsx (1)

64-64: LGTM!

Comment thread apps/iris/src/routes/_private/admin/users.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/iris/src/routes/_private/admin/users.tsx (1)

61-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the thrown query error text.

Line 62 throws a hard-coded English message, and Line 127 renders error.message to users, so this bypasses translation.

💡 Proposed fix
-      if (!res.success) {
-        throw new Error('Failed to load users');
-      }
+      if (!res.success) {
+        throw new Error(t('users.loadErrorMessage'));
+      }

As per coding guidelines, “New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/routes/_private/admin/users.tsx` around lines 61 - 63, Replace
the hard-coded throw new Error('Failed to load users') with a localized message
using the app's translation function (e.g., t('...')) so the error.message
rendered later is translated; update the locale files with a key like
admin.users.loadFailed (and use that same key when surfacing toasts/UI). Locate
the check that inspects res.success (the throw inside the users fetch/block) and
swap the literal string for a call to the translator, ensuring the translator is
in scope or imported where this throw happens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/iris/src/routes/_private/admin/users.tsx`:
- Around line 61-63: Replace the hard-coded throw new Error('Failed to load
users') with a localized message using the app's translation function (e.g.,
t('...')) so the error.message rendered later is translated; update the locale
files with a key like admin.users.loadFailed (and use that same key when
surfacing toasts/UI). Locate the check that inspects res.success (the throw
inside the users fetch/block) and swap the literal string for a call to the
translator, ensuring the translator is in scope or imported where this throw
happens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9460038d-2c77-4714-b7d9-5787ed3fbcac

📥 Commits

Reviewing files that changed from the base of the PR and between 72c610d and fe4c9fe.

📒 Files selected for processing (1)
  • apps/iris/src/routes/_private/admin/users.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
apps/iris/src/routes/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/routes/**/*.tsx: Follow route composition patterns from apps/iris/src/routes/_private/admin/news/system-messages.tsx: use createFileRoute(...) at the top, then permission gating, then queries and mutations grouped near the component that owns them
Keep search-param-driven page state in TanStack Router when the page already uses it for filters or selection. Do not fork that state into unrelated local state

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
apps/iris/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

Treat apps/iris/src/route-tree.gen.ts as generated; change source files under apps/iris/src/routes instead

Files:

  • apps/iris/src/routes/_private/admin/users.tsx
🔇 Additional comments (2)
apps/iris/src/routes/_private/admin/users.tsx (2)

34-48: LGTM!


22-25: LGTM!

Also applies to: 50-58, 66-67, 89-145

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 28, 2026
@KoZsombat KoZsombat dismissed coderabbitai[bot]’s stale review May 29, 2026 10:55

The merge-base changed after approval.

@nemvince nemvince merged commit fe4c9fe into main May 30, 2026
7 checks passed
@nemvince nemvince deleted the unify-admin-page-layouts branch May 30, 2026 13:53
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Filc Issue Tracker May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request iris - web Work to be done on iris, the web

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Unify all admin page layouts

2 participants