Skip to content

feat: Add multi-calendar export support#121

Merged
panteLx merged 5 commits intomainfrom
feat/multi-export
Jan 15, 2026
Merged

feat: Add multi-calendar export support#121
panteLx merged 5 commits intomainfrom
feat/multi-export

Conversation

@panteLx
Copy link
Owner

@panteLx panteLx commented Jan 15, 2026

Adds a multi-calendar export option to the export dialog so users can combine shifts from multiple calendars into a single export file.

Adds a toggle and selectable calendar list with animations, preselects the current calendar, validates selection, and switches the request flow to POST for multi-calendar exports. Wires available calendars through the settings/dialog flow and includes new i18n strings for the UI. Also updates the lockfile metadata.

Summary by CodeRabbit

  • New Features

    • Multi-calendar exports: select multiple calendars to combine shifts into a single ICS or PDF.
    • Calendar selection UI with checkboxes, selection count, and validation.
  • Improvements

    • Exported events are labeled with calendar names when combining calendars.
    • Filenames and export metadata adapt when exporting multiple calendars.
    • PDF/ICS exports support multi-calendar date/range and all-day semantics.
  • Bug Fixes

    • Export rate limiting added with user-facing limit responses.
  • Localization

    • New export strings for English, German, and Italian.

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

panteLx and others added 4 commits January 14, 2026 23:02
Adds a multi-calendar export option to the export dialog so users can combine shifts from multiple calendars into a single export file.

Adds a toggle and selectable calendar list with animations, preselects the current calendar, validates selection, and switches the request flow to POST for multi-calendar exports. Wires available calendars through the settings/dialog flow and includes new i18n strings for the UI. Also updates the lockfile metadata.
Copilot AI review requested due to automatic review settings January 15, 2026 10:14
@panteLx panteLx linked an issue Jan 15, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Converted single-calendar export flows into multi-calendar exports: replaced GET with POST for ICS/PDF endpoints, added rate limiting for ICS, extended backend queries and export generation to handle multiple calendars, added multi-calendar selection UI, and introduced localization strings for the feature.

Changes

Cohort / File(s) Summary
API Export Routes
app/api/export/ics/route.ts, app/api/export/pdf/route.ts
GET → POST handlers now accept { calendarIds: [...] }. Replaced single-calendar queries with batch queries (inArray), added ICS rate-limit checks, applied permission filtering (403 if none), aggregated shifts across calendars, and updated file naming/metadata for multi-calendar exports.
Export UI Components
components/export-dialog.tsx, components/calendar-settings-sheet.tsx, components/dialog-manager.tsx
Added availableCalendars prop; introduced multi-calendar toggle and selectable calendar list; preselects current calendar; builds POST body { calendarIds: [...] }; validates selection and surfaces errors/toasts; forwards calendar data from DialogManager → CalendarSettingsSheet → ExportDialog.
Rate Limiting
lib/rate-limiter.ts, .env.example
Added exportIcs config and integrated "export-ics" into rateLimit and getRateLimitStatus. .env.example updated with commented RATE_LIMIT_EXPORT_ICS_REQUESTS and RATE_LIMIT_EXPORT_ICS_WINDOW examples.
Localization
messages/en.json, messages/de.json, messages/it.json
Added export-related keys: multiCalendar, multiCalendarHint, selectCalendars, selectCalendarsDescription, selectedCount (pluralized), and selectAtLeastOne.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Export Dialog
    participant API as /api/export/[format]
    participant RateLimit as Rate Limiter
    participant DB as Database
    participant FileGen as Export Generator

    User->>UI: Select calendars & click Export
    UI->>UI: Validate selection (>=1)
    UI->>API: POST /api/export/{format} { calendarIds: [...] }
    API->>RateLimit: check("export-ics") / check("export-pdf")
    alt rate limit exceeded
        RateLimit-->>API: 429
        API-->>UI: 429 response
        UI-->>User: show rate-limit error
    else allowed
        RateLimit-->>API: ok
        API->>DB: fetch calendars by ids
        DB-->>API: calendars
        API->>DB: fetch shifts for accessible calendars
        DB-->>API: shifts
        API->>FileGen: generate ICS/PDF (multi-calendar metadata)
        FileGen-->>API: file buffer
        API-->>UI: file response (download)
        UI-->>User: save file
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

