Skip to content

restyled components used react-pdf-tailwind and react-pdf-table - #125

Open
atungcod wants to merge 16 commits into
mainfrom
react-pdf-tailwind-restyle
Open

restyled components used react-pdf-tailwind and react-pdf-table#125
atungcod wants to merge 16 commits into
mainfrom
react-pdf-tailwind-restyle

Conversation

@atungcod

@atungcod atungcod commented Nov 24, 2025

Copy link
Copy Markdown
Collaborator

-for table components: the header row is slightly off from rows below it (check HR tags)
-text in cells need to be centered
-adjust TR tag styles to include a smaller height property across all cells

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a demo page for viewing and testing Program Area Grids rendered as PDFs, complete with sample activities, schedules, and program area data for demonstration
    • Integrated Tailwind CSS-based styling support for PDF documents, enabling consistent and customizable visual design with extended color palette options

@coderabbitai

coderabbitai Bot commented Nov 24, 2025

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds react-pdf-tailwind dependency and creates a Tailwind-based styling utility wrapper for React PDF components. Introduces a demo page that renders a PDF-based Program Area Grid with mock program area data and activity schedules.

Changes

Cohort / File(s) Summary
Dependency Management
package.json
Adds react-pdf-tailwind at version ^3.0.0 as a new dependency.
PDF Styling Utility
src/utils/reactPdfTailwind.ts
Creates a Tailwind CSS wrapper (tw) for react-pdf using a customized theme that includes Helvetica font stack and an extended color palette (header, bunk, gray variants).
Demo Implementation
src/app/demo/program-area-grid/page.tsx
Adds a new demo page component that renders a PDF viewer with mock program area data (four activity blocks with nested assignments across multiple program areas and age groups).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • generate Program Area grid #61: Directly related—adds a ProgramAreaGrid demo page implementation that depends on the react-pdf-tailwind utility helper created in these changes.

Suggested reviewers

  • nkanchinadam

Poem

🐰 A rabbit hops through PDFs bright,
With Tailwind magic, styled just right,
Program areas dance in bunks of blue,
React-pdf-tailwind—hooray, we're through! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions using react-pdf-tailwind and react-pdf-table, but the changeset only shows react-pdf-tailwind integration without react-pdf-table implementation. Update the title to accurately reflect the actual changes. Consider: 'Add react-pdf-tailwind utility and Program Area Grid demo page' or remove mention of react-pdf-table if not implemented.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch react-pdf-tailwind-restyle

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.

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

Caution

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

⚠️ Outside diff range comments (2)
src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)

2-45: Remove unused StyleSheet import and dead StyleSheet block (fixes lint).

StyleSheet is no longer used since the old styles are commented out, which causes the eslint no-unused-vars error and breaks the pipeline. You can safely delete both the StyleSheet import and the commented StyleSheet.create block.

-import { Document, Page, Text, View, StyleSheet } from "@react-pdf/renderer";
+import { Document, Page, Text, View } from "@react-pdf/renderer";
-
-/*const styles = StyleSheet.create({
-  page: { padding: 40, fontSize: 12, fontFamily: "Helvetica" },
-  ...
-  activityDesc: { fontSize: 11 },
-}); */
src/features/scheduling/exporting/EmployeeGrid.tsx (1)

59-62: Unsafe cast of getFreeplayAssignmentId result may throw at runtime

The function getFreeplayAssignmentId returns number[] | number | string | null, but at line 61 it is blindly cast to number[] without a type guard. The code at lines 60–61:

const fpBuddyIds = getFreeplayAssignmentId(freeplay, employee.id);
const fpBuddies = fpBuddyIds ? (fpBuddyIds as number[]).map(id => campers.find(c => c.id === id)) : [];

will throw at runtime if getFreeplayAssignmentId returns a string (post ID) or single number (staff ID), since .map() does not exist on those types.

Guard on the runtime type instead of casting:

-            const fpBuddyIds = getFreeplayAssignmentId(freeplay, employee.id);
-            const fpBuddies = fpBuddyIds ? (fpBuddyIds as number[]).map(id => campers.find(c => c.id === id)) : [];
+            const fpAssignment = getFreeplayAssignmentId(freeplay, employee.id);
+            const fpBuddyIds = Array.isArray(fpAssignment) ? fpAssignment : [];
+            const fpBuddies = fpBuddyIds.map(id => campers.find(c => c.id === id));

If posts (string IDs) or other cases need different handling, add explicit branches for those cases.

🧹 Nitpick comments (12)
src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)

10-18: Tailwind-based PDF styling and section props look consistent; minor cleanup only.

The createTw config and usage on <Page>, header rows, and activity text all look good and keep Helvetica as the base font. The new sectionType, sectionName, and conditional ageGroup props are wired correctly into the header and BUNDLE-only activity filtering. The inner <View style={tw("")}> can be dropped since it carries no classes, but that’s purely cosmetic.

Also applies to: 62-90

src/features/scheduling/ProgramAreaGrid.tsx (1)

59-72: Confirm that showing only the first activity per area/block is intended.

renderActivityText intentionally picks activities[0] and ignores any additional activities in that area/block. If prior behavior showed all activities, this is a functional change that may hide data in the PDF; if that’s desired for readability, all good, otherwise consider joining names or adding a multi-line rendering.

src/features/scheduling 2/generation/FreeplayScheduler.ts (1)

3-44: Fluent builder surface for FreeplayScheduler looks consistent; methods are ready for future logic.

The default state and with* chainable methods give a clear, composable API aligned with the other schedulers introduced in the PR. The stubbed withOtherFreeplays, assignPosts, and assignCampers are fine placeholders for now; just ensure callers don’t assume they perform real assignments until the logic is filled in.

src/features/scheduling 2/generation/BunkJamboreeScheduler.ts (1)

3-30: BunkJamboreeScheduler API matches the other schedulers and is ready for implementation.

The defaults and fluent with* / forBlocks methods provide a clean configuration surface. The three assign* methods are currently no-ops, which is fine as long as they’re treated as TODOs and not relied on for real allocations yet.

src/features/scheduling 2/generation/schedulingUtils.ts (1)

10-47: Type guards and ID helpers look solid; consider aliasing getFreeplayAssignmentId’s union.

The bundle/jamboree, assignment-shape, and attendee-ID helpers are idiomatic and match the shapes in sessionTypes. getFreeplayAssignmentId’s logic for posts vs buddy mappings is clear; you may want to export a named union type (e.g., type FreeplayAssignmentRef = string | number | number[] | null) so callers can share and re-use the return type instead of repeating the literal union.

src/features/scheduling 2/generation/BundleScheduler.ts (1)

17-56: Builder API is clear, but all assign* methods are currently no‑ops

The fluent setters/read‑only configuration are fine, but every assign* method (assignProgramCounselors, assignPeriodsOff, assignOcpChats, assignSwimBlocks, assignCampers, assignStaff, assignAdmins) just returns this without touching schedule or any of the collections. That’s OK as a scaffold, but if these are already being wired into the scheduling flow, callers may think work is happening when it is not.

Consider either:

  • Implementing the first slice of logic, or
  • Making it explicit (via TODOs/JSDoc) that these are placeholders and must not be relied on yet.
src/features/scheduling 2/exporting/CamperGrid.tsx (3)

16-141: Trim or reuse unused style definitions

Several style rules (page, cell, bold, table, headerCell, bunkCell, dataCell, twoColumnPage, leftColumn, rightColumn) are currently unused. Consider removing them or wiring them into the layout to keep this component focused and easier to maintain.


155-163: Avoid mutating campers props and recomputing blocks per row

  • Line 155: campers.sort(...) mutates props in place; safer to sort a shallow copy: const sortedCampers = [...campers].sort(...) and iterate that.
  • Line 183: const blocksArray = Object.values(schedule.blocks); is recomputed for every camper; compute once outside the campers.map to avoid unnecessary work.
