Skip to content

Conversation

@Yemyam
Copy link
Contributor

@Yemyam Yemyam commented Dec 28, 2025

View Agencies Page

Adds admin page to view all organizations and their members.

Features

  • Agency tabs: Switch between organizations
  • Agency info: Display name, slug, and creation date
  • Member table: Show all members with names and emails
  • CSV export: Download agency and member data

Changes

Frontend:

  • New route: /admin/agencies
  • Added "View Agencies" to admin navbar
  • Created mail icon component

Backend:

  • Added getAllWithMembers query to fetch organizations with members
  • Added database relations for organizations/members

Summary by CodeRabbit

  • New Features

    • Added "View Agencies" admin navigation link
    • New Agencies admin interface with tabbed agency selection, agency details, member list, responsive layout, and empty-state handling
    • CSV export for agency data
    • New mail icon
  • Style / UI

    • Removed greeting header from the admin layout to streamline the admin page display

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds an admin "Agencies" page and navbar link, server endpoint to fetch organizations with members, database relation exports for organizations/members, a mail SVG icon, and SCSS for the agencies admin UI.

Changes

Cohort / File(s) Summary
Admin Navigation
src/app/_components/common/navbar.tsx
Added admin-only navigation item linking to /admin/agencies with a secondary-styled "View Agencies" button.
Agencies Admin UI
src/app/admin/agencies/page.tsx, src/app/admin/agencies/agencies.module.scss
New client-side AgenciesPage: fetches organizations with members via TRPC, tabs to select agency, member list table, CSV export button; added module-scoped styles (layout, tabs, grid, responsive rules).
Icon Asset
src/assets/icons/mail.tsx
New memoized Mail SVG React component with accessibility attributes and default size.
Backend API & DB Relations
src/server/api/routers/organizations.ts, src/server/db/auth-schema.ts
New admin-protected TRPC procedure getAllWithMembers returning organizations with members and user data; exported organizationRelations and memberRelations; extended userRelations to include memberships.
Admin Layout
src/app/admin/layout.tsx
Removed heading/greeting from admin layout; main area now renders children only within existing adminPage styling.

Sequence Diagram

sequenceDiagram
    autonumber
    actor User
    participant Navbar as Navbar component
    participant Browser as AgenciesPage (client)
    participant TRPC as TRPC client
    participant API as organizationRouter.getAllWithMembers
    participant DB as Database

    User->>Navbar: Click "View Agencies"
    Navbar->>Browser: Navigate to /admin/agencies
    Browser->>TRPC: request getAllWithMembers()
    TRPC->>API: invoke procedure
    API->>DB: query organizations with members & users
    DB-->>API: return nested orgs + members
    API-->>TRPC: return data
    TRPC-->>Browser: deliver organizations payload
    Browser->>Browser: set selectedAgencyId, render tabs & table
    Browser-->>User: display agencies and members

    alt User exports CSV
        User->>Browser: Click "Export to CSV"
        Browser->>Browser: generate CSV from selected agency & members
        Browser-->>User: prompt file download
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • promatty
  • burtonjong
  • Lujarios
  • JustinTan-1
  • tanvimahal
  • themaxboucher

Poem

🐰 Hopping through agencies bright,

Tabs and exports in soft moonlight,
Members listed, CSV in view,
Relations linked, the code hops too,
A little rabbit cheers for you!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Sanc 57 large view agencies pages' is vague and uses non-descriptive terms; it references a ticket ID and contains unclear phrasing like 'large view' that doesn't clearly convey what the changeset accomplishes. Revise the title to clearly describe the main change, such as 'Add admin agencies page to view organizations and members' or 'Implement agencies admin dashboard with organization and member listings'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link

@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: 4

🧹 Nitpick comments (4)
src/assets/icons/mail.tsx (1)

1-32: LGTM with a minor consistency note.

The component follows the existing icon patterns with proper accessibility attributes. Consider using #434343 for the stroke color to match other icons (e.g., edit.tsx, call.tsx) for visual consistency across the icon set.

src/app/admin/agencies/page.tsx (1)

26-32: Consider memoizing the formatDate helper.

Defining formatDate inside the component recreates it on every render. Since it has no dependencies on component state, extract it outside the component or wrap with useCallback.

