Unify admin page layouts#225
Conversation
📝 WalkthroughWalkthroughLocalizes 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. ChangesAdmin Panel Localization and Dashboard Enhancement
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labelsfeature, iris - web Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
@coderabbitai work |
|
✅ Actions performedReview triggered.
|
|
@coderabbitai work |
|
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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 liftPersist 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
📒 Files selected for processing (13)
apps/iris/public/locales/en/translation.jsonapps/iris/public/locales/hu/translation.jsonapps/iris/src/components/admin/stat-card.tsxapps/iris/src/components/admin/user-dialog.tsxapps/iris/src/components/admin/users-table.tsxapps/iris/src/components/subs.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/iris/src/routes/_private/admin/roles.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/timetable/substitutions.tsxapps/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.tsxapps/iris/src/routes/_private/admin/roles.tsxapps/iris/src/components/subs.tsxapps/iris/src/components/admin/stat-card.tsxapps/iris/src/routes/_private/admin/timetable/substitutions.tsxapps/iris/src/components/admin/user-dialog.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/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
UseparseResponse(...)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 throught(...)and the locale files, even when surfaced through toasts
Files:
apps/iris/src/components/admin/users-table.tsxapps/iris/src/routes/_private/admin/roles.tsxapps/iris/src/components/subs.tsxapps/iris/src/components/admin/stat-card.tsxapps/iris/src/routes/_private/admin/timetable/substitutions.tsxapps/iris/src/components/admin/user-dialog.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/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 int(...)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 withuseForm,useStore(form.store, selector), and<form.Field>{(field) => ...}</form.Field>
form.reset(values)takes raw values, not{ values }.form.resetandform.setFieldValueare not stableuseEffectdependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers useonClick, not Radix-styleonSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already ownsResponsiveContainer; 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.tsxapps/iris/src/routes/_private/admin/roles.tsxapps/iris/src/components/subs.tsxapps/iris/src/components/admin/stat-card.tsxapps/iris/src/routes/_private/admin/timetable/substitutions.tsxapps/iris/src/components/admin/user-dialog.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/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: usecreateFileRoute(...)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.tsxapps/iris/src/routes/_private/admin/timetable/substitutions.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/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.tsxapps/iris/src/routes/_private/admin/timetable/substitutions.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/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 withuseStore(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.resettakes raw values, not{ values: ... }. Becauseform.resetandform.setFieldValueare not stable dependencies, do not add them touseEffectarrays 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 int(...)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 winConfirm
lesson.period.periodis a valid field
Inapps/iris/src/components/subs.tsx(lines 71-72), the JSX renderslesson.period.period; confirm thelesson.period/PeriodTypeScript (and underlying API/schema) defines a numericperiodproperty, 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 whiletimetablesQuery.isLoadingis true the UI shows0for all stats even though the table below shows a skeleton. IfStatCardsupports anisLoading/loading prop, passtimetablesQuery.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!
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
apps/iris/src/components/admin/user-dialog.tsxapps/iris/src/routes/_private/admin/doorlock/cards.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/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.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/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
UseparseResponse(...)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 throught(...)and the locale files, even when surfaced through toasts
Files:
apps/iris/src/components/admin/user-dialog.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/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 withuseStore(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.resettakes raw values, not{ values: ... }. Becauseform.resetandform.setFieldValueare not stable dependencies, do not add them touseEffectarrays 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 int(...)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 int(...)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 withuseForm,useStore(form.store, selector), and<form.Field>{(field) => ...}</form.Field>
form.reset(values)takes raw values, not{ values }.form.resetandform.setFieldValueare not stableuseEffectdependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers useonClick, not Radix-styleonSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already ownsResponsiveContainer; 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.tsxapps/iris/src/routes/_private/admin/users.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/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: usecreateFileRoute(...)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.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/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.tsxapps/iris/src/routes/_private/admin/timetable/manage.tsxapps/iris/src/routes/_private/admin/doorlock/devices.tsxapps/iris/src/routes/_private/admin/doorlock/index.tsxapps/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!
There was a problem hiding this comment.
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 winLocalize the thrown query error text.
Line 62 throws a hard-coded English message, and Line 127 renders
error.messageto 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
📒 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: usecreateFileRoute(...)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
UseparseResponse(...)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 throught(...)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 int(...)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 withuseForm,useStore(form.store, selector), and<form.Field>{(field) => ...}</form.Field>
form.reset(values)takes raw values, not{ values }.form.resetandform.setFieldValueare not stableuseEffectdependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers useonClick, not Radix-styleonSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already ownsResponsiveContainer; 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
The merge-base changed after approval.
Summary by CodeRabbit
New Features
UI Improvements
Localization