-  campers.sort((a, b) => {
+  const sortedCampers = [...campers].sort((a, b) => {
@@
-        {campers.map((camper) => {
-          const blocksArray = Object.values(schedule.blocks);
+        {sortedCampers.map((camper) => {
+          const blocksArray = Object.values(schedule.blocks);

Also applies to: 182-219


173-177: Consider stabilizing block column order

Object.keys(schedule.blocks) relies on object insertion order. If blocks can be created/edited dynamically, you may want a stable sort (e.g., numeric or lexicographic) so column order doesn’t jump unexpectedly in exported PDFs.

-          {Object.keys(schedule.blocks).map((blockId) => (
+          {Object.keys(schedule.blocks).sort().map((blockId) => (
src/features/scheduling 2/generation/SessionScheduler.ts (1)

4-24: Duplicate SessionScheduler implementation in two locations

This SessionScheduler mirrors the existing one under src/features/scheduling/generation/SessionScheduler.ts. Maintaining two copies risks divergence.

Consider exporting a single shared implementation (e.g., move to a common module and re-export) rather than duplicating the class.

src/features/scheduling 2/exporting/BlockRatiosGrid.tsx (1)

128-141: Optional: Avoid recomputing isIndividualAssignments and add basic safety on attendee lookup

Within each row you call isIndividualAssignments(activity.assignments) multiple times and assume attendeesById[id] always exists. For robustness and small perf win:

-                const camperOrBunkIds = isIndividualAssignments(activity.assignments) ? activity.assignments.camperIds : activity.assignments.bunkNums;
-                const employeeIds = isIndividualAssignments(activity.assignments) ? [...activity.assignments.staffIds, ...activity.assignments.adminIds] : activity.assignments.adminIds;
+                const individual = isIndividualAssignments(activity.assignments);
+                const camperOrBunkIds = individual
+                  ? activity.assignments.camperIds
+                  : activity.assignments.bunkNums;
+                const employeeIds = individual
+                  ? [...activity.assignments.staffIds, ...activity.assignments.adminIds]
+                  : activity.assignments.adminIds;
@@
-                      {camperOrBunkIds.map(camperOrBunkId => <Text key={camperOrBunkId}>{isIndividualAssignments(activity.assignments) ? getFullName(attendeesById[camperOrBunkId]) : `Bunk ${camperOrBunkId}`}</Text>)}
+                      {camperOrBunkIds.map(camperOrBunkId => (
+                        <Text key={camperOrBunkId}>
+                          {individual
+                            ? (attendeesById[camperOrBunkId] ? getFullName(attendeesById[camperOrBunkId]) : "")
+                            : `Bunk ${camperOrBunkId}`}
+                        </Text>
+                      ))}
src/features/scheduling 2/generation/NonBunkJamboreeScheduler.ts (1)

9-41: Clarify stubbed scheduling methods or add TODOs

assignPeriodsOff, assignCampers, and assignCounselors are no-ops returning this. That’s fine as scaffolding, but it’s easy to forget they’re unimplemented.

Add brief comments/TODOs indicating intended future behavior, or throw if called before implementation, so misuse is more obvious during development:

-  assignPeriodsOff(): NonBunkJamboreeScheduler { return this; }
+  // TODO: Implement NON-BUNK-JAMBO staff/admin period-off assignment.
+  assignPeriodsOff(): NonBunkJamboreeScheduler { return this; }
@@
-  assignCampers(): NonBunkJamboreeScheduler { return this; }
+  // TODO: Implement camper assignment for NON-BUNK-JAMBO blocks.
+  assignCampers(): NonBunkJamboreeScheduler { return this; }
@@
-  assignCounselors(): NonBunkJamboreeScheduler { return this; }
+  // TODO: Implement counselor assignment for NON-BUNK-JAMBO blocks.
+  assignCounselors(): NonBunkJamboreeScheduler { return this; }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 789b8d3 and 685e70c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • package.json (2 hunks)
  • src/components/SmallDirectoryBlock.tsx (1 hunks)
  • src/components/demo/page.tsx (1 hunks)
  • src/features/scheduling 2/exporting/BlockRatiosGrid.tsx (1 hunks)
  • src/features/scheduling 2/exporting/CamperGrid.tsx (1 hunks)
  • src/features/scheduling 2/exporting/CamperPreferencesSheet.tsx (1 hunks)
  • src/features/scheduling 2/exporting/EmployeeGrid.tsx (1 hunks)
  • src/features/scheduling 2/generation/BundleScheduler.ts (1 hunks)
  • src/features/scheduling 2/generation/BunkJamboreeScheduler.ts (1 hunks)
  • src/features/scheduling 2/generation/FreeplayScheduler.ts (1 hunks)
  • src/features/scheduling 2/generation/NonBunkJamboreeScheduler.ts (1 hunks)
  • src/features/scheduling 2/generation/SessionScheduler.ts (1 hunks)
  • src/features/scheduling 2/generation/schedulingUtils.ts (1 hunks)
  • src/features/scheduling/ProgramAreaGrid.tsx (2 hunks)
  • src/features/scheduling/exporting/BlockRatiosGrid.tsx (5 hunks)
  • src/features/scheduling/exporting/CamperGrid.tsx (3 hunks)
  • src/features/scheduling/exporting/CamperPreferencesSheet.tsx (4 hunks)
  • src/features/scheduling/exporting/EmployeeGrid.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
src/features/scheduling 2/exporting/EmployeeGrid.tsx (3)
src/types/sessionTypes.ts (5)
  • SchedulingSectionType (52-52)
  • SectionScheduleID (76-76)
  • Freeplay (132-135)
  • CamperAttendeeID (26-26)
  • AdminAttendeeID (41-41)
src/features/scheduling 2/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
src/utils/personUtils.ts (1)
  • getFullName (3-5)
src/components/SmallDirectoryBlock.tsx (1)
src/types/personTypes.ts (4)
  • CamperID (27-27)
  • StaffID (50-50)
  • AdminID (52-52)
  • UserRole (18-18)
src/features/scheduling 2/exporting/CamperPreferencesSheet.tsx (2)
src/types/sessionTypes.ts (4)
  • SchedulingSectionType (52-52)
  • SectionSchedule (72-75)
  • AgeGroup (144-144)
  • BundleActivity (105-108)
src/features/scheduling 2/generation/schedulingUtils.ts (1)
  • isBundleActivity (10-12)
src/features/scheduling 2/exporting/BlockRatiosGrid.tsx (3)
src/types/sessionTypes.ts (5)
  • SchedulingSectionType (52-52)
  • SectionSchedule (72-75)
  • CamperAttendeeID (26-26)
  • StaffAttendeeID (35-35)
  • AdminAttendeeID (41-41)
src/features/scheduling 2/generation/schedulingUtils.ts (3)
  • getAttendeesById (43-47)
  • isBundleActivity (10-12)
  • isIndividualAssignments (35-37)
src/utils/personUtils.ts (1)
  • getFullName (3-5)
src/features/scheduling 2/generation/BundleScheduler.ts (2)
src/types/sessionTypes.ts (5)
  • SectionSchedule (72-75)
  • CamperAttendeeID (26-26)
  • StaffAttendeeID (35-35)
  • AdminAttendeeID (41-41)
  • SectionPreferences (150-152)
src/features/scheduling/generation/BundleScheduler.ts (1)
  • BundleScheduler (3-57)
src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)
src/features/scheduling/generation/schedulingUtils.ts (1)
  • isBundleActivity (10-12)
src/features/scheduling/exporting/BlockRatiosGrid.tsx (1)
src/features/scheduling/generation/schedulingUtils.ts (2)
  • isBundleActivity (10-12)
  • isIndividualAssignments (35-37)
src/features/scheduling/exporting/CamperGrid.tsx (1)
src/features/scheduling/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
src/features/scheduling 2/generation/SessionScheduler.ts (2)
src/types/sessionTypes.ts (5)
  • SessionID (11-11)
  • SectionID (55-55)
  • StaffAttendeeID (35-35)
  • AdminAttendeeID (41-41)
  • NightShiftID (49-49)
src/features/scheduling/generation/SessionScheduler.ts (1)
  • SessionScheduler (4-25)
src/features/scheduling 2/generation/BunkJamboreeScheduler.ts (1)
src/types/sessionTypes.ts (4)
  • SectionSchedule (72-75)
  • BunkID (130-130)
  • AdminAttendeeID (41-41)
  • SectionPreferences (150-152)
src/features/scheduling 2/generation/NonBunkJamboreeScheduler.ts (1)
src/types/sessionTypes.ts (5)
  • SectionSchedule (72-75)
  • CamperAttendeeID (26-26)
  • StaffAttendeeID (35-35)
  • AdminAttendeeID (41-41)
  • SectionPreferences (150-152)
src/features/scheduling/ProgramAreaGrid.tsx (1)
src/types/sessionTypes.ts (2)
  • BundleActivity (105-108)
  • ProgramAreaID (98-98)
src/features/scheduling 2/exporting/CamperGrid.tsx (3)
src/types/sessionTypes.ts (3)
  • SchedulingSectionType (52-52)
  • SectionScheduleID (76-76)
  • Freeplay (132-135)
src/utils/personUtils.ts (1)
  • getFullName (3-5)
src/features/scheduling 2/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
src/features/scheduling 2/generation/schedulingUtils.ts (1)
src/types/sessionTypes.ts (6)
  • AttendeeID (14-14)
  • BundleActivity (105-108)
  • JamboreeActivity (100-103)
  • Freeplay (132-135)
  • IndividualAssignments (114-118)
  • BunkAssignments (120-123)
🪛 GitHub Actions: Lint & Build on PR
src/components/SmallDirectoryBlock.tsx

[error] 23-23: Step 'npm run lint' failed. ESLint: 'CamperAttendee' is defined but never used. (no-unused-vars)

src/features/scheduling/exporting/CamperPreferencesSheet.tsx

[error] 2-2: Step 'npm run lint' failed. ESLint: 'StyleSheet' is defined but never used. (no-unused-vars)

src/features/scheduling/exporting/BlockRatiosGrid.tsx

[error] 2-2: Step 'npm run lint' failed. ESLint: 'StyleSheet' is defined but never used. (no-unused-vars)

src/features/scheduling/exporting/EmployeeGrid.tsx

[error] 9-9: Step 'npm run lint' failed. ESLint: 'View' is defined but never used. (no-unused-vars)

src/features/scheduling/exporting/CamperGrid.tsx

[error] 9-9: Step 'npm run lint' failed. ESLint: 'View' is defined but never used. (no-unused-vars)


[error] 170-170: Step 'npm run lint' failed. ESLint: 'staff' is assigned a value but never used. (no-unused-vars)

src/features/scheduling/ProgramAreaGrid.tsx

[error] 9-9: Step 'npm run lint' failed. ESLint: 'TH' is defined but never used. (no-unused-vars)

🔇 Additional comments (9)
src/components/demo/page.tsx (1)

1-11: LGTM!

The demo page component is clean and straightforward. The use of Mantine's Container for layout is appropriate.

src/components/SmallDirectoryBlock.tsx (4)

25-34: LGTM!

Type definitions are clear and well-structured. The optional bunk property on CamperWithBunk appropriately extends CamperID.


47-60: LGTM!

The filtering logic correctly handles both role-based and name-based filtering with case-insensitive search. The slicing logic appropriately respects the showAll flag.


121-125: LGTM!

The conditional rendering of bunk numbers uses proper type guards (person.role === 'CAMPER' && 'bunk' in person) to safely access the optional bunk property.


141-158: LGTM!

The view more/less button logic correctly shows the button only when there are more items than initialVisibleCount, and the icon appropriately changes based on the showAll state.

package.json (1)

16-16: Dependencies verified—no issues found.

All three newly added packages exist at the specified versions, are currently at their latest releases, and have no known security vulnerabilities. The dependency versions are valid and safe to use.

src/features/scheduling 2/generation/schedulingUtils.ts (1)

3-8: Double-check conflict rule for non-camper roles (requires both nono and yesyes).

For campers you flag a conflict if any nonoList id appears, but for staff/admins you currently require that otherAttendeeIds contain at least one nonoList id and at least one yesyesList id. That’s a pretty specific condition; if the intent was “any no-no present is a conflict” for all roles, the non-camper branch should likely only check nonoList.

return attendee.nonoList.some((id) => otherAttendeeIds.includes(id));
src/features/scheduling 2/exporting/CamperPreferencesSheet.tsx (1)

38-95: CamperPreferencesSheet generic and filtering logic look solid

The generic props, conditional ageGroup typing, and the sectionType === "BUNDLE" filter all align with the session types and isBundleActivity guard. Rendering of titles, blocks, and descriptions is clear and matches the data model.

src/features/scheduling 2/exporting/BlockRatiosGrid.tsx (1)

14-72: No changes required — CSS shorthand borders are valid in @react-pdf/renderer

@react-pdf/renderer supports both the CSS-like shorthand "border" (e.g. "1pt solid black") and the explicit properties borderWidth/borderStyle/borderColor. The code's use of border: '1pt solid black' and individual border properties (e.g., borderTop: '1pt solid black') is correct and does not need refactoring.

Likely an incorrect or invalid review comment.

Comment thread src/components/SmallDirectoryBlock.tsx Outdated
Comment on lines +62 to +72
const handleViewMore = () => {
if (showAll) {
setShowAll(false);
setVisibleCount(initialVisibleCount);
} else {
setVisibleCount((prev) => Math.min(prev + loadMoreCount, filteredCount));
if (visibleCount + loadMoreCount >= filteredCount) {
setShowAll(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.

⚠️ Potential issue | 🔴 Critical

Fix stale state reference in view more handler.

Line 68 reads visibleCount after it was updated in line 67, but state updates in React are asynchronous. The condition evaluates against the stale (old) value, causing the "Show all" toggle to activate one step late.

Apply this diff to use the computed new value:

   const handleViewMore = () => {
     if (showAll) {
       setShowAll(false);
       setVisibleCount(initialVisibleCount);
     } else {
-      setVisibleCount((prev) => Math.min(prev + loadMoreCount, filteredCount));
-      if (visibleCount + loadMoreCount >= filteredCount) {
+      const newCount = Math.min(visibleCount + loadMoreCount, filteredCount);
+      setVisibleCount(newCount);
+      if (newCount >= filteredCount) {
         setShowAll(true);
       }
     }
   };
🤖 Prompt for AI Agents
In src/components/SmallDirectoryBlock.tsx around lines 62 to 72, the
handleViewMore reads visibleCount immediately after calling setVisibleCount so
it uses a stale value; compute the new visible count first and use that to
update state and decide whether to setShowAll. Concretely: when expanding,
compute newCount = Math.min(visibleCount + loadMoreCount, filteredCount) (or use
the functional updater form to derive newCount), call setVisibleCount(newCount),
then call setShowAll(newCount >= filteredCount); keep the collapse branch
unchanged.

Comment thread src/components/SmallDirectoryBlock.tsx Outdated
Comment on lines +75 to +82
const blockIds = Object.keys(schedule.blocks).sort()
const blocks = blockIds.map(blockId => schedule.blocks[blockId]);

const attendeesById = getAttendeesById([...campers, ...staff, ...admins]);

// Find the maximum number of activities across all blocks
const maxActivities = Math.max(...blocks.map(block => block.activities.length));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Handle empty schedule.blocks to avoid Math.max(...[]) crash

If schedule.blocks is empty:

  • Line 81: Math.max(...blocks.map(...)) becomes Math.max() with no arguments, yielding -Infinity.
  • Line 95: Array.from({ length: maxActivities }) then throws due to invalid (negative, non-finite) length.

Add a guard to early-return or skip the activity rows when there are no blocks:

-  const maxActivities = Math.max(...blocks.map(block => block.activities.length));
+  const maxActivities = blocks.length
+    ? Math.max(...blocks.map(block => block.activities.length))
+    : 0;
@@
-      {/* Render activities row by row across all blocks */}
-      {Array.from({ length: maxActivities }).map((_, activityIndex) => {
+      {/* Render activities row by row across all blocks */}
+      {maxActivities > 0 && Array.from({ length: maxActivities }).map((_, activityIndex) => {

Also applies to: 95-101

🤖 Prompt for AI Agents
In src/features/scheduling 2/exporting/BlockRatiosGrid.tsx around lines 75-82
and 95-101, the code calls Math.max over blocks.map(...) which crashes when
schedule.blocks is empty; add a guard to handle empty blocks by either
early-returning a sensible JSX (null or a “no blocks” placeholder) or by
defaulting maxActivities to 0 (e.g., compute maxActivities only if blocks.length
> 0, otherwise 0) and skip rendering the activity rows when maxActivities === 0
so Array.from({ length: maxActivities }) is never called with an invalid length.

Comment on lines +171 to +237
{employees.map((employee) => {
const fpBuddyIds = getFreeplayAssignmentId(freeplay, employee.id);
const fpBuddies = fpBuddyIds ? (fpBuddyIds as number[]).map(id => campers.find(c => c.id === id)) : [];

let apoText: string = '-';
for (const period of Object.keys(schedule.alternatePeriodsOff)) {
if (schedule.alternatePeriodsOff[period].includes(employee.id)) {
apoText = period;
break;
}
}

return (
<View key={employee.id} style={styles.row}>
{/* Name column - Use dataCell style */}
<View style={styles.dataCell}>
<Text>
{employee.name.firstName} {employee.name.lastName[0]}.
</Text>
</View>

{/* Block assignments - Use dataCell style */}
{Object.entries(schedule.blocks).map(([blockId, block]) => {
let activityText;
if (block.periodsOff.includes(employee.id)) {
activityText = "OFF";
} else if (employee.role === "STAFF") {
const activity = block.activities.find((act) =>
isIndividualAssignments(act.assignments)
? act.assignments.staffIds.includes(employee.id)
: act.assignments.bunkNums.includes(employee.bunk)
);
activityText = activity
? isBundleActivity(activity)
? activity.programArea.id
: activity.name
: "-";
} else {
const activity = block.activities.find((act) =>
act.assignments.adminIds.includes(employee.id)
);
activityText = activity
? isBundleActivity(activity)
? activity.programArea.id
: activity.name
: "-";
}

return (
<View key={blockId} style={styles.cell}>
<Text>{activityText}</Text>
</View>
);
})}

<View style={styles.dataCell}>
<Text>
{apoText}
</Text>
</View>

{/* Freeplay assignment - Use dataCell style */}
<View style={styles.dataCell}>
<Text>
{fpBuddies.map(fpBuddy => fpBuddy ? getFullName(fpBuddy) : "").join(", ")}
</Text>
</View>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix getFreeplayAssignmentId handling to avoid runtime crash and support all return types

getFreeplayAssignmentId can return number[] | number | string | null, but the code assumes it’s always a number[]:

const fpBuddyIds = getFreeplayAssignmentId(freeplay, employee.id);
const fpBuddies = fpBuddyIds ? (fpBuddyIds as number[]).map(...) : [];

For employees assigned via freeplay.posts, this will be a string postId, so calling .map on it will throw at runtime. Similarly, a bare number would also explode.

Refactor to narrow on the actual type before mapping, e.g.:

const assignment = getFreeplayAssignmentId(freeplay, employee.id);

let fpBuddies: CamperAttendeeID[] = [];
let fpPostText = "";

if (Array.isArray(assignment)) {
  fpBuddies = assignment
    .map(id => campers.find(c => c.id === id))
    .filter((c): c is CamperAttendeeID => !!c);
} else if (typeof assignment === "string") {
  fpPostText = assignment; // or whatever label you want for admin/staff posts
}

// ...

<Text>
  {fpBuddies.length
    ? fpBuddies.map(b => getFullName(b)).join(", ")
    : fpPostText || "-"}
</Text>

This preserves the buddy-name behavior while safely handling string/number/null cases.

🤖 Prompt for AI Agents
In src/features/scheduling 2/exporting/EmployeeGrid.tsx around lines 171-237,
the code assumes getFreeplayAssignmentId always returns number[] and calls .map,
which crashes for string/number/null returns; change to narrow by type: if
Array.isArray(result) map each id to campers.find(...) and filter out undefined,
if typeof result === "number" treat as a single id and find that camper, if
typeof result === "string" treat as a post label (keep as text), and if
null/undefined show "-" ; then render either the joined buddy full-names (if
any) or the post text or "-" accordingly.

Comment thread src/features/scheduling/exporting/BlockRatiosGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/CamperGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/EmployeeGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/EmployeeGrid.tsx Outdated
Comment thread src/features/scheduling/ProgramAreaGrid.tsx Outdated

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

Caution

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

⚠️ Outside diff range comments (2)
src/features/scheduling/exporting/BlockRatiosGrid.tsx (1)

92-99: Guard Math.max against empty schedule.blocks to prevent runtime error.

When blocks is empty, Math.max(...[]) returns -Infinity, causing Array.from({ length: -Infinity }) at line 106 to throw a TypeError. Apply the suggested guard:

const maxActivities = blocks.length
  ? Math.max(...blocks.map((block) => block.activities.length))
  : 0;

Additionally, three Tailwind class strings are missing spaces:

  • Line 138: justify-centerp-[5px]justify-center p-[5px]
  • Line 139: justify-centerborderjustify-center border
  • Line 159: justify-centertext-[6px]justify-center text-[6px]

Also add defensive null checks at lines 174 and 187 before dereferencing attendee and employee, as they may be undefined from the attendeesById lookup.

src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)

20-45: Remove commented-out code.

The old StyleSheet-based styles are no longer needed after the migration to react-pdf-tailwind. Version control preserves the history if you need to reference it later.

Apply this diff to remove the commented code:

-const tw = createTw({
-  theme: {
-    fontFamily: {
-      sans: ["Helvetica"],
-    },
-  },
-});
-
-/*const styles = StyleSheet.create({
-  page: { padding: 40, fontSize: 12, fontFamily: "Helvetica" },
-  title: {
-    fontSize: 18,
-    textAlign: "center",
-    marginBottom: 16,
-    fontWeight: "bold",
-  },
-  nameRow: {
-    flexDirection: "row",
-    justifyContent: "space-between",
-    marginBottom: 10,
-  },
-  block: { marginTop: 12 },
-  blockTitle: { fontSize: 14, fontWeight: "bold", marginBottom: 4 },
-  activity: {
-    marginLeft: 10,
-    marginBottom: 6,
-    display: "flex",
-    flexDirection: "row",
-    justifyContent: "space-between",
-  },
-  activityInfo: {},
-  activityDesc: { fontSize: 11 },
-}); */
+const tw = createTw({
+  theme: {
+    fontFamily: {
+      sans: ["Helvetica"],
+    },
+  },
+});
♻️ Duplicate comments (3)
src/features/scheduling/exporting/BlockRatiosGrid.tsx (1)

103-139: Normalize table header structure and fix Tailwind typos.

  1. Header: use a TR with TH cells rather than TH wrapping TRs.
    Current markup:

    <TH style={tw("text-center items-center justify-center flex-row min-h-[20px]")}>
      {blockIds.map(... => (
        <TR ...>...</TR>
      ))}
    </TH>

    is unusual for table semantics and for @ag-media/react-pdf-table. Prefer a single header row:

    <TR style={tw("flex-row min-h-[20px]")}>
      {blockIds.map((blockId) => (
        <TH
          key={blockId}
          style={tw(
            "flex-1 border border-black bg-gray-light p-[5px] text-center items-center justify-center font-bold text-[8px]"
          )}
        >
          <Text>BLOCK {blockId}</Text>
        </TH>
      ))}
    </TR>
  2. Fix Tailwind spacing typos so utilities apply correctly.

    • Activity header empty cell:

  • if (!activity) return <TD key={header-${blockId}-${activityIndex}} style={[tw("flex-1 border border-black text-center items-center justify-centerp-[5px] text-[6px]"), { backgroundColor: rowBgColor, textAlign: 'center' }]} />
  • if (!activity) return <TD key={header-${blockId}-${activityIndex}} style={[tw("flex-1 border border-black text-center items-center justify-center p-[5px] text-[6px]"), { backgroundColor: rowBgColor, textAlign: 'center' }]} />
    
    - Activity header populated cell:
    
    ```diff
    
  • style={[tw("flex-1 text-center items-center justify-centerborder border-black p-[5px] text-[6px]"), { backgroundColor: rowBgColor, textAlign: 'center' }]}
  • style={[tw("flex-1 text-center items-center justify-center border border-black p-[5px] text-[6px]"), { backgroundColor: rowBgColor, textAlign: 'center' }]}
    
    - Empty data row staff/admin cell:
    
    ```diff
    
  • style={[tw("flex-1 border border-black p-[3px] text-center items-center justify-centertext-[6px]"), { backgroundColor: rowBgColor, textAlign: 'center' }]}
  • style={[tw("flex-1 border border-black p-[3px] text-center items-center justify-center text-[6px]"), { backgroundColor: rowBgColor, textAlign: 'center' }]}
    
    

These fixes restore the intended padding, borders, and centering.

src/features/scheduling/ProgramAreaGrid.tsx (1)

119-126: Fix Tailwind class typo in non-empty area cells.

The non-empty branch uses min-h-[13px]text-center (missing space), so Tailwind treats it as an invalid utility and doesn’t apply text-center.

-                          ? tw("bg-white p-[2px] min-h-[13px] text-center border border-black w-[80px]")
-                          : tw("bg-white p-[2px] min-h-[13px]text-center border border-black w-[80px]")
+                          ? tw("bg-white p-[2px] min-h-[13px] text-center border border-black w-[80px]")
+                          : tw("bg-white p-[2px] min-h-[13px] text-center border border-black w-[80px]")

This makes empty and non-empty cells consistently centered.

src/features/scheduling/exporting/CamperGrid.tsx (1)

192-207: Label the bunk column header for clarity.

The first header cell (for the bunk column) is empty, so the bunk numbers in the first data column have no label. Adding a simple “BUNK” label improves readability:

-          <TR style={tw("bg-black")}>
-            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: "30px" }]}>
-            </TH>
+          <TR style={tw("bg-black")}>
+            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: "30px" }]}>
+              <Text>BUNK</Text>
+            </TH>
🧹 Nitpick comments (5)
src/features/scheduling/ProgramAreaGrid.tsx (1)

59-71: Confirm intent to show only the first activity per block.

renderActivityText now renders only activities[0]. If blocks can have multiple activities per area, the rest are silently dropped. If that’s intentional (e.g., you only care about the primary activity), all good; otherwise, consider mapping and stacking all activities in the cell.

src/features/scheduling/exporting/BlockRatiosGrid.tsx (1)

154-199: Defensively handle missing attendees in attendeesById.

Both camper and staff/admin rendering assume attendeesById[id] always exists:

const attendee = attendeesById[camperOrBunkId];
...
const employee = attendeesById[employeeId];

If schedule data or attendees arrays ever get out of sync, this will throw on attendee.name / employee.name. A small guard keeps the PDF resilient:

const attendee = attendeesById[camperOrBunkId];
if (!attendee) {
  return null; // or a fallback <Text key={...}>Unknown</Text>
}

and similarly for employee. That way, bad data results in a blank or placeholder instead of a crash.

src/features/scheduling/exporting/CamperGrid.tsx (1)

172-180: Avoid mutating the campers prop when sorting.

campers.sort(...) mutates the array passed in via props, which is generally discouraged in React. Prefer sorting a copy:

-  campers.sort((a, b) => {
+  const sortedCampers = [...campers].sort((a, b) => {
@@
-  });
+  });

and then iterate over sortedCampers in your JSX. This keeps props immutable and avoids surprises if campers is reused elsewhere.

src/features/scheduling/exporting/CamperPreferencesSheet.tsx (2)

62-62: Consider using a consistent styling approach.

The Page component mixes tw() utility styling with an inline style object. Since Helvetica is already configured as the sans font in the theme (line 15), consider using tw("font-sans") instead of the inline fontFamily property for consistency.

If react-pdf-tailwind supports font-sans at the Page level, apply this diff:

-      <Page size="A4" style={[tw("p-[40px] text-[12px]"), { fontFamily: "Helvetica" }]}>
+      <Page size="A4" style={tw("p-[40px] text-[12px] font-sans")}>

84-84: Remove unnecessary empty style.

The View component on line 84 has an empty style string tw("") which serves no purpose and can be removed.

Apply this diff:

-                  <View style={tw("")}>
+                  <View>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 685e70c and 0a64eed.

📒 Files selected for processing (6)
  • src/components/SmallDirectoryBlock.tsx (1 hunks)
  • src/features/scheduling/ProgramAreaGrid.tsx (2 hunks)
  • src/features/scheduling/exporting/BlockRatiosGrid.tsx (5 hunks)
  • src/features/scheduling/exporting/CamperGrid.tsx (4 hunks)
  • src/features/scheduling/exporting/CamperPreferencesSheet.tsx (4 hunks)
  • src/features/scheduling/exporting/EmployeeGrid.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/SmallDirectoryBlock.tsx
  • src/features/scheduling/exporting/EmployeeGrid.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
src/features/scheduling/ProgramAreaGrid.tsx (1)
src/types/sessionTypes.ts (2)
  • BundleActivity (105-108)
  • ProgramAreaID (98-98)
src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)
src/features/scheduling/generation/schedulingUtils.ts (1)
  • isBundleActivity (10-12)
src/features/scheduling/exporting/BlockRatiosGrid.tsx (1)
src/features/scheduling/generation/schedulingUtils.ts (2)
  • isBundleActivity (10-12)
  • isIndividualAssignments (35-37)
src/features/scheduling/exporting/CamperGrid.tsx (1)
src/features/scheduling/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
🔇 Additional comments (1)
src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)

10-18: No compatibility issues found — all Tailwind classes are literal strings and use basic utilities supported by react-pdf-tailwind.

All classes in the component (p-[40px], text-[12px], flex-row, mb-[10px], etc.) are passed as literal strings to tw(), so Tailwind will generate them at build time. The utilities used are basic CSS properties (padding, margin, text sizing, flex layout, font-weight) that map directly to react-pdf's supported styling—no browser-only features or unsupported modifiers. The explicit flex-row usage is appropriate given that react-pdf (via Yoga) defaults flex-direction to column.

@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

♻️ Duplicate comments (1)
src/features/scheduling/exporting/EmployeeGrid.tsx (1)

45-70: Remove <TR> wrapper from header—it violates @ag-media/react-pdf-table API and causes misalignment

Per the library's documented structure and the previous review comment, <TH> must directly contain <TD> elements without a <TR> wrapper. The current nesting (<TH><TR><TD>) causes the header row to misalign with data rows and defeats the stated PR objective.

Apply this diff to remove the <TR> wrapper:

       <TH style={tw("mb-[8px] border border-black")}>  
-         <TR style={tw("flex-row bg-black")}>  
           <TD>  
             <Text style={tw("flex-1 text-center text-[8px] font-bold text-white border border-black p-[1px] bg-black justify-center items-center")}>  
               NAME  
             </Text>  
           </TD>  
           {Object.keys(schedule.blocks).map((blockId) => (  
             <TD key={blockId}>  
               <Text style={tw("flex-1 text-center text-[8px] font-bold text-white border border-black p-[1px] bg-black justify-center items-center")}>  
                 {blockId}  
               </Text>  
             </TD>  
           ))}  
           <TD>  
             <Text style={tw("flex-1 text-center text-[8px] font-bold text-white border border-black p-[1px] bg-black justify-center items-center")}>  
               APO  
             </Text>  
           </TD>  
           <TD>  
             <Text style={tw("flex-1 text-center text-[8px] font-bold text-white border border-black p-[1px] bg-black justify-center items-center")}>  
               AM/PM FP  
             </Text>  
           </TD>  
-         </TR>  
       </TH>

Based on the previous review comment and library documentation.

🧹 Nitpick comments (2)
src/features/scheduling/ProgramAreaGrid.tsx (2)

85-85: Remove or clarify the alignment comment.

The comment suggests a workaround was attempted. If the alignment issue has been resolved, remove this comment. Otherwise, clarify the current status or open a tracking issue.


142-142: Remove redundant inline fontFamily style.

The fontFamily: "Helvetica" inline style is redundant since Helvetica is already configured as the default font in the theme (lines 13-14). The tw() utility should apply it automatically.

-      <Page size="A4" style={[tw("p-[20px] text-[9px]"), { fontFamily: "Helvetica" }]}>
+      <Page size="A4" style={tw("p-[20px] text-[9px]")}>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a64eed and a16e471.

📒 Files selected for processing (2)
  • src/features/scheduling/ProgramAreaGrid.tsx (2 hunks)
  • src/features/scheduling/exporting/EmployeeGrid.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/features/scheduling/ProgramAreaGrid.tsx (1)
src/types/sessionTypes.ts (2)
  • BundleActivity (105-108)
  • ProgramAreaID (98-98)
src/features/scheduling/exporting/EmployeeGrid.tsx (1)
src/utils/personUtils.ts (1)
  • getFullName (3-5)
🔇 Additional comments (6)
src/features/scheduling/exporting/EmployeeGrid.tsx (3)

9-9: LGTM: Unused import removed

The View import has been correctly removed, addressing the previous ESLint error.


85-139: LGTM: Data rows correctly structured and styled

The data row structure (<TR> directly containing <TD> elements) follows the @ag-media/react-pdf-table conventions. The centering classes (text-center, justify-center, items-center) properly address the PR objective of centering text within table cells. The assignment logic for activities, APO, and freeplay buddies is sound.


42-44: LGTM: Page and table setup is appropriate

The Page and Table configuration with Tailwind-based styling and dynamic title generation based on employee role is well-structured.

src/features/scheduling/ProgramAreaGrid.tsx (3)

11-26: LGTM!

The Tailwind configuration is well-structured with appropriate font family settings for PDF rendering and custom color definitions.


8-9: Imports are correctly configured and library versions are compatible.

The react-pdf-tailwind v3.0.0 and @ag-media/react-pdf-table v2.0.3 packages are both designed for and compatible with @react-pdf/renderer v4.3.1. The imports on lines 8-9 are correct with no unused dependencies.


59-72: Confirm the behavioral change to display only the first activity aligns with requirements.

The function now renders only the first activity from each filtered set, as indicated by the explicit comment. However, this behavioral change is undocumented and not mentioned in the PR description, which focuses on alignment and centering. Since the cell can contain multiple activities (filtered by programArea.id), verify with your requirements that displaying only the first activity is the intended behavior and doesn't result in unintended data loss.

Comment thread src/features/scheduling/ProgramAreaGrid.tsx Outdated

@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

🧹 Nitpick comments (3)
src/features/scheduling/ProgramAreaGrid.tsx (3)

11-26: Remove unused color definitions.

gray-50 and gray-700 are defined in the theme but never used in this component. Consider removing them to keep the configuration lean.

       extend: {
         colors: {
-          'gray-50': '#f8f9fa',
           'gray-400': '#b8b8b8',
-          'gray-700': '#495057',
           'gray-900': '#333',
         },
       },

39-47: Replace flatMap with forEach for side-effect-only iteration.

flatMap is intended for transformation and its return value is discarded here. Using forEach is more appropriate and clearer for side-effect operations.

   const programAreaMap: Record<string, ProgramAreaID> = {};
-  Object.values(schedule.blocks).flatMap((block) =>
-    block.activities.forEach(
-      (a) => (programAreaMap[a.programArea.id] = a.programArea)
-    )
-  );
+  Object.values(schedule.blocks).forEach((block) =>
+    block.activities.forEach((a) => {
+      programAreaMap[a.programArea.id] = a.programArea;
+    })
+  );

85-86: Address or remove the TODO comment about header alignment.

The comment indicates a known alignment issue that was worked around by using rows instead of a dedicated header. Consider documenting whether this workaround fully resolves the issue or if further investigation is needed.

Would you like me to open an issue to track investigating proper header alignment with @ag-media/react-pdf-table?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a16e471 and 5775288.

📒 Files selected for processing (1)
  • src/features/scheduling/ProgramAreaGrid.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/features/scheduling/ProgramAreaGrid.tsx (1)
src/types/sessionTypes.ts (2)
  • BundleActivity (105-108)
  • ProgramAreaID (98-98)
🔇 Additional comments (3)
src/features/scheduling/ProgramAreaGrid.tsx (3)

1-9: LGTM!

Imports are clean and all imported entities are used. The previously flagged unused TH import has been correctly removed.


59-72: LGTM!

The function correctly renders only the first activity as intended, with appropriate null handling for empty arrays.


133-144: LGTM!

The document structure is clean with proper page sizing and layout. The inline fontFamily style on the Page component ensures consistent font rendering, which is a reasonable approach for PDF generation.

@nkanchinadam nkanchinadam left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall, it looks good. I left a comments on how to streamline some of the code and to use the Tailwind config that we already defined.

Comment thread src/features/scheduling 2/exporting/BlockRatiosGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/CamperGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/CamperGrid.tsx Outdated

@nkanchinadam nkanchinadam left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall, it looks good. I left a comments on how to streamline some of the code and to use the Tailwind config that we already defined.

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

♻️ Duplicate comments (4)
src/features/scheduling/ProgramAreaGrid.tsx (1)

42-55: First-activity rendering is intentional; the as BundleActivity[] cast is likely unnecessary.

Limiting each cell to the first activity matches the comment and PR description. Given schedule is already SectionSchedule<"BUNDLE">, block.activities should already be a bundle-activity array, so const activities = block.activities as BundleActivity[]; is probably redundant and drops the richer intersection type if you ever need assignments again. You can let inference handle this.

Also applies to: 85-86

src/features/scheduling/exporting/BlockRatiosGrid.tsx (1)

80-88: Fix tw() typos, guard maxActivities, and re-check header/data column alignment.

There are a few concrete issues in this section:

  • maxActivities = Math.max(...blocks.map(...)) will return -Infinity if schedule.blocks is ever empty. Guard it with a length check:
-  const maxActivities = Math.max(...blocks.map(block => block.activities.length));
+  const maxActivities = blocks.length
+    ? Math.max(...blocks.map((block) => block.activities.length))
+    : 0;
  • Several tw() strings are missing spaces, so utilities don’t apply:
- tw("flex-1 border border-black text-center items-center justify-centerp-[5px] text-[6px]")
+ tw("flex-1 border border-black text-center items-center justify-center p-[5px] text-[6px]")

- tw("flex-1 text-center items-center justify-centerborder border-black p-[5px] text-[6px]")
+ tw("flex-1 text-center items-center justify-center border border-black p-[5px] text-[6px]")

- tw("flex-1 border border-black p-[3px] text-center items-center justify-centertext-[6px]")
+ tw("flex-1 border border-black p-[3px] text-center items-center justify-center text-[6px]")

Without these fixes, padding/centering will be off in exactly the cells you’re trying to standardize.

  • With @ag-media/react-pdf-table, TH is intended as the header row whose direct children are cells. Here TH wraps multiple TRs, and each activity row renders two TDs per block (campers + staff) while the activity-header row only renders one TD per block. That mismatch in column counts can cause header cells not to line up cleanly with their data columns. It’s worth restructuring so the header row has the same number of logical columns as the data rows, following the library’s TH/TR/TD conventions.

Also applies to: 90-101, 110-123, 130-139

src/features/scheduling/exporting/EmployeeGrid.tsx (1)

35-60: Header alignment is fixed; you can simplify TH/TR nesting.

The header now has one cell per column (NAME, each block, APO, AM/PM FP), so it lines up with the data rows. To better match @ag-media/react-pdf-table’s pattern, you can drop the inner <TR> and render the <TD>s directly inside <TH> (since TH is already the header row), e.g.:

-      <TH style={tw("mb-[8px] border border-black")}>  
-         <TR style={tw("flex-row bg-black")}>  
-           <TD>  
-             <Text ...>NAME</Text>  
-           </TD>  
-           {Object.keys(schedule.blocks).map((blockId) => (  
-             <TD key={blockId}>  
-               <Text ...>{blockId}</Text>  
-             </TD>  
-           ))}  
-           <TD>  
-             <Text ...>APO</Text>  
-           </TD>  
-           <TD>  
-             <Text ...>AM/PM FP</Text>  
-           </TD>  
-         </TR>  
-       </TH>  
+      <TH style={tw("mb-[8px] border border-black flex-row bg-black")}>
+        <TD>
+          <Text ...>NAME</Text>
+        </TD>
+        {Object.keys(schedule.blocks).map((blockId) => (
+          <TD key={blockId}>
+            <Text ...>{blockId}</Text>
+          </TD>
+        ))}
+        <TD>
+          <Text ...>APO</Text>
+        </TD>
+        <TD>
+          <Text ...>AM/PM FP</Text>
+        </TD>
+      </TH>

This removes one level of nesting and follows the library’s expected header structure.

src/features/scheduling/exporting/CamperGrid.tsx (1)

175-197: Fix invalid width tokens in tw() and label the bunk header column.

Two concrete issues in the header:

  • tw("... width: 30px") / tw("... width: 35px") won’t produce width styles—Tailwind-style parsers won’t understand raw width: tokens. These header cells will fall back to default widths, which won’t match the data cells below.

  • The first header cell is still empty, so the bunk column is unlabeled.

You can also align header and data widths by sharing the same values, e.g.:

-          <TR style={tw("bg-black")}>
-            <TH style={tw("text-center text-[7px] font-bold text-white p-[1px] width: 30px")}>
-            </TH>
-            <TH style={tw("text-center text-[7px] font-bold text-white p-[1px] width: 35px" )}>
-              <Text>NAME</Text>
-            </TH>
+          <TR style={tw("bg-black")}>
+            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: 10 }]}>
+              <Text>BUNK</Text>
+            </TH>
+            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: 50 }]}>
+              <Text>NAME</Text>
+            </TH>
             {blockIds.map((blockId) => (
               <TH
                 key={blockId}
-                style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: "30px" }]}
+                style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: 20 }]}
               >
                 <Text>{blockId}</Text>
               </TH>
             ))}
-            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: "25px" }]}>
+            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: 20 }]}>
               <Text>+</Text>
             </TH>
-            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: "25px" }]}>
+            <TH style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: 20 }]}>
               <Text>FP</Text>
             </TH>
           </TR>