"I hop with joy across the keys,
calendars bundled like springtime leaves,
exports stitched in one neat file,
limits keep servers on their mile —
nibble carrots, celebrate these deeds!" 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add multi-calendar export support' directly and clearly describes the main feature addition across the changeset.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
components/export-dialog.tsx (1)

174-184: Consider updating filename for multi-calendar exports.

When exporting multiple calendars, the filename still uses calendarName (the current calendar's name), which may be misleading. Consider using a generic name like "combined_export" or "multi_calendar_export" when multiCalendar is enabled and multiple calendars are selected.

💡 Suggested improvement
       // Get filename from Content-Disposition header or create one
       const contentDisposition = response.headers.get("Content-Disposition");
-      let filename = `${calendarName
-        .replace(/[^a-z0-9]/gi, "_")
-        .toLowerCase()}_export.${exportFormat}`;
+      let filename = multiCalendar && selectedCalendarIds.length > 1
+        ? `combined_calendars_export.${exportFormat}`
+        : `${calendarName
+            .replace(/[^a-z0-9]/gi, "_")
+            .toLowerCase()}_export.${exportFormat}`;
       if (contentDisposition) {
         const filenameMatch = contentDisposition.match(/filename="(.+)"/);
         if (filenameMatch) {
           filename = filenameMatch[1];
         }
       }

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9454805 and 5466928.

📒 Files selected for processing (1)
  • components/export-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (3)
components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Components in /components directory should be "use client" by default for interactive UI, and should use shadcn/ui patterns with Radix UI primitives and Lucide icons.

Files:

  • components/export-dialog.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Store and display dates as YYYY-MM-DD strings (local dates without timezone conversion). Use formatDateToLocal() from lib/date-utils.ts before saving to database and date-fns with locale from getDateLocale() for display.
Use Drizzle-inferred types from schema directly (no any types). TypeScript must remain in strict mode with proper type safety throughout the application.
All React files must be in strict mode with TypeScript 5. No any types allowed. Import inferred types from Drizzle schema.
Use formatDateToLocal(date) utility from lib/date-utils.ts to convert dates to YYYY-MM-DD format before saving to database.

Files:

  • components/export-dialog.tsx
components/**/*dialog*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use the Dialog pattern (components/ui/dialog.tsx from shadcn/ui) for list views, confirmations, and read-only displays.

Files:

  • components/export-dialog.tsx
🧠 Learnings (2)
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to **/*.{tsx} : Use `useCalendarPermission(calendarId)` hook to check UI permissions, which returns `canEdit`, `canDelete`, `canShare`, and `permission` level for displaying conditional UI elements.

Applied to files:

  • components/export-dialog.tsx
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to components/**/*dialog*.{tsx,ts} : Use the Dialog pattern (`components/ui/dialog.tsx` from shadcn/ui) for list views, confirmations, and read-only displays.

Applied to files:

  • components/export-dialog.tsx
🔇 Additional comments (7)
components/export-dialog.tsx (7)

1-29: LGTM!

The imports are well-organized and follow the coding guidelines: "use client" directive is present, shadcn/ui components are used, and the CalendarWithCount type is properly imported from the types file.


31-45: LGTM!

The interface is properly typed with the optional availableCalendars prop using CalendarWithCount[], and the default empty array in destructuring is a good pattern for optional array props.


46-56: LGTM!

State declarations are properly typed and organized. The new multiCalendar and selectedCalendarIds state variables are appropriately typed.


88-110: LGTM!

The useEffect properly initializes default month/year selections when the dialog opens and resets all state when it closes. The pre-selection of the current calendar ID provides good UX for the multi-calendar feature.


214-242: LGTM!

The multi-calendar toggle is conditionally rendered only when multiple calendars are available, and properly pre-selects the current calendar when enabled. The UI follows shadcn patterns with proper accessibility via the htmlFor label association.


244-291: LGTM!

The calendar selection list is well-implemented with proper motion animations and centralized click handling. The pointer-events-none on the Checkbox correctly prevents duplicate toggle logic, with the parent motion.div handling all click interactions. The visual feedback with calendar colors and selection count enhances UX.


293-412: LGTM!

The export format and range selection UI is well-structured with proper conditional rendering for PDF-specific options. The dialog follows the shadcn/ui Dialog pattern as required by the coding guidelines, with appropriate loading states and button behaviors.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds multi-calendar export functionality allowing users to combine shifts from multiple calendars into a single export file (ICS or PDF). The implementation includes a new toggle UI in the export dialog with animated calendar selection, proper permission checking, and rate limiting.

Changes:

  • Export dialog enhanced with multi-calendar toggle and selectable calendar list
  • API routes converted from GET to POST to support array of calendar IDs in request body
  • Rate limiting added for ICS exports to match PDF export protection
  • Internationalization strings added for all three supported locales (en, de, it)

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
messages/en.json Added English translations for multi-calendar export UI
messages/de.json Added German translations for multi-calendar export UI
messages/it.json Added Italian translations for multi-calendar export UI
lib/rate-limiter.ts Added rate limiting configuration for ICS exports (20 requests per 10 minutes)
components/export-dialog.tsx Enhanced dialog with multi-calendar toggle, calendar selection list, and POST request support
components/dialog-manager.tsx Threaded availableCalendars prop to settings sheet
components/calendar-settings-sheet.tsx Accepted and passed availableCalendars prop to export dialog
app/api/export/pdf/route.ts Converted from GET to POST, added multi-calendar support with permission filtering
app/api/export/ics/route.ts Converted from GET to POST, added multi-calendar support with rate limiting
.env.example Documented new ICS export rate limit environment variables
Comments suppressed due to low confidence (2)

app/api/export/pdf/route.ts:51

  • The accessibleCalendars array should be typed explicitly. Declare it as const accessibleCalendars: typeof requestedCalendars = [] to maintain proper TypeScript typing and avoid implicit any[] type.
    app/api/export/ics/route.ts:47
  • The accessibleCalendars array should be typed explicitly. Declare it as const accessibleCalendars: typeof requestedCalendars = [] to maintain proper TypeScript typing and avoid implicit any[] type.

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

Caution

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

⚠️ Outside diff range comments (1)
components/export-dialog.tsx (1)

167-177: Fallback filename doesn't reflect multi-calendar export.

When Content-Disposition header is missing, the fallback filename uses calendarName (single calendar name) even in multi-calendar mode. This is a minor inconsistency since the server should always provide the header, but consider updating the fallback for completeness.

🔧 Suggested fix
      // Get filename from Content-Disposition header or create one
      const contentDisposition = response.headers.get("Content-Disposition");
-     let filename = `${calendarName
-       .replace(/[^a-z0-9]/gi, "_")
-       .toLowerCase()}_export.${exportFormat}`;
+     let filename = multiCalendar
+       ? `multi_calendar_export.${exportFormat}`
+       : `${calendarName
+           .replace(/[^a-z0-9]/gi, "_")
+           .toLowerCase()}_export.${exportFormat}`;
🤖 Fix all issues with AI agents
In `@app/api/export/pdf/route.ts`:
- Around line 119-129: The hexToRgb helper currently only accepts 6‑digit hex
and returns black for shorthand like "#FFF"; update hexToRgb to normalize
shorthand by expanding 3‑digit hex to 6‑digit (or accept both via regex) before
parsing so "#FFF" becomes "#FFFFFF", and ensure it still falls back safely for
invalid input; also add validation in the ColorPicker input handler (the custom
color text field) to reject or normalize non‑hex values before they call
hexToRgb or are persisted.
🧹 Nitpick comments (4)
lib/rate-limiter.ts (1)

442-496: Pre-existing type inconsistency in getRateLimitStatus.

The type parameter union in getRateLimitStatus is missing the admin-related types (admin-user-mutations, admin-password-reset, admin-bulk-operations, admin-calendar-mutations) that exist in the rateLimit function. This is a pre-existing issue not introduced by this PR, but worth noting for future consistency.

The "export-ics" addition here is correct and matches the pattern.

app/api/export/ics/route.ts (1)

40-47: Consider parallelizing permission checks.

The sequential permission checks could be parallelized for better performance when exporting many calendars.

♻️ Optional optimization
-    // Filter calendars by permission - only include calendars user can view
-    const accessibleCalendars = [];
-    for (const calendar of requestedCalendars) {
-      const hasAccess = await canViewCalendar(user?.id, calendar.id);
-      if (hasAccess) {
-        accessibleCalendars.push(calendar);
-      }
-    }
+    // Filter calendars by permission - only include calendars user can view
+    const permissionChecks = await Promise.all(
+      requestedCalendars.map(async (calendar) => ({
+        calendar,
+        hasAccess: await canViewCalendar(user?.id, calendar.id),
+      }))
+    );
+    const accessibleCalendars = permissionChecks
+      .filter(({ hasAccess }) => hasAccess)
+      .map(({ calendar }) => calendar);
components/export-dialog.tsx (1)

261-271: Redundant onCheckedChange handler on disabled checkbox.

The Checkbox has pointer-events-none which prevents user interaction, so the onCheckedChange handler will never be called. The parent motion.div's onClick handles all toggling. Consider removing the redundant handler for clarity.

♻️ Simplify checkbox
                      <Checkbox
                        checked={isSelected}
-                       onCheckedChange={() => {
-                         setSelectedCalendarIds((prev) =>
-                           prev.includes(calendar.id)
-                             ? prev.filter((id) => id !== calendar.id)
-                             : [...prev, calendar.id]
-                         );
-                       }}
                        className="pointer-events-none"
                      />
app/api/export/pdf/route.ts (1)

95-100: Redundant sort after DB query.

The shifts are already sorted by date in the database query (line 66), making this manual sort unnecessary.

♻️ Remove redundant sort
-    // Sort all shifts by date (already sorted from DB, but ensure it)
-    allShifts.sort((a, b) => {
-      const dateA = (a.date as Date).getTime();
-      const dateB = (b.date as Date).getTime();
-      return dateA - dateB;
-    });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between caba74e and 9454805.

📒 Files selected for processing (10)
  • .env.example
  • app/api/export/ics/route.ts
  • app/api/export/pdf/route.ts
  • components/calendar-settings-sheet.tsx
  • components/dialog-manager.tsx
  • components/export-dialog.tsx
  • lib/rate-limiter.ts
  • messages/de.json
  • messages/en.json
  • messages/it.json
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Store and display dates as YYYY-MM-DD strings (local dates without timezone conversion). Use formatDateToLocal() from lib/date-utils.ts before saving to database and date-fns with locale from getDateLocale() for display.
Use Drizzle-inferred types from schema directly (no any types). TypeScript must remain in strict mode with proper type safety throughout the application.
All React files must be in strict mode with TypeScript 5. No any types allowed. Import inferred types from Drizzle schema.
Use formatDateToLocal(date) utility from lib/date-utils.ts to convert dates to YYYY-MM-DD format before saving to database.

Files:

  • lib/rate-limiter.ts
  • components/dialog-manager.tsx
  • app/api/export/pdf/route.ts
  • components/export-dialog.tsx
  • components/calendar-settings-sheet.tsx
  • app/api/export/ics/route.ts
components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Components in /components directory should be "use client" by default for interactive UI, and should use shadcn/ui patterns with Radix UI primitives and Lucide icons.

Files:

  • components/dialog-manager.tsx
  • components/export-dialog.tsx
  • components/calendar-settings-sheet.tsx
components/**/*dialog*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use the Dialog pattern (components/ui/dialog.tsx from shadcn/ui) for list views, confirmations, and read-only displays.

Files:

  • components/dialog-manager.tsx
  • components/export-dialog.tsx
app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All components under app/ are Server Components by default unless marked with "use client". Use "use client" directive only for interactive UI components.

Files:

  • app/api/export/pdf/route.ts
  • app/api/export/ics/route.ts
app/api/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/api/**/*.ts: All API routes must include GET/POST/PUT/PATCH/DELETE exports in app/api/** directory and follow the pattern: check permissions → perform database operation → emit SSE event.
Use getSessionUser(request.headers) from lib/auth/sessions.ts to get current user in API routes. Use Better Auth's authClient methods on the client side for session management.
After any database mutation (create/update/delete), emit an SSE event using eventEmitter.emit('calendar-change', {...}) with appropriate type and action to keep clients in sync.
For calendar permission hierarchy in SQL queries and permission checks, follow: owner (highest) > admin > write > read (lowest). Use getUserAccessibleCalendars(userId) to get all accessible calendars.
Use getUserCalendarPermission(userId, calendarId) from lib/auth/permissions.ts to retrieve the exact permission level, which returns permission string or null if user has no access.

Files:

  • app/api/export/pdf/route.ts
  • app/api/export/ics/route.ts
components/**/*sheet*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use the Sheet pattern (components/ui/base-sheet.tsx) for all forms with side panels, including unsaved changes confirmation via useDirtyState hook and consistent header/footer styling with gradient backgrounds.

Files:

  • components/calendar-settings-sheet.tsx
🧠 Learnings (6)
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to **/*.{tsx} : Use `useCalendarPermission(calendarId)` hook to check UI permissions, which returns `canEdit`, `canDelete`, `canShare`, and `permission` level for displaying conditional UI elements.

Applied to files:

  • components/dialog-manager.tsx
  • components/export-dialog.tsx
  • components/calendar-settings-sheet.tsx
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to components/**/*dialog*.{tsx,ts} : Use the Dialog pattern (`components/ui/dialog.tsx` from shadcn/ui) for list views, confirmations, and read-only displays.

Applied to files:

  • components/dialog-manager.tsx
  • components/export-dialog.tsx
  • components/calendar-settings-sheet.tsx
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to app/api/**/*.ts : For calendar permission hierarchy in SQL queries and permission checks, follow: `owner` (highest) > `admin` > `write` > `read` (lowest). Use `getUserAccessibleCalendars(userId)` to get all accessible calendars.

Applied to files:

  • app/api/export/pdf/route.ts
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to app/api/**/*.ts : Use `getUserCalendarPermission(userId, calendarId)` from `lib/auth/permissions.ts` to retrieve the exact permission level, which returns permission string or `null` if user has no access.

Applied to files:

  • app/api/export/pdf/route.ts
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to app/api/**/*.ts : All API routes must include `GET/POST/PUT/PATCH/DELETE` exports in `app/api/**` directory and follow the pattern: check permissions → perform database operation → emit SSE event.

Applied to files:

  • app/api/export/pdf/route.ts
  • app/api/export/ics/route.ts
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: Applies to components/**/*sheet*.{tsx,ts} : Use the Sheet pattern (`components/ui/base-sheet.tsx`) for all forms with side panels, including unsaved changes confirmation via `useDirtyState` hook and consistent header/footer styling with gradient backgrounds.

Applied to files:

  • components/calendar-settings-sheet.tsx
🧬 Code graph analysis (4)
app/api/export/pdf/route.ts (3)
app/api/export/ics/route.ts (1)
  • POST (11-192)
lib/db/index.ts (1)
  • db (19-19)
lib/db/schema.ts (3)
  • calendars (100-124)
  • user (8-28)
  • shifts (216-249)
components/export-dialog.tsx (4)
lib/types.ts (1)
  • CalendarWithCount (4-18)
components/ui/dialog.tsx (4)
  • DialogContent (141-141)
  • DialogHeader (144-144)
  • DialogTitle (147-147)
  • DialogDescription (142-142)
components/ui/checkbox.tsx (1)
  • Checkbox (32-32)
components/ui/label.tsx (1)
  • Label (24-24)
components/calendar-settings-sheet.tsx (1)
lib/types.ts (1)
  • CalendarWithCount (4-18)
app/api/export/ics/route.ts (4)
app/api/export/pdf/route.ts (1)
  • POST (10-315)
lib/db/schema.ts (3)
  • user (8-28)
  • calendars (100-124)
  • shifts (216-249)
lib/rate-limiter.ts (1)
  • rateLimit (314-436)
lib/date-utils.ts (1)
  • getServerTimezone (159-179)
⏰ 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). (2)
  • GitHub Check: Agent
  • GitHub Check: build-dev