🔎 Proposed fix - extract outside component
+// Format date helper - outside component
+const formatDate = (date: Date) => {
+  return new Intl.DateTimeFormat("en-US", {
+    month: "long",
+    day: "numeric",
+    year: "numeric",
+  }).format(new Date(date));
+};

 export default function AgenciesPage() {
   // ...
-  // Format date helper
-  const formatDate = (date: Date) => {
-    return new Intl.DateTimeFormat("en-US", {
-      month: "long",
-      day: "numeric",
-      year: "numeric",
-    }).format(new Date(date));
-  };
src/app/admin/agencies/agencies.module.scss (2)

17-26: Move transition to the base selector for smooth hover-out animation.

Currently the transition is defined inside :hover, which means the transition only applies when hovering in, not when hovering out. Moving it to the base .agencyTab selector ensures a smooth animation in both directions.

🔎 Proposed fix
 .agencyTab {
   border-radius: 24px;
   padding: 0.5rem 1.5rem;
   font-weight: 500;
+  transition: transform 0.2s ease;

   &:hover {
     transform: translateY(-2px);
-    transition: transform 0.2s ease;
   }
 }

43-44: Empty selector can be removed.

.userInfoSection is empty and provides no styling. Consider removing it unless it's a placeholder for future styles.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42ecab9 and d31ea8b.

📒 Files selected for processing (6)
  • src/app/_components/common/navbar.tsx
  • src/app/admin/agencies/agencies.module.scss
  • src/app/admin/agencies/page.tsx
  • src/assets/icons/mail.tsx
  • src/server/api/routers/organizations.ts
  • src/server/db/auth-schema.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-23T05:44:35.017Z
Learnt from: tanvimahal
Repo: Code-the-Change-YYC/salvationarmy PR: 25
File: src/server/api/routers/organizations.ts:38-38
Timestamp: 2025-12-23T05:44:35.017Z
Learning: In this codebase's TRPC protectedProcedure context, ctx.session has a nested structure containing both a session object and a user object. To access activeOrganizationId, use ctx.session.session.activeOrganizationId (not ctx.session.activeOrganizationId).

Applied to files:

  • src/server/api/routers/organizations.ts
🧬 Code graph analysis (5)
src/server/api/routers/organizations.ts (2)
src/server/api/trpc.ts (1)
  • protectedProcedure (122-132)
src/lib/auth.ts (1)
  • session (94-112)
src/server/db/auth-schema.ts (2)
src/server/db/booking-schema.ts (1)
  • agency (39-50)
src/lib/auth.ts (1)
  • session (94-112)
src/assets/icons/mail.tsx (6)
src/assets/icons/bell.tsx (1)
  • props (4-35)
src/assets/icons/edit.tsx (1)
  • props (4-78)
src/assets/icons/user.tsx (1)
  • props (4-48)
src/assets/icons/call.tsx (1)
  • props (4-52)
src/assets/icons/calendar.tsx (1)
  • props (4-108)
src/assets/icons/location.tsx (1)
  • props (4-110)
src/app/_components/common/navbar.tsx (1)
src/app/_components/common/button/Button.tsx (1)
  • Button (41-80)
src/app/admin/agencies/page.tsx (2)
src/trpc/react.tsx (1)
  • api (25-25)
src/app/_components/common/button/Button.tsx (1)
  • Button (41-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci
🔇 Additional comments (2)
src/app/_components/common/navbar.tsx (1)

33-35: LGTM!

The new "View Agencies" navigation item follows the existing pattern used for other admin navigation links, maintaining consistency with the codebase.

src/server/db/auth-schema.ts (1)

106-125: LGTM!

The relation definitions are correctly structured:

  • userRelations includes memberships for user → member (one-to-many)
  • organizationRelations links organization → members (one-to-many)
  • memberRelations properly defines the inverse relations with field/reference mappings

This enables the nested with queries used in getAllWithMembers.

Copy link
Contributor

@promatty promatty left a comment

Choose a reason for hiding this comment

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

can we also get a vertical line separating the columns of the table

month: "long",
day: "numeric",
year: "numeric",
}).format(new Date(date));
Copy link
Contributor