These widths now match the corresponding TD widths below, which should keep the header row aligned with the data columns.

🧹 Nitpick comments (9)
src/utils/reactPdfTailwind.ts (1)

1-28: Centralized tw helper looks good; consider reusing more of the base theme.

This setup is clean and correctly pulls extend.colors from tailwindConfig. If you later rely on more Tailwind tokens in PDFs, consider spreading tailwindConfig.theme into theme (and then layering your overrides) to avoid the PDF theme drifting from the main Tailwind config, but it’s not required right now.

src/app/demo/program-area-grid/page.tsx (1)

13-131: Demo page is fine; you can hoist mock data out of the component.

The mock schedule/program areas work well for this viewer. If this page ever re-renders frequently, consider moving programAreas, createActivity, and mockSchedule outside the component so they’re constructed once, and double‑check the literals stay in sync with ProgramAreaID / BundleActivity if those interfaces gain required fields.

src/features/scheduling/ProgramAreaGrid.tsx (1)

21-40: Simplify program area collection for readability.

Using Object.values(schedule.blocks).flatMap(...forEach...) only for side effects is a bit hard to read. A simple nested for...of or forEach over schedule.blocks and block.activities would avoid allocating the intermediate array and make the intent (“collect unique program areas”) clearer without changing behavior.

