Skip to content

Admin Pages#12

Merged
aristachauhan merged 7 commits into
mainfrom
abc/admin-ui
Jul 14, 2026
Merged

Admin Pages#12
aristachauhan merged 7 commits into
mainfrom
abc/admin-ui

Conversation

@aristachauhan

@aristachauhan aristachauhan commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

MVP functionality for admin pages.

Summary by CodeRabbit

  • New Features

    • Added an authenticated admin area with a status dashboard (freshness, issues, metrics) plus users, departments, and admin overview tabs.
    • Introduced a shared admin layout with tabbed navigation.
  • Improvements

    • Direct admin access now consistently opens the status dashboard, while non-admin access shows a restricted-access error.
    • Updated error pages for clearer “not found” and unavailable messaging.
  • Chores / Maintenance

    • Removed outdated authenticated demo/sample pages and their related tests.
    • Added/strengthened server-side admin dashboard and admin management APIs, including stricter validation/uniqueness.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an authenticated admin area with dashboard aggregation, user and department management, shared navigation, identity handling, standardized error pages, development seeding, schema updates, and removal of legacy sample routes and UI.

Changes

Admin platform

Layer / File(s) Summary
Admin API and persistence
server/Controllers/AdminController.cs, server.core/Data/DbInitializer.cs, server.core/Domain/*, server.core/Migrations/*
Adds dashboard and management endpoints, development data seeding, relational/non-relational initialization, and schema constraints for routing emails and leave-request actors.
Admin data provider and routing
client/src/shared/admin/*, client/src/routes/(authenticated)/admin.tsx, client/src/routeTree.gen.ts
Adds normalized React Query admin data, mutation invalidation, admin authorization, nested routes, and tab navigation.
Admin dashboard views
client/src/routes/(authenticated)/admin.*.tsx
Adds status, user management, and department management pages with metrics, forms, modals, filtering, and routing-email controls.
Identity, login, and error handling
server/Services/UserService.cs, server/Controllers/UserController.cs, server/Controllers/AccountController.cs, client/src/shared/errors/*, client/src/shared/auth/*
Updates display-name resolution, development login rendering, authenticated navigation, and shared error pages.
Application surface cleanup
client/src/routes/(authenticated)/index.tsx, client/src/routes/(authenticated)/table-export.tsx, client/src/main.css, client/src/test/*
Removes legacy sample content and tests, simplifies the authenticated home page, and updates styling and test documentation.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminUser
  participant AdminStatusRoute
  participant AdminDataProvider
  participant AdminController
  participant AppDbContext
  AdminUser->>AdminStatusRoute: open /admin/status
  AdminStatusRoute->>AdminDataProvider: request dashboard data
  AdminDataProvider->>AdminController: GET /api/admin/dashboard
  AdminController->>AppDbContext: query dashboard records
  AppDbContext-->>AdminController: source data
  AdminController-->>AdminDataProvider: dashboard response
  AdminDataProvider-->>AdminStatusRoute: normalized data
  AdminStatusRoute-->>AdminUser: render status dashboard
Loading

Possibly related PRs

  • ucdavis/leaves#5: Both changes modify DbInitializer development seeding and database setup behavior.
  • ucdavis/leaves#7: Both changes modify authenticated admin routing and authorization behavior.

Suggested reviewers: jsylvestre, sprucely

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the changes, but it is too generic to convey the main scope of the PR. Rename it to something specific like 'Add admin dashboard pages and APIs' so teammates can scan history quickly.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch abc/admin-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
client/src/shared/admin/adminData.tsx (1)

107-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Model the wire response separately from normalized UI data.

The API returns departmentId: null for unmapped users, but this response type intersects it with AdminUser.departmentId: string. Define a raw nullable response type, then normalize into the non-null UI model.

As per coding guidelines, “Write type-safe API calls and maintain TypeScript interfaces for API responses.”

Also applies to: 193-215

🤖 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 `@client/src/shared/admin/adminData.tsx` around lines 107 - 114, The
AdminDashboardResponse users type incorrectly combines nullable API departmentId
values with the non-null AdminUser model. Define a separate raw response
user/response type with departmentId: string | null, use it for the API payload,
and normalize the users in the relevant response handling code (around the
dashboard data transformation) into the non-null AdminUser UI model with an
explicit fallback or mapping strategy.

Source: Coding guidelines

client/src/routes/(authenticated)/admin.departments.tsx (1)

309-459: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Use TanStack React Form for department settings.

This modal manages multiple persisted fields, dynamic routing emails, and submission behavior manually. Use TanStack React Form with the existing Query mutations to centralize validation, pending state, and server errors.

As per coding guidelines, “Use TanStack React Form for complex forms and combine form state with TanStack Query mutations for server interactions.”

🤖 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 `@client/src/routes/`(authenticated)/admin.departments.tsx around lines 309 -
459, Refactor DepartmentSettingsModal to use TanStack React Form for
approvalMode, clusterId, and routing-email input instead of local useState and
manual submission handling. Wire the form submission and routing-email actions
to the existing Query mutations, including validation, mutation pending/disabled
states, and surfaced server errors; update onSave and related callbacks as
needed while preserving the current modal behavior.

Source: Coding guidelines

client/src/routes/(authenticated)/admin.users.tsx (1)

331-536: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Use TanStack React Form for the user forms.

These forms manually duplicate field state, validation, errors, submission state, and mutation coordination. Migrate them to TanStack React Form and connect submission to the existing Query mutations.

As per coding guidelines, “Use TanStack React Form for complex forms and combine form state with TanStack Query mutations for server interactions.”

🤖 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 `@client/src/routes/`(authenticated)/admin.users.tsx around lines 331 - 536,
Replace the manual state, validation, field-error, and submission handling in
UserEditorModal and CreateUserModal with TanStack React Form, defining form
fields from the existing user schema and wiring submission to the existing Query
mutations. Use the form’s values, validation errors, and submission state for
rendering and disable/feedback behavior, while preserving the current create and
edit payloads and callbacks.

Source: Coding guidelines

🤖 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 `@client/src/main.css`:
- Line 3: Move the `@ucdavis/gunrock-tailwind/imports.css` import to the beginning
of client/src/main.css, before all other rules and declarations, except any
required `@charset` or `@layer` statements.

In `@client/src/routes/`(authenticated)/admin.departments.tsx:
- Around line 151-155: Update the department mutation handlers and their
callers, including renameCluster, settings save, routing-email add, and
routing-email removal in the referenced UI sections, to await the mutation
promises and handle failures. Only close modals or clear/reset entered values
after a successful mutation; preserve the current UI state on rejection and
surface an appropriate error message.

In `@client/src/routes/`(authenticated)/admin.users.tsx:
- Around line 127-139: Update the user mutation handlers, including the status
toggle and edit-save logic around updateUser and the affected handlers at the
referenced sections, to return/await their promises instead of using void. Catch
rejected updates, preserve the modal and user edits on failure, and expose a
visible error state; only close the modal or update the UI after the mutation
succeeds.

In `@client/src/shared/errors/PageErrorState.tsx`:
- Around line 17-43: Render the provided action and secondaryAction elements in
PageErrorState after the description, using a clear flex layout with spacing and
preserving any existing styling expectations. Ensure both props are
conditionally rendered so callers in route.tsx and __root.tsx display their
recovery buttons, including sign-in, retry, and home navigation actions.

In `@server/Controllers/AdminController.cs`:
- Around line 240-261: Add a unique database index/constraint on
DepartmentEmailRouting for the DepartmentCode and ToEmail pair, then update the
routing write action to catch the resulting unique-constraint DbUpdateException
and return a 409 Conflict response. Keep the existing lookup for normal flow,
but rely on the database constraint for atomic enforcement and identify the
conflict specifically rather than converting unrelated persistence errors.
- Around line 246-258: The department email routing update currently uses
GetFallbackAdminUserId(), which can record the wrong actor. Resolve the
authenticated User principal to its AppUser record, reject the write when no
mapping exists, and assign that ID to UpdatedByAppUserId; apply the same
authenticated-user mapping change in the fallback helper.
- Around line 299-314: Update CreateUser and UpdateUser to catch
unique-constraint DbUpdateException errors from SaveChangesAsync involving IamId
or EmployeeId, and return Conflict (409) instead of allowing a 500; rethrow
unrelated database exceptions.

---

Nitpick comments:
In `@client/src/routes/`(authenticated)/admin.departments.tsx:
- Around line 309-459: Refactor DepartmentSettingsModal to use TanStack React
Form for approvalMode, clusterId, and routing-email input instead of local
useState and manual submission handling. Wire the form submission and
routing-email actions to the existing Query mutations, including validation,
mutation pending/disabled states, and surfaced server errors; update onSave and
related callbacks as needed while preserving the current modal behavior.

In `@client/src/routes/`(authenticated)/admin.users.tsx:
- Around line 331-536: Replace the manual state, validation, field-error, and
submission handling in UserEditorModal and CreateUserModal with TanStack React
Form, defining form fields from the existing user schema and wiring submission
to the existing Query mutations. Use the form’s values, validation errors, and
submission state for rendering and disable/feedback behavior, while preserving
the current create and edit payloads and callbacks.

In `@client/src/shared/admin/adminData.tsx`:
- Around line 107-114: The AdminDashboardResponse users type incorrectly
combines nullable API departmentId values with the non-null AdminUser model.
Define a separate raw response user/response type with departmentId: string |
null, use it for the API payload, and normalize the users in the relevant
response handling code (around the dashboard data transformation) into the
non-null AdminUser UI model with an explicit fallback or mapping strategy.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d60c263-0577-4d3a-a526-cdc3486d38fd

📥 Commits

Reviewing files that changed from the base of the PR and between fd3895f and b83a8c4.

📒 Files selected for processing (30)
  • .devcontainer/devcontainer.json
  • client/src/main.css
  • client/src/routeTree.gen.ts
  • client/src/routes/(authenticated)/admin.departments.tsx
  • client/src/routes/(authenticated)/admin.status.tsx
  • client/src/routes/(authenticated)/admin.tsx
  • client/src/routes/(authenticated)/admin.users.tsx
  • client/src/routes/(authenticated)/fetch.tsx
  • client/src/routes/(authenticated)/form.tsx
  • client/src/routes/(authenticated)/index.tsx
  • client/src/routes/(authenticated)/me.tsx
  • client/src/routes/(authenticated)/notification.tsx
  • client/src/routes/(authenticated)/route.tsx
  • client/src/routes/(authenticated)/styles.tsx
  • client/src/routes/(authenticated)/table-export.tsx
  • client/src/routes/__root.tsx
  • client/src/routes/about.tsx
  • client/src/shared/admin/adminData.tsx
  • client/src/shared/admin/adminLayout.tsx
  • client/src/shared/auth/AuthenticatedShell.tsx
  • client/src/shared/errors/PageErrorState.tsx
  • client/src/test/README.md
  • client/src/test/routes/(authenticated)/fetch.test.tsx
  • client/src/test/routes/(authenticated)/notification.test.tsx
  • client/src/test/routes/(authenticated)/table-export.test.tsx
  • server/Controllers/AccountController.cs
  • server/Controllers/AdminController.cs
  • server/Controllers/UserController.cs
  • server/Helpers/LoggingMiddlewareHelper.cs
  • server/Services/UserService.cs
💤 Files with no reviewable changes (11)
  • client/src/test/routes/(authenticated)/fetch.test.tsx
  • client/src/routes/about.tsx
  • .devcontainer/devcontainer.json
  • client/src/routes/(authenticated)/fetch.tsx
  • client/src/test/routes/(authenticated)/notification.test.tsx
  • client/src/routes/(authenticated)/notification.tsx
  • client/src/test/routes/(authenticated)/table-export.test.tsx
  • client/src/routes/(authenticated)/styles.tsx
  • client/src/routes/(authenticated)/form.tsx
  • client/src/routes/(authenticated)/table-export.tsx
  • client/src/routes/(authenticated)/me.tsx

Comment thread client/src/main.css
@import "tailwindcss";
@plugin "daisyui";
@import '@ucdavis/gunrock-tailwind/imports.css'; No newline at end of file
@import '@ucdavis/gunrock-tailwind/imports.css';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

@import must precede all other rules.

Stylelint reports no-invalid-position-at-import-rule at line 3. CSS requires @import statements to come before any other rules (except @charset and @layer). If lines 1–2 contain non-import CSS, browsers may silently ignore this import, breaking the Gunrock design system styles.

🔧 Move the import to the top of the file

Ensure the @import is the first statement in the file (or immediately after any @charset/@layer declarations):

+@import '`@ucdavis/gunrock-tailwind/imports.css`';
+
+/* existing lines 1-2 content here */
+
 :root {
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 3-3: Invalid position for @import rule (no-invalid-position-at-import-rule)

(no-invalid-position-at-import-rule)

🤖 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 `@client/src/main.css` at line 3, Move the
`@ucdavis/gunrock-tailwind/imports.css` import to the beginning of
client/src/main.css, before all other rules and declarations, except any
required `@charset` or `@layer` statements.

Source: Linters/SAST tools

Comment thread client/src/routes/(authenticated)/admin.departments.tsx Outdated
Comment thread client/src/routes/(authenticated)/admin.users.tsx Outdated
Comment on lines +17 to +43
export function PageErrorState({
action,
badge,
code,
description,
secondaryAction,
title,
}: PageErrorStateProps) {
return (
<main className="min-h-screen bg-white px-4 py-16">
<section className="mx-auto flex min-h-[calc(100vh-8rem)] w-full max-w-xl flex-col items-center justify-center text-center">
<div className="text-7xl font-black tracking-tight text-[var(--admin-blue)]">
{code}
</div>
<p className="mt-3 text-sm font-semibold uppercase tracking-[0.2em] text-[var(--admin-ink-soft)]">
{badge}
</p>
<h1 className="mt-4 text-3xl font-bold text-[var(--admin-blue)]">
{title}
</h1>
<p className="mt-3 text-base leading-7 text-[var(--admin-ink-muted)]">
{description}
</p>
</section>
</main>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

action and secondaryAction props are never rendered.

The component accepts action and secondaryAction props but its JSX only renders code, badge, title, and description. All callers—route.tsx (403 and 500 errors) and __root.tsx (404)—pass these props expecting clickable buttons (e.g., "Sign in again", "Try again", "Go home"). Users will see error pages with no actionable navigation, breaking recovery flows entirely.

🐛 Proposed fix to render action buttons
 export function PageErrorState({
   action,
   badge,
   code,
   description,
   secondaryAction,
   title,
 }: PageErrorStateProps) {
+  const renderAction = (act: PageErrorAction, isSecondary = false) => {
+    if (!act) return null;
+    const baseClass = isSecondary
+      ? 'text-sm font-semibold text-[var(--admin-ink-muted)] underline hover:text-[var(--admin-blue)]'
+      : 'rounded-lg bg-[var(--admin-blue)] px-6 py-3 text-sm font-semibold text-white hover:opacity-90';
+    if (act.to) {
+      return (
+        <Link className={baseClass} to={act.to}>
+          {act.label}
+        </Link>
+      );
+    }
+    if (act.href) {
+      return (
+        <a className={baseClass} href={act.href}>
+          {act.label}
+        </a>
+      );
+    }
+    return (
+      <button className={baseClass} onClick={act.onClick} type="button">
+        {act.label}
+      </button>
+    );
+  };
+
   return (
     <main className="min-h-screen bg-white px-4 py-16">
       <section className="mx-auto flex min-h-[calc(100vh-8rem)] w-full max-w-xl flex-col items-center justify-center text-center">
         <div className="text-7xl font-black tracking-tight text-[var(--admin-blue)]">
           {code}
         </div>
         <p className="mt-3 text-sm font-semibold uppercase tracking-[0.2em] text-[var(--admin-ink-soft)]">
           {badge}
         </p>
         <h1 className="mt-4 text-3xl font-bold text-[var(--admin-blue)]">
           {title}
         </h1>
         <p className="mt-3 text-base leading-7 text-[var(--admin-ink-muted)]">
           {description}
         </p>
+        {(action || secondaryAction) && (
+          <div className="mt-8 flex items-center justify-center gap-6">
+            {renderAction(action)}
+            {renderAction(secondaryAction, true)}
+          </div>
+        )}
       </section>
     </main>
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function PageErrorState({
action,
badge,
code,
description,
secondaryAction,
title,
}: PageErrorStateProps) {
return (
<main className="min-h-screen bg-white px-4 py-16">
<section className="mx-auto flex min-h-[calc(100vh-8rem)] w-full max-w-xl flex-col items-center justify-center text-center">
<div className="text-7xl font-black tracking-tight text-[var(--admin-blue)]">
{code}
</div>
<p className="mt-3 text-sm font-semibold uppercase tracking-[0.2em] text-[var(--admin-ink-soft)]">
{badge}
</p>
<h1 className="mt-4 text-3xl font-bold text-[var(--admin-blue)]">
{title}
</h1>
<p className="mt-3 text-base leading-7 text-[var(--admin-ink-muted)]">
{description}
</p>
</section>
</main>
);
}
export function PageErrorState({
action,
badge,
code,
description,
secondaryAction,
title,
}: PageErrorStateProps) {
const renderAction = (act: PageErrorAction, isSecondary = false) => {
if (!act) return null;
const baseClass = isSecondary
? 'text-sm font-semibold text-[var(--admin-ink-muted)] underline hover:text-[var(--admin-blue)]'
: 'rounded-lg bg-[var(--admin-blue)] px-6 py-3 text-sm font-semibold text-white hover:opacity-90';
if (act.to) {
return (
<Link className={baseClass} to={act.to}>
{act.label}
</Link>
);
}
if (act.href) {
return (
<a className={baseClass} href={act.href}>
{act.label}
</a>
);
}
return (
<button className={baseClass} onClick={act.onClick} type="button">
{act.label}
</button>
);
};
return (
<main className="min-h-screen bg-white px-4 py-16">
<section className="mx-auto flex min-h-[calc(100vh-8rem)] w-full max-w-xl flex-col items-center justify-center text-center">
<div className="text-7xl font-black tracking-tight text-[var(--admin-blue)]">
{code}
</div>
<p className="mt-3 text-sm font-semibold uppercase tracking-[0.2em] text-[var(--admin-ink-soft)]">
{badge}
</p>
<h1 className="mt-4 text-3xl font-bold text-[var(--admin-blue)]">
{title}
</h1>
<p className="mt-3 text-base leading-7 text-[var(--admin-ink-muted)]">
{description}
</p>
{(action || secondaryAction) && (
<div className="mt-8 flex items-center justify-center gap-6">
{renderAction(action)}
{renderAction(secondaryAction, true)}
</div>
)}
</section>
</main>
);
}
🤖 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 `@client/src/shared/errors/PageErrorState.tsx` around lines 17 - 43, Render the
provided action and secondaryAction elements in PageErrorState after the
description, using a clear flex layout with spacing and preserving any existing
styling expectations. Ensure both props are conditionally rendered so callers in
route.tsx and __root.tsx display their recovery buttons, including sign-in,
retry, and home navigation actions.

Comment on lines +240 to +261
var existing = await _db.DepartmentEmailRoutings.FirstOrDefaultAsync(
item => item.DepartmentCode == departmentCode && item.ToEmail == email,
cancellationToken);

if (existing == null)
{
var adminUserId = await GetFallbackAdminUserId(cancellationToken);
if (adminUserId == null)
{
return ValidationProblem("An AppUser row is required before routing emails can be updated.");
}

existing = new DepartmentEmailRouting
{
DepartmentCode = departmentCode,
IsActive = true,
ToEmail = email,
UpdatedByAppUserId = adminUserId.Value,
UpdatedUtc = DateTime.UtcNow,
};

_db.DepartmentEmailRoutings.Add(existing);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Enforce unique routing emails atomically.

Two concurrent requests can both observe no row and insert duplicate department/email routings. Add a unique (DepartmentCode, ToEmail) database constraint and translate its conflict to a 409 response.

🤖 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 `@server/Controllers/AdminController.cs` around lines 240 - 261, Add a unique
database index/constraint on DepartmentEmailRouting for the DepartmentCode and
ToEmail pair, then update the routing write action to catch the resulting
unique-constraint DbUpdateException and return a 409 Conflict response. Keep the
existing lookup for normal flow, but rely on the database constraint for atomic
enforcement and identify the conflict specifically rather than converting
unrelated persistence errors.

Comment thread server/Controllers/AdminController.cs Outdated
Comment thread server/Controllers/AdminController.cs

var adminIamIds = await _db.AppAdminAssignments
.AsNoTracking()
.Select(assignment => assignment.IamId.Trim())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if IamId can be null

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
server/Controllers/AdminController.cs (2)

212-217: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject unsupported approval modes instead of enabling direct submission.

Any non-empty value other than "approval" currently selects DirectSubmission, so a typo can silently disable approval requirements. Accept known values explicitly and return a validation error otherwise.

Proposed fix
-        if (!string.IsNullOrWhiteSpace(request.ApprovalMode))
+        if (request.ApprovalMode is not null)
         {
-            department.WorkflowMode = request.ApprovalMode == "approval"
-                ? WorkflowMode.ApprovalRequired
-                : WorkflowMode.DirectSubmission;
+            department.WorkflowMode = request.ApprovalMode.Trim().ToLowerInvariant() switch
+            {
+                "approval" => WorkflowMode.ApprovalRequired,
+                "notification" => WorkflowMode.DirectSubmission,
+                _ => throw new ArgumentException("Unsupported approval mode."),
+            };
         }

Return ValidationProblem rather than allowing the exception to escape.

🤖 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 `@server/Controllers/AdminController.cs` around lines 212 - 217, Update the
approval-mode handling in AdminController to accept only the supported values,
including "approval" and the valid direct-submission value, and return
ValidationProblem for any other non-empty input before modifying
department.WorkflowMode. Preserve the existing workflow assignment for
recognized modes.

132-134: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include routing changes in the departments timestamp.

The source claims to represent both departments and routing records, but its timestamp only observes Department.UpdatedUtc. Routing additions or reactivations therefore leave UpdatedAt stale.

-GetLatestTimestamp(departments.Select(department => department.UpdatedUtc))
+GetLatestTimestamp(
+    departments.Select(department => department.UpdatedUtc)
+        .Concat(departments.SelectMany(department =>
+            department.DepartmentEmailRoutings.Select(routing => routing.UpdatedUtc))))
🤖 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 `@server/Controllers/AdminController.cs` around lines 132 - 134, Update the
“db-departments” AdminDataSourceResponse timestamp to include the latest
UpdatedUtc from both departments and their DepartmentEmailRouting records, while
preserving the existing department data and response metadata.
🤖 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 `@server.core/Migrations/20260713153424_Cleanup.cs`:
- Around line 17-43: Update the migration’s Up method to deterministically
backfill existing LeaveRequestAction rows before altering ActorIamId and
ActorAppUserId to NOT NULL, and resolve or reject duplicate
DepartmentEmailRouting (DepartmentCode, ToEmail) rows before creating the unique
index. Fail fast with a clear migration error if safe cleanup cannot be
performed, then apply the existing constraints.

---

Outside diff comments:
In `@server/Controllers/AdminController.cs`:
- Around line 212-217: Update the approval-mode handling in AdminController to
accept only the supported values, including "approval" and the valid
direct-submission value, and return ValidationProblem for any other non-empty
input before modifying department.WorkflowMode. Preserve the existing workflow
assignment for recognized modes.
- Around line 132-134: Update the “db-departments” AdminDataSourceResponse
timestamp to include the latest UpdatedUtc from both departments and their
DepartmentEmailRouting records, while preserving the existing department data
and response metadata.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e1237c2-9e7d-469c-b1b0-1b3ca2d7389d

📥 Commits

Reviewing files that changed from the base of the PR and between b83a8c4 and 86f3a6d.

📒 Files selected for processing (14)
  • client/src/main.css
  • client/src/routes/(authenticated)/admin.departments.tsx
  • client/src/routes/(authenticated)/admin.users.tsx
  • client/src/routes/(authenticated)/route.tsx
  • client/src/routes/__root.tsx
  • client/src/shared/errors/PageErrorState.tsx
  • server.core/Data/DbInitializer.cs
  • server.core/Domain/DepartmentEmailRouting.cs
  • server.core/Domain/LeaveRequestAction.cs
  • server.core/Migrations/20260713153424_Cleanup.Designer.cs
  • server.core/Migrations/20260713153424_Cleanup.cs
  • server.core/Migrations/AppDbContextModelSnapshot.cs
  • server/Controllers/AdminController.cs
  • server/Program.cs
💤 Files with no reviewable changes (3)
  • client/src/shared/errors/PageErrorState.tsx
  • client/src/routes/(authenticated)/route.tsx
  • client/src/routes/__root.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/src/routes/(authenticated)/admin.departments.tsx
  • client/src/routes/(authenticated)/admin.users.tsx

Comment on lines +17 to +43
migrationBuilder.AlterColumn<string>(
name: "ActorIamId",
table: "LeaveRequestAction",
type: "char(10)",
maxLength: 10,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "char(10)",
oldMaxLength: 10,
oldNullable: true);

migrationBuilder.AlterColumn<int>(
name: "ActorAppUserId",
table: "LeaveRequestAction",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);

migrationBuilder.CreateIndex(
name: "UX_DepartmentEmailRouting_DepartmentCode_ToEmail",
table: "DepartmentEmailRouting",
columns: new[] { "DepartmentCode", "ToEmail" },
unique: true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant files
git ls-files 'server.core/Migrations/*.cs' | sed -n '1,40p'
printf '\n--- target migration ---\n'
sed -n '1,220p' server.core/Migrations/20260713153424_Cleanup.cs

printf '\n--- search related entities/migrations ---\n'
rg -n "LeaveRequestAction|DepartmentEmailRouting|ActorIamId|ActorAppUserId|UX_DepartmentEmailRouting_DepartmentCode_ToEmail" server.core -g '*.cs'

Repository: ucdavis/leaves

Length of output: 23681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target migration and nearby history for context
printf -- '--- file size ---\n'
wc -l server.core/Migrations/20260713153424_Cleanup.cs

printf -- '\n--- target migration excerpt ---\n'
cat -n server.core/Migrations/20260713153424_Cleanup.cs | sed -n '1,260p'

printf -- '\n--- nearby migration files ---\n'
git ls-files 'server.core/Migrations/*.cs' | tail -n 10

printf -- '\n--- related code search ---\n'
rg -n "LeaveRequestAction|DepartmentEmailRouting|ActorIamId|ActorAppUserId|ToEmail|DepartmentCode" server.core -g '*.cs'

Repository: ucdavis/leaves

Length of output: 31706


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- target migration ---'
nl -ba server.core/Migrations/20260713153424_Cleanup.cs | sed -n '1,260p'

echo
echo '--- surrounding migrations mentioning the same tables ---'
rg -n -C 3 "LeaveRequestAction|DepartmentEmailRouting" server.core/Migrations -g '*.cs'

echo
echo '--- entity/model definitions ---'
rg -n -C 4 "class LeaveRequestAction|class DepartmentEmailRouting|ActorIamId|ActorAppUserId|ToEmail|DepartmentCode" server.core -g '*.cs'

Repository: ucdavis/leaves

Length of output: 217


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf -- '--- LeaveRequestAction domain ---\n'
cat -n server.core/Domain/LeaveRequestAction.cs | sed -n '1,220p'

printf -- '\n--- DepartmentEmailRouting domain ---\n'
cat -n server.core/Domain/DepartmentEmailRouting.cs | sed -n '1,220p'

printf -- '\n--- code that creates LeaveRequestAction rows ---\n'
rg -n -C 3 "new LeaveRequestAction|ActorAppUserId =|ActorIamId =" server.core -g '*.cs'

printf -- '\n--- migration history around the same tables ---\n'
rg -n -C 2 "ActorAppUserId|ActorIamId|DepartmentEmailRouting" server.core/Migrations/20260626225422_AppUser.cs server.core/Migrations/20260702152625_AppAdminAssignment.cs server.core/Migrations/20260710153520_InitialAdminTables.cs -g '*.cs'

Repository: ucdavis/leaves

Length of output: 13071


Backfill legacy rows before adding these constraints. LeaveRequestAction still has a nullable history, and DepartmentEmailRouting(DepartmentCode, ToEmail) is only enforced by this migration; add a deterministic cleanup/backfill or fail fast before the NOT NULL and unique index changes.

🤖 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 `@server.core/Migrations/20260713153424_Cleanup.cs` around lines 17 - 43,
Update the migration’s Up method to deterministically backfill existing
LeaveRequestAction rows before altering ActorIamId and ActorAppUserId to NOT
NULL, and resolve or reject duplicate DepartmentEmailRouting (DepartmentCode,
ToEmail) rows before creating the unique index. Fail fast with a clear migration
error if safe cleanup cannot be performed, then apply the existing constraints.

@aristachauhan aristachauhan requested a review from jSylvestre July 13, 2026 17:03
Comment thread server.core/Data/DbInitializer.cs Outdated
Comment on lines +15 to +26
[
new("adminherd", "11111111-1111-1111-1111-111111111111", "84726195", "Maya Thompson", "admin@ucdavis.edu", true, "2026-07-01T16:00:00", "2026-07-08T18:10:00"),
new("apatel", "22222222-2222-2222-2222-222222222222", "36190428", "Asha Patel", "apatel@ucdavis.edu", true, "2026-07-01T16:15:00", "2026-07-08T17:42:00"),
new("jlin", "33333333-3333-3333-3333-333333333333", "59281746", "Jordan Lin", "jlin@ucdavis.edu", true, "2026-07-01T16:20:00", "2026-07-08T17:35:00"),
new("egarcia", "44444444-4444-4444-4444-444444444444", "11846372", "Elena Garcia", "egarcia@ucdavis.edu", true, "2026-07-01T16:25:00", "2026-07-08T16:48:00"),
new("kchen", "55555555-5555-5555-5555-555555555555", "73029514", "Kai Chen", "kchen@ucdavis.edu", true, "2026-07-01T16:30:00", "2026-07-08T19:03:00"),
new("mowens", "66666666-6666-6666-6666-666666666666", "28465091", "Morgan Owens", "mowens@ucdavis.edu", true, "2026-07-01T16:35:00", "2026-07-08T18:21:00"),
new("lwilson", "77777777-7777-7777-7777-777777777777", "66510837", "Lena Wilson", "lwilson@ucdavis.edu", true, "2026-07-01T16:40:00", "2026-07-07T15:16:00"),
new("rshah", "88888888-8888-8888-8888-888888888888", "40957263", "Riya Shah", null, true, "2026-07-01T16:45:00", "2026-07-08T14:10:00"),
new("nroberts", "99999999-9999-9999-9999-999999999999", "95374128", "Noah Roberts", "nroberts@ucdavis.edu", true, "2026-07-01T16:50:00", "2026-07-08T13:28:00"),
new("sbaker", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "17628405", "Sofia Baker", "sbaker@ucdavis.edu", true, "2026-07-01T16:55:00", "2026-07-08T11:47:00"),
new("tnguyen", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "52893617", "Theo Nguyen", "tnguyen@ucdavis.edu", false, "2026-07-01T17:00:00", "2026-07-05T10:12:00"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if we don't need the real email addrs it might be better to not have them listed in public here

);
}

function StatCard({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't be afraid to break these into individal components, maybe in a components folder. better to keep each file small and targeted

);
}

function NameEditor({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

perhaps not worth it here, but something to think about is tanstack forms since i think it would handle most of this w/o manual state stuff. Also other comment applies this might be better in a different component we just import.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
client/src/routes/(authenticated)/admin.departments.tsx (2)

421-442: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate email format before submitting to the API.

The <input type="email"> provides browser validation only on native form submission, but the "Add email" button uses type="button", so browser validation never triggers. The only check is !newEmail.trim(), allowing malformed strings like "abc" to be sent to the server.

🛡️ Proposed fix to add email format validation
   const handleAddEmail = async () => {
     const emailAddress = newEmail.trim();

     if (!emailAddress) {
       return;
     }

+    // Simple RFC 5322 subset check
+    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+    if (!emailRegex.test(emailAddress)) {
+      setRoutingEmailError('Please enter a valid email address.');
+      return;
+    }
+
     setIsAddingEmail(true);
🤖 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 `@client/src/routes/`(authenticated)/admin.departments.tsx around lines 421 -
442, Update handleAddEmail to validate emailAddress format after trimming and
before setting the loading state or calling onUpsertRoutingEmail; return early
and set routingEmailError with the existing validation-error mechanism when the
address is malformed, while preserving the current empty-input and
valid-submission behavior.

316-321: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the missing auto option to the approval-mode select ApprovalMode includes 'auto', but the modal only renders 'notification' and 'approval', so an existing auto department can’t be represented correctly in the form.

🤖 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 `@client/src/routes/`(authenticated)/admin.departments.tsx around lines 316 -
321, Add the missing 'auto' option to the approval-mode select in the department
modal, using the existing ApprovalMode value and matching the 'Auto-approve'
label used by approvalLabel. Ensure departments with approvalMode set to 'auto'
render and remain selectable correctly, while preserving the existing
'notification' and 'approval' options.
server/Controllers/AccountController.cs (1)

84-104: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject backslash-prefixed return URLs
NormalizeReturnUrl still allows /\evil.com and /\/evil.com, which browsers resolve to https://evil.com/. That makes the Redirect(...) / Challenge(...RedirectUri...) flow an open redirect.

Use Url.IsLocalUrl() / LocalRedirect(), or reject a second character of / or \ here.

🤖 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 `@server/Controllers/AccountController.cs` around lines 84 - 104, Update
NormalizeReturnUrl to reject return URLs whose second character is a backslash,
including patterns such as `/\evil.com` and `/\/evil.com`, before returning the
trimmed value. Preserve the existing handling for null, whitespace, non-rooted,
and double-slash URLs.
🧹 Nitpick comments (1)
client/src/routes/(authenticated)/admin.departments.tsx (1)

457-597: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use DaisyUI's modal component instead of a custom-built modal.

The modal is constructed from raw divs with fixed inset-0 z-50, missing Escape-key handling, focus trapping, role="dialog"/aria-modal attributes, and backdrop click-to-close. DaisyUI's modal component provides these out of the box and aligns with the guideline to prefer DaisyUI components over custom UI.

As per coding guidelines: "Prefer DaisyUI components when appropriate, rather than building custom UI from scratch."

♻️ Proposed refactor using DaisyUI modal
-    <div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/40 px-4 py-8">
-      <div className="max-h-[90vh] w-full max-w-3xl overflow-y-auto rounded-[1.5rem] border border-[var(--admin-border)] bg-white p-6 shadow-2xl">
+    <div
+      className="modal modal-open"
+      onClick={onClose}
+    >
+      <div
+        className="modal-box max-h-[90vh] w-full max-w-3xl rounded-[1.5rem] border border-[var(--admin-border)] p-6"
+        onClick={(e) => e.stopPropagation()}
+      >
🤖 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 `@client/src/routes/`(authenticated)/admin.departments.tsx around lines 457 -
597, Replace the custom fixed-overlay wrapper around the Department settings
content with DaisyUI’s modal component, preserving the existing form contents
and close behavior. Use the modal structure and accessibility attributes
provided by DaisyUI, including dialog semantics, aria-modal, Escape-key
handling, focus trapping, and backdrop click-to-close; update the relevant modal
handlers around the existing onClose usage without changing the settings
functionality.

Source: Coding guidelines

🤖 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 `@client/src/routes/`(authenticated)/admin.departments.tsx:
- Around line 421-442: Update handleAddEmail to validate emailAddress format
after trimming and before setting the loading state or calling
onUpsertRoutingEmail; return early and set routingEmailError with the existing
validation-error mechanism when the address is malformed, while preserving the
current empty-input and valid-submission behavior.
- Around line 316-321: Add the missing 'auto' option to the approval-mode select
in the department modal, using the existing ApprovalMode value and matching the
'Auto-approve' label used by approvalLabel. Ensure departments with approvalMode
set to 'auto' render and remain selectable correctly, while preserving the
existing 'notification' and 'approval' options.

In `@server/Controllers/AccountController.cs`:
- Around line 84-104: Update NormalizeReturnUrl to reject return URLs whose
second character is a backslash, including patterns such as `/\evil.com` and
`/\/evil.com`, before returning the trimmed value. Preserve the existing
handling for null, whitespace, non-rooted, and double-slash URLs.

---

Nitpick comments:
In `@client/src/routes/`(authenticated)/admin.departments.tsx:
- Around line 457-597: Replace the custom fixed-overlay wrapper around the
Department settings content with DaisyUI’s modal component, preserving the
existing form contents and close behavior. Use the modal structure and
accessibility attributes provided by DaisyUI, including dialog semantics,
aria-modal, Escape-key handling, focus trapping, and backdrop click-to-close;
update the relevant modal handlers around the existing onClose usage without
changing the settings functionality.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f29fce3c-68ac-4d4c-86d2-3b0ba88e3fec

📥 Commits

Reviewing files that changed from the base of the PR and between 86f3a6d and eb1b649.

📒 Files selected for processing (4)
  • client/src/routes/(authenticated)/admin.departments.tsx
  • server.core/Data/DbInitializer.cs
  • server.core/Data/DevelopmentSeedData.cs
  • server/Controllers/AccountController.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • server.core/Data/DbInitializer.cs

@aristachauhan aristachauhan requested a review from srkirkland July 13, 2026 20:11

@jSylvestre jSylvestre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good.
You will want to put an email validator when adding department emails, but that can happen later.

@aristachauhan aristachauhan merged commit 9c8968b into main Jul 14, 2026
4 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants