Skip to content

feat. export volunteer availability as csv#194

Open
daynayoon wants to merge 2 commits intomainfrom
csv_availability
Open

feat. export volunteer availability as csv#194
daynayoon wants to merge 2 commits intomainfrom
csv_availability

Conversation

@daynayoon
Copy link
Contributor

Summary

  • Added "Export CSV" button to the Volunteers tab in Member Management
  • Fetches all volunteer data from the backend and generates a CSV file on the frontend
  • CSV includes identity fields (name, email, phone, city, province, status) and human-readable availability
  • frontend tested with dummy volunteer data, but need to be checked again.
  • feature only for admin

@daynayoon daynayoon linked an issue Mar 14, 2026 that may be closed by this pull request
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR adds a CSV export feature for volunteer availability data in the Member Management page. An "Export CSV" button is added to the Volunteers tab (admin-only, gated by the existing users: ["view"] layout guard and an authorizedProcedure on the backend). The CSV includes identity fields and a human-readable availability string formatted from the existing bitstring representation.

  • New exportAvailability tRPC query endpoint fetches all volunteers (with optional search filtering) without pagination
  • formatAvailabilityByDay utility converts availability bitstrings into daily time ranges (e.g., "Mon 09:00-12:00 | Tue None")
  • CSV generation and download are handled entirely on the frontend with proper escaping for commas, quotes, and newlines
  • Unit tests cover the availability formatting logic with correct assertions
  • The export query has no row limit, which is fine for current scale but worth monitoring

Confidence Score: 4/5

  • This PR is safe to merge — well-structured feature with proper authorization, correct CSV escaping, and good test coverage.
  • Score of 4 reflects a clean, well-implemented feature. The backend endpoint is properly authorized (admin-only), CSV escaping handles standard edge cases, and the availability formatting logic is correctly implemented with unit tests. Minor concerns: the unbounded export query could be a problem at scale, and a test comment is misleading. No critical or logic issues found.
  • No files require special attention. src/server/services/entity/volunteerService.ts has an unbounded query that may need a limit at larger scale.

Important Files Changed

Filename Overview
src/components/members/pages/view-volunteers-view.tsx Adds Export CSV button with disabled-query pattern, CSV builder, escape helper, and download trigger. Clean implementation; availability output contains `
src/utils/availabilityUtils.ts Adds formatAvailabilityByDay to convert availability bitstring into human-readable daily ranges. Edge cases (empty, invalid, end-of-day boundary) are handled correctly.
src/server/services/entity/volunteerService.ts Adds getVolunteersForExport with optional search filtering. No pagination limit, which is intentional for export but could be a concern with large datasets.
src/server/api/routers/volunteer-router.ts Adds exportAvailability query endpoint with users: ["view"] permission (admin-only). Consistent with existing authorization patterns.
src/models/api/volunteer.ts Adds VolunteerExportInput Zod schema with optional search field. Clean and minimal.
src/test/unit/availability-utils.test.ts New unit tests for formatAvailabilityByDay covering invalid input, single range, and multi-range/multi-day scenarios. Test assertions are correct, but one inline comment is misleading.

Last reviewed commit: 60335f5

Comment on lines +37 to +38
// Wednesday 18:00-20:00 (slots offset by 2 days)
const mondaySlots = [0, 1, 28, 29, 30];
Copy link
Contributor

Choose a reason for hiding this comment

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

Misleading test comment

The comment says "Wednesday 18:00-20:00" but only 2 slots are set (wednesdayOffset + 36 and wednesdayOffset + 37), which is 1 hour, not 2. The assertion on line 47 correctly expects "Wed 18:00-19:00". The comment should match:

Suggested change
// Wednesday 18:00-20:00 (slots offset by 2 days)
const mondaySlots = [0, 1, 28, 29, 30];
// Wednesday 18:00-19:00 (slots offset by 2 days)
const wednesdaySlots = [wednesdayOffset + 36, wednesdayOffset + 37];

Comment on lines +230 to +255
let builder = this.db
.select(getViewColumns(volunteerUserView))
.from(volunteerUserView)
.$dynamic();

if (hasQuery) {
builder = builder
.where(
buildSearchCondition(
[
volunteerUserView.name,
volunteerUserView.lastName,
volunteerUserView.email,
],
queryInput,
),
)
.orderBy(desc(similarity!));
} else {
builder = builder.orderBy(
volunteerUserView.lastName,
volunteerUserView.name,
);
}

const rows = await builder.execute();
Copy link
Contributor

Choose a reason for hiding this comment

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

Unbounded query may return very large result set

Unlike getVolunteersForRequest, this method has no .limit() on the query. If the volunteer table grows large, this will fetch every row in a single query, which could cause high memory usage on both the database and app server, and a slow response for the client.

Consider adding a reasonable upper bound (e.g., .limit(5000)) as a safety net, or paginating the export on the frontend side.

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.

Exporting availability as CSV

1 participant