src/features/scheduling/exporting/BlockRatiosGrid.tsx (2)

20-78: Consider removing the commented-out StyleSheet block.

The old StyleSheet definition is fully commented and no longer used. Dropping it would make this file easier to scan and keep the focus on the new Table + tw layout.


143-191: Defensively handle missing attendees from attendeesById.

Both the camper/bunk and staff/admin sections assume attendeesById[id] is always defined and immediately read name and role. If the schedule or attendee arrays ever drift (e.g., stale IDs), this will throw during PDF generation. A small guard, e.g. skipping entries when !attendee before calling formatNameShort, would make this more robust without changing the happy path.

src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)

12-37: Remove the commented-out StyleSheet for clarity.

The legacy StyleSheet block is fully commented and superseded by tw styles. Removing it will declutter the file and reduce the chance of someone editing dead styles by mistake.

src/features/scheduling/exporting/EmployeeGrid.tsx (1)

63-65: Avoid casting Freeplay assignments to number[] before mapping.

getFreeplayAssignmentId(freeplay, employee.id) can return a string postId, a numeric staffId, an array of camperIds, or null. Casting that union to number[] will silently mis-handle the non-array cases. Safer is to branch on array-ness:

-            const fpBuddyIds = getFreeplayAssignmentId(freeplay, employee.id);
-            const fpBuddies = fpBuddyIds ? (fpBuddyIds as number[]).map(id => campers.find(c => c.id === id)) : [];
+            const fpAssignment = getFreeplayAssignmentId(freeplay, employee.id);
+            const fpBuddies = Array.isArray(fpAssignment)
+              ? fpAssignment
+                  .map((id) => campers.find((c) => c.id === id))
+                  .filter((c): c is CamperAttendeeID => !!c)
+              : [];