🔇 Additional comments (16)
components/dialog-manager.tsx (1)

153-153: LGTM!

The availableCalendars prop is correctly threaded from props.calendars (typed as CalendarWithCount[]) to CalendarSettingsSheet, enabling the multi-calendar export feature downstream.

components/calendar-settings-sheet.tsx (2)

27-27: LGTM!

The CalendarWithCount type is properly imported and used for the new optional availableCalendars prop with a sensible default of []. This maintains type safety throughout the component.

Also applies to: 39-39, 57-57


318-324: LGTM!

The availableCalendars prop is correctly forwarded to ExportDialog, completing the data flow for multi-calendar export functionality.

lib/rate-limiter.ts (2)

101-105: LGTM!

The exportIcs rate limit configuration follows the established pattern with sensible defaults (20 requests per 10 minutes). The higher limit compared to PDF export (10 requests) is appropriate since ICS generation is typically less resource-intensive.


376-378: LGTM!

The switch case correctly maps "export-ics" to config.exportIcs, consistent with the existing pattern for other rate limit types.

.env.example (1)

136-137: LGTM!

The new environment variable examples are correctly documented with values matching the defaults in rate-limiter.ts (20 requests, 600 seconds). The placement under "Export Features" maintains logical organization.

messages/de.json (1)

432-438: LGTM!

The German translations are grammatically correct and follow the established ICU MessageFormat pattern for pluralization. The new keys provide comprehensive coverage for the multi-calendar export UI including the toggle, calendar selection, count display, and validation message.

messages/en.json (1)

432-438: LGTM!

The new localization keys for multi-calendar export are well-structured and follow the existing patterns. The ICU plural syntax in selectedCount is correct for handling zero, singular, and plural cases.

messages/it.json (1)

432-438: LGTM!

Italian translations are consistent with the English keys and use correct plural forms for the language.

app/api/export/ics/route.ts (2)

91-177: LGTM!

The ICS generation logic correctly handles:

  • Multi-calendar event prefixing
  • All-day events per RFC 5545 (DTEND exclusive)
  • Timed events with proper UTC conversion
  • Filename sanitization with reasonable truncation

15-17: Missing await on rate limit check.

Based on the rate limiter signature in lib/rate-limiter.ts, the rateLimit function returns NextResponse | null. If this function is asynchronous, the missing await would cause the rate limit check to always pass (the Promise object is truthy but not a NextResponse).

Verify whether rateLimit is synchronous or asynchronous:

#!/bin/bash
# Check the rateLimit function signature
ast-grep --pattern $'export async function rateLimit($$$) {
  $$$
}'
# Also check for non-async version
ast-grep --pattern $'export function rateLimit($$$) {
  $$$
}'
components/export-dialog.tsx (2)

55-56: LGTM!

State initialization and cleanup logic correctly:

  • Pre-selects the current calendar when dialog opens
  • Resets all state including multi-calendar selections when dialog closes
  • Dependencies array includes calendarId for proper re-initialization

Also applies to: 89-103


293-412: LGTM!

The export format selection, PDF range options, and action buttons are well-implemented with proper loading state management.

app/api/export/pdf/route.ts (3)

10-30: LGTM on POST conversion and validation.

The API correctly:

  • Converts to POST method for body parsing
  • Validates calendarIds array
  • Returns appropriate 400 error for invalid input

The same rateLimit await question applies here as in the ICS route.


212-283: LGTM!

PDF rendering correctly handles:

  • Per-shift color indicators
  • Multi-calendar calendar name display
  • Dynamic layout positioning
  • Month grouping and page breaks

288-300: LGTM!

Filename generation is consistent with the ICS route pattern - sanitizing, truncating to 20 chars, limiting to 3 calendar names, and appending the current date.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@panteLx panteLx merged commit c0256ff into main Jan 15, 2026
3 of 4 checks passed
@panteLx panteLx deleted the feat/multi-export branch January 15, 2026 11:08
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.

feature request: Merge multiple calendars into one PDF/ICS

1 participant