@promatty promatty Dec 29, 2025

Choose a reason for hiding this comment

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

i feel like creating a new Date object is causing slight timezone conversion issues. For example, my creation date for an org is "2025-12-23 02:31:17.386" in supabase, but the date joined says: "Date Joined December 22, 2025". if creating a new date isnt the issue, we must still figure out where this mismatch in conversion is.

admin view:
Screenshot 2025-12-28 at 11 00 12 PM

supabase item:
Screenshot 2025-12-28 at 11 00 49 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the date was being formatted from utc to our local time. I updated formatDate to use the utc timezone

Copy link
Contributor

@promatty promatty Jan 4, 2026

Choose a reason for hiding this comment

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

@Yemyam ohh i see, its a supabase timezone issue. lets actually keep it as local time in the format function then. but still, is there an issue where we are double converting this date? since it comes in as a Date type already, but we make it a new Date() as well?

);
}

const handleExportToCSV = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

the export to csv functionality isnt apart of your ticket. it will have to be a separate, dynamic component which adjusts to all the different tables itll be used in. please remove the implementation here

@promatty
Copy link
Contributor

can you also delete this line with the "hello {name}" as part of your pr lol

<h1 className={styles.title}>Hello, {session.user?.name}</h1>

@Yemyam Yemyam force-pushed the SANC-57-LARGE-view-agencies-pages branch from 6ec405b to 7ad1739 Compare January 4, 2026 20:00
Copy link

@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

🧹 Nitpick comments (4)
src/assets/icons/mail.tsx (1)

15-28: Consider using currentColor for stroke to enable color customization.

The hardcoded #424242 stroke color limits reusability. Using currentColor would allow the icon to inherit the text color from its parent, making it easier to style in different contexts.

🔎 Proposed fix
     <path
       d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z"
-      stroke="#424242"
+      stroke="currentColor"
       strokeWidth={2}
       strokeLinecap="round"
       strokeLinejoin="round"
     />
     <path
       d="M22 6L12 13L2 6"
-      stroke="#424242"
+      stroke="currentColor"
       strokeWidth={2}
       strokeLinecap="round"
       strokeLinejoin="round"
     />
src/app/admin/agencies/page.tsx (1)

27-35: Consider extracting formatDate outside the component.

Defining formatDate inside the component recreates it on every render. Moving it outside improves performance slightly and clarifies it has no dependency on component state.

🔎 Proposed fix
+// Format date helper
+const formatDate = (date: Date) => {
+  return new Intl.DateTimeFormat("en-US", {
+    month: "long",
+    day: "numeric",
+    year: "numeric",
+    timeZone: "UTC",
+  }).format(new Date(date));
+};
+
 export default function AgenciesPage() {
   const { data: organizations, isLoading } = api.organization.getAllWithMembers.useQuery();

   const [selectedAgencyId, setSelectedAgencyId] = useState<string | null>(null);

   // Set the first agency as selected when data loads
   const selectedAgency =
     organizations?.find((org) => org.id === selectedAgencyId) ?? organizations?.[0];

   // Update selected ID when organizations load
   useEffect(() => {
     if (organizations && organizations.length > 0 && !selectedAgencyId) {
       setSelectedAgencyId(organizations[0]?.id ?? null);
     }
   }, [organizations, selectedAgencyId]);

-  // Format date helper
-  const formatDate = (date: Date) => {
-    return new Intl.DateTimeFormat("en-US", {
-      month: "long",
-      day: "numeric",
-      year: "numeric",
-      timeZone: "UTC",
-    }).format(new Date(date));
-  };
src/app/admin/agencies/agencies.module.scss (2)

17-26: Move transition outside of &:hover to enable smooth hover-out animation.

The transition is defined inside the :hover pseudo-class, so the transform animates on hover-in but snaps back instantly on hover-out.

🔎 Proposed fix
 .agencyTab {
   border-radius: 24px;
   padding: 0.5rem 1.5rem;
   font-weight: 500;
+  transition: transform 0.2s ease;

   &:hover {
     transform: translateY(-2px);
-    transition: transform 0.2s ease;
   }
 }

43-44: Empty .userInfoSection class can be removed or populated.

This class is currently empty. If no styles are planned, consider removing it to avoid dead code.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d31ea8b and 7ad1739.

📒 Files selected for processing (7)
  • src/app/_components/common/navbar.tsx
  • src/app/admin/agencies/agencies.module.scss
  • src/app/admin/agencies/page.tsx
  • src/app/admin/layout.tsx
  • src/assets/icons/mail.tsx
  • src/server/api/routers/organizations.ts
  • src/server/db/auth-schema.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/server/db/auth-schema.ts
  • src/app/_components/common/navbar.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-23T05:44:37.925Z
Learnt from: tanvimahal
Repo: Code-the-Change-YYC/salvationarmy PR: 25
File: src/server/api/routers/organizations.ts:38-38
Timestamp: 2025-12-23T05:44:37.925Z
Learning: In this codebase's TRPC protectedProcedure context, ctx.session has a nested structure containing both a session object and a user object. To access activeOrganizationId, use ctx.session.session.activeOrganizationId (not ctx.session.activeOrganizationId).

Applied to files:

  • src/server/api/routers/organizations.ts
🧬 Code graph analysis (2)
src/server/api/routers/organizations.ts (1)
src/server/api/trpc.ts (1)
  • adminProcedure (134-146)
src/app/admin/agencies/page.tsx (2)
src/trpc/react.tsx (1)
  • api (25-25)
src/server/db/auth-schema.ts (1)
  • member (80-90)
🔇 Additional comments (4)
src/app/admin/layout.tsx (1)

7-15: LGTM!

The greeting removal has been addressed as requested. The layout now cleanly wraps children with the admin navbar and HydrateClient.

src/server/api/routers/organizations.ts (1)

28-47: LGTM!

The endpoint now correctly uses adminProcedure to restrict access to admin users, addressing the previous review concern about protecting member PII. The implementation follows the established pattern in this router.

src/app/admin/agencies/page.tsx (2)

21-25: LGTM!

The setSelectedAgencyId call has been correctly moved to a useEffect hook, addressing the previous review concern about calling setState during render.


45-54: LGTM!

Good addition of the empty state message when no organizations exist, addressing the previous review feedback.

Comment on lines +60 to +67
<Button
variant="outline"
color="dark"
leftSection={<Grid />}
className={styles.exportButton}
>
Export to CSV File
</Button>
Copy link

@coderabbitai coderabbitai bot Jan 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Export button has no functionality - consider removing or disabling it.

The "Export to CSV File" button is rendered but has no onClick handler. Per the earlier review comment that CSV export should be a separate, dynamic component, this button should either be removed or disabled with a tooltip indicating it's not yet implemented.

🔎 Option 1: Remove the button
       <div className={styles.header}>
         <Title order={2}>View all Agencies</Title>
-        <Button
-          variant="outline"
-          color="dark"
-          leftSection={<Grid />}
-          className={styles.exportButton}
-        >
-          Export to CSV File
-        </Button>
       </div>
🔎 Option 2: Disable with indication
         <Button
           variant="outline"
           color="dark"
           leftSection={<Grid />}
           className={styles.exportButton}
+          disabled
+          title="Coming soon"
         >
           Export to CSV File
         </Button>
📝 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
<Button
variant="outline"
color="dark"
leftSection={<Grid />}
className={styles.exportButton}
>
Export to CSV File
</Button>
<Button
variant="outline"
color="dark"
leftSection={<Grid />}
className={styles.exportButton}
disabled
title="Coming soon"
>
Export to CSV File
</Button>
🤖 Prompt for AI Agents
In src/app/admin/agencies/page.tsx around lines 60 to 67, the "Export to CSV
File" Button is rendered without any onClick behavior; either remove the Button
or disable it with an explanatory tooltip until the CSV export component is
implemented. To fix: delete the Button JSX if you choose removal, or set the
Button to disabled and wrap it with a Tooltip (or add a title attribute) that
clearly indicates "CSV export not yet implemented" so users know why it’s
inactive.

Copy link
Contributor

Choose a reason for hiding this comment

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

keep the button

Copy link

Choose a reason for hiding this comment

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

@promatty, understood! I'll leave the button as is. ✓

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