This keeps the existing behavior for “buddy” assignments while avoiding incorrect casts for postId/staffId returns.

Also applies to: 124-127

src/features/scheduling/exporting/CamperGrid.tsx (2)

18-143: Clean up the commented-out StyleSheet.

The old StyleSheet block is fully commented and replaced by the Table + tw layout. Removing it will simplify the file and avoid confusion about which styling path is authoritative.


199-267: Optionally, share block/width constants to reduce duplication and order assumptions.

You’re relying on Object.keys(schedule.blocks) for headers and Object.values(schedule.blocks) for blocksArray. JS currently preserves insertion order so these stay in sync, but deriving a single const blockEntries = Object.entries(schedule.blocks) and mapping it for both headers and data would make the coupling explicit. Likewise, the various hard-coded widths (10/50/20/20/20) could be pulled into named constants or Tailwind width utilities so you only need to change them in one place if the layout needs to be tweaked.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5775288 and 2df0ea8.

📒 Files selected for processing (7)
  • src/app/demo/program-area-grid/page.tsx (1 hunks)
  • src/features/scheduling/ProgramAreaGrid.tsx (2 hunks)
  • src/features/scheduling/exporting/BlockRatiosGrid.tsx (5 hunks)
  • src/features/scheduling/exporting/CamperGrid.tsx (4 hunks)
  • src/features/scheduling/exporting/CamperPreferencesSheet.tsx (4 hunks)
  • src/features/scheduling/exporting/EmployeeGrid.tsx (4 hunks)
  • src/utils/reactPdfTailwind.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/features/scheduling/exporting/CamperPreferencesSheet.tsx (2)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
src/features/scheduling/generation/schedulingUtils.ts (1)
  • isBundleActivity (10-12)
src/features/scheduling/exporting/BlockRatiosGrid.tsx (2)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
src/features/scheduling/generation/schedulingUtils.ts (2)
  • isBundleActivity (10-12)
  • isIndividualAssignments (35-37)
src/features/scheduling/exporting/CamperGrid.tsx (2)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
src/features/scheduling/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
src/features/scheduling/exporting/EmployeeGrid.tsx (2)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
src/utils/personUtils.ts (1)
  • getFullName (3-5)
src/features/scheduling/ProgramAreaGrid.tsx (2)
src/types/sessionTypes.ts (2)
  • BundleActivity (105-108)
  • ProgramAreaID (98-98)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
🔇 Additional comments (2)
src/features/scheduling/ProgramAreaGrid.tsx (1)

67-80: Table structure now satisfies the alignment and centering goals.

Header and body rows both use TR with matching TD shapes and consistent widths/min-heights, and the text-center styles on both header labels and cell content ensure horizontal centering. This should fix the header/body misalignment and oversized-row issues.

Also applies to: 90-107, 118-125

src/features/scheduling/exporting/CamperPreferencesSheet.tsx (1)

54-62: Activity filtering and new layout look solid.

The block-level activities computation correctly narrows bundle blocks by ageGroup when provided, and the new tw-based Page/row styles keep the sheet compact while preserving readability. No functional issues here from my side.

Also applies to: 64-92

@nkanchinadam
nkanchinadam force-pushed the main branch 2 times, most recently from a884ab9 to 974f469 Compare December 2, 2025 05:43

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

🤖 Fix all issues with AI agents
In @src/features/scheduling/exporting/CamperGrid.tsx:
- Around line 74-91: The code misses handling when getFreeplayAssignmentId
returns number[] for buddy cases; add an Array.isArray(fpAssignment) branch
after the typeof checks that iterates each staffId in fpAssignment, looks up
matching postId(s) from freeplay.posts (same logic used for the number case),
collects unique postIds, and sets fpActivityCode to either a sensible display
string (e.g., comma-separated unique postIds) or "-" if none found; update
references to fpAssignment, fpActivityCode, and freeplay.posts accordingly.

In @src/features/scheduling/exporting/EmployeeGrid.tsx:
- Around line 42-83: The table header currently nests a TR inside TH in
EmployeeGrid.tsx (TH contains a TR that contains TDs), which violates
@ag-media/react-pdf-table expectations; refactor the header so TH directly
contains the TD elements (move the NAME, mapped blockId TDs, APO and AM/PM FP
TDs out of the TR and into TH), remove the TR wrapper from inside TH, and ensure
any styling applied to the former TR is moved to the TDs or to TH as appropriate
so header alignment is preserved.

In @src/features/scheduling/exporting/ProgramAreaGrid.tsx:
- Line 118: In ProgramAreaGrid's JSX where the Text element renders
"{sectionName} Program Area Grid", fix the prop typo by replacing the incorrect
prop name "stylstyle" with "style" on the Text component so the styling is
applied and the runtime error is resolved; verify the Text component accepts a
style prop if it's a custom component (component name: Text, JSX containing
{sectionName} Program Area Grid).
- Line 2: The import line in ProgramAreaGrid.tsx includes unused symbols
Document and Page from "@react-pdf/renderer" which trigger lint errors; remove
Document and Page from the import so only the used symbols (e.g., Text, View)
are imported, or delete the entire import if none are used, then run the linter
to confirm the warning is resolved.
- Line 85: The explicit type assertion on block.activities should be removed to
preserve the narrowed generic typing from SectionSchedule<"BUNDLE">; replace the
cast "as BundleActivity[]" by using the already-typed value (e.g., change the
assignment in ProgramAreaGrid where const activities = block.activities as
BundleActivity[] to just use block.activities) so you don't lose extra
properties like assignments and keep the proper type information for downstream
code.
- Around line 42-55: renderActivityText in ProgramAreaGrid currently returns
only the first activity; update it to indicate when additional activities are
present by appending a concise count (e.g., "First Activity Name (Age) (+2
more)") so the PDF cell still fits but the user knows items are omitted. Locate
the renderActivityText function that takes BundleActivity[] and modify its
returned Text (using tw styling) to check activities.length>1 and append `
(+{activities.length - 1} more)` after the first activity string; keep the
existing styling/size constraints and ensure no line breaks are introduced so
the cell layout remains stable.
🧹 Nitpick comments (2)
src/features/scheduling/exporting/CamperGrid.tsx (1)

48-142: Consider using Tailwind width utilities for consistency.

Throughout the table, width is specified using object literals like { width: "30px" }. Tailwind supports width utilities (e.g., w-[30px]) that could be included in the tw() strings for more consistent styling.

Example:

// Current
style={[tw("text-center text-[7px] font-bold text-white p-[1px]"), { width: "30px" }]}

// Alternative
style={tw("text-center text-[7px] font-bold text-white p-[1px] w-[30px]")}

Based on past review comments suggesting to use Tailwind built-in styles where available.

src/features/scheduling/exporting/ProgramAreaGrid.tsx (1)

69-80: Use TH instead of TR for the header row to follow @ag-media/react-pdf-table conventions.

The library provides a dedicated TH (TableHeader) component for header rows. The recommended pattern is <TH><TD>…</TD></TH> for headers and <TR><TD>…</TD></TR> for data rows. Replace the header <TR> at line 69 with <TH> for consistency with the library's API.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2df0ea8 and f042419.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json
  • src/features/scheduling/exporting/CamperGrid.tsx
  • src/features/scheduling/exporting/EmployeeGrid.tsx
  • src/features/scheduling/exporting/ProgramAreaGrid.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (3)
src/features/scheduling/exporting/EmployeeGrid.tsx (3)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
src/features/scheduling/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
src/utils/personUtils.ts (1)
  • getFullName (3-5)
src/features/scheduling/exporting/CamperGrid.tsx (2)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
src/features/scheduling/generation/schedulingUtils.ts (3)
  • getFreeplayAssignmentId (18-33)
  • isIndividualAssignments (35-37)
  • isBundleActivity (10-12)
src/features/scheduling/exporting/ProgramAreaGrid.tsx (2)
src/types/sessionTypes.ts (2)
  • BundleActivity (104-107)
  • ProgramAreaID (97-97)
src/utils/reactPdfTailwind.ts (1)
  • tw (8-28)
🪛 GitHub Actions: Lint & Build on PR
src/features/scheduling/exporting/ProgramAreaGrid.tsx

[error] 2-10: ESLint: 'Document' is defined but never used. (no-unused-vars)


[error] 2-20: ESLint: 'Page' is defined but never used. (no-unused-vars)

🔇 Additional comments (2)
src/features/scheduling/exporting/EmployeeGrid.tsx (1)

101-174: Data row structure looks good.

The employee row rendering correctly uses TR with TD elements for each column, properly handles OFF periods, and uses type guards to safely determine activity assignments.

src/features/scheduling/exporting/CamperGrid.tsx (1)

93-141: Camper data row rendering looks good.

The logic correctly:

  • Formats camper names consistently
  • Uses type guards to safely determine activity assignments
  • Handles both individual and bunk-based assignments
  • Gracefully displays empty strings for missing activities

Comment thread src/features/scheduling/exporting/CamperGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/EmployeeGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/ProgramAreaGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/ProgramAreaGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/ProgramAreaGrid.tsx Outdated
Comment thread src/features/scheduling/exporting/ProgramAreaGrid.tsx Outdated
@nkanchinadam

nkanchinadam commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

The various PDF components, when populated with data, sometimes overflow the page they're on and get cut off, making them unreadable for users.

Before we merge this PR, we should do the following:

  • Merge the latest changes from main into this branch
  • Fix the page overflow issues when using the PDF library for the different PDF components we have

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants