Skip to content

feat: add iCloud sync display modes and manage visibility options#8

Merged
panteLx merged 3 commits intomainfrom
feat/icloud-sync-display-mode-visibility
Dec 3, 2025
Merged

feat: add iCloud sync display modes and manage visibility options#8
panteLx merged 3 commits intomainfrom
feat/icloud-sync-display-mode-visibility

Conversation

@panteLx
Copy link
Owner

@panteLx panteLx commented Dec 3, 2025

  • Introduced display modes for iCloud synced shifts: normal and minimal.
  • Updated CalendarGrid to handle and display shifts based on their sync display mode.
  • Enhanced ICloudSyncManageDialog to allow users to set display modes and manage visibility (hide calendar and hide from stats).
  • Added new fields to the iCloud syncs schema for display mode and visibility settings.
  • Implemented a new SyncedShiftsDialog component to view synced shifts with improved UI.
  • Updated translations for new features and options.

Summary by CodeRabbit

  • New Features

    • Display mode per iCloud sync (normal/minimal)
    • Per-sync visibility toggles: hide calendar and hide from stats
    • Dialog to view synced shifts for a selected date
  • Improvements

    • Calendar UI shows synced shifts as grouped badges with counts and drill-in
    • Shift queries and statistics now exclude hidden or "hide from stats" iCloud syncs
    • UI wiring to refresh syncs/shifts on sync lifecycle events
  • Localization

    • Added English and German strings for display modes, synced-shift UI, and visibility controls
  • Chores

    • Database migration to add displayMode/isHidden/hideFromStats fields

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

- Introduced display modes for iCloud synced shifts: normal and minimal.
- Updated CalendarGrid to handle and display shifts based on their sync display mode.
- Enhanced ICloudSyncManageDialog to allow users to set display modes and manage visibility (hide calendar and hide from stats).
- Added new fields to the iCloud syncs schema for display mode and visibility settings.
- Implemented a new SyncedShiftsDialog component to view synced shifts with improved UI.
- Updated translations for new features and options.
Copilot AI review requested due to automatic review settings December 3, 2025 14:11
@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Caution

Review failed

The pull request is closed.

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

Adds displayMode, isHidden, and hideFromStats to icloud_syncs (DB + migration), exposes them in create/patch endpoints (emitting calendar-change on visibility/color updates), filters shifts/stats to exclude hidden or hidden-from-stats syncs, and adds UI for managing display and visibility plus a dialog for viewing synced shifts.

Changes

Cohort / File(s) Summary
Database schema & migrations
lib/db/schema.ts, drizzle/0005_silky_smiling_tiger.sql, drizzle/meta/0005_snapshot.json, drizzle/meta/_journal.json
Add displayMode (text, default "normal"), isHidden (boolean, default false), and hideFromStats (boolean, default false) to icloud_syncs; include migration and Drizzle snapshot/journal updates.
iCloud sync API endpoints
app/api/icloud-syncs/route.ts, app/api/icloud-syncs/[id]/route.ts
POST accepts and persists displayMode; PATCH accepts displayMode, isHidden, hideFromStats and updates only defined fields; emit calendar-change event on visibility or color updates; validation and error flows unchanged.
Shifts queries & stats
app/api/shifts/route.ts, app/api/shifts/stats/route.ts
Left-join icloudSyncs, include syncedFromIcloud in results, and add or(isNull(shifts.icloudSyncId), eq(icloudSyncs.isHidden, false)) / or(..., eq(icloudSyncs.isHidden, false), eq(icloudSyncs.hideFromStats, false)) style filters to exclude shifts from hidden / hidden-from-stats syncs.
Frontend — page & state
app/page.tsx
Add icloudSyncs state and fetch flow; refetch after sync completion; pass icloudSyncs and onShowSyncedShifts into CalendarGrid; wire SyncedShiftsDialog and related show/selected state.
Frontend — calendar grid
components/calendar-grid.tsx
Update CalendarGridProps to accept icloudSyncs and onShowSyncedShifts; separate regular vs. synced shifts rendering; group synced shifts by displayMode/sync ID and render per-sync badges that open synced-shifts UI.
Frontend — sync management dialog
components/icloud-sync-manage-dialog.tsx
Add formDisplayMode select (normal/minimal); include displayMode in create/update payloads; add isHidden and hideFromStats checkboxes with PATCH toggle handlers; invoke parent refresh/toasts on changes.
Frontend — synced shifts dialog
components/synced-shifts-dialog.tsx
New exported SyncedShiftsDialog component that displays ShiftWithCalendar items for a date with localized strings and locale-aware time formatting.
Types & translations
lib/types.ts, messages/en.json, messages/de.json
Add optional syncedFromIcloud?: boolean to ShiftWithCalendar. Add i18n keys for display modes, synced-shifts UI, and visibility toggles/hints in English and German.
Minor / formatting
hooks/useCalendars.ts
Reformat JSON.stringify call in createCalendar (no functional change).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Browser as UI
    participant API as Server (app/api/icloud-syncs)
    participant DB as Database
    participant Shifts as Server (app/api/shifts)

    User->>Browser: Toggle "Hide Calendar" / "Hide From Stats"
    Browser->>API: PATCH /api/icloud-syncs/{id} { isHidden?, hideFromStats?, displayMode? }
    API->>DB: UPDATE icloud_syncs SET ...
    DB-->>API: OK
    API->>API: emit event calendar-change { type:"shift", action:"update", data:{ icloudSyncId, visibilityUpdated:true } }
    API-->>Browser: 200 OK
    Browser->>Shifts: GET /api/shifts (refresh)
    Shifts->>DB: SELECT ... LEFT JOIN icloud_syncs ... WHERE or(isNull(...), icloud_syncs.isHidden = false) ...
    DB-->>Shifts: filtered shifts
    Shifts-->>Browser: shifts (hidden syncs excluded)
    Browser->>User: render updated calendar & stats
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing extra attention:
    • Consistency of visibility filters across both shifts endpoints (isHidden vs hideFromStats).
    • Correct defaults / not-null behavior in Drizzle schema and migration.
    • Event emission logic and payload shape for calendar-change.
    • UI prop threading (icloudSyncs, onShowSyncedShifts) and dialog wiring.
    • Type alignment for syncedFromIcloud vs returned rows.

Possibly related PRs

Poem

🐰
I nibble toggles, soft and bright,
Hide a calendar, set display right,
Synced shifts gather, badges in sight,
A floppy-eared cheer for code at night,
Hoppity hops — the UI feels light!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 accurately reflects the main changes: adding display modes (normal/minimal) and visibility management (hide calendar/hide from stats) for iCloud syncs.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e852c7 and da69fd5.

📒 Files selected for processing (1)
  • components/icloud-sync-manage-dialog.tsx (13 hunks)

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 display mode configuration and visibility controls for iCloud synced shifts, allowing users to customize how synced calendar events appear in their BetterShift calendar view and whether they're included in statistics.

Key Changes

  • Display Modes: Introduced "normal" (show all shifts) and "minimal" (show count badge only) display modes for iCloud syncs
  • Visibility Controls: Added isHidden (hide from calendar) and hideFromStats (exclude from statistics) flags to iCloud syncs
  • UI Enhancements: New SyncedShiftsDialog component for viewing minimal-mode shifts, and updated ICloudSyncManageDialog with visibility toggle checkboxes

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lib/db/schema.ts Added displayMode, isHidden, and hideFromStats fields to icloudSyncs table
drizzle/0005_silky_smiling_tiger.sql Migration to add new columns to existing icloud_syncs table
drizzle/meta/_journal.json Updated migration journal with new migration entry
drizzle/meta/0005_snapshot.json Complete database schema snapshot after migration
lib/types.ts Added syncedFromIcloud field to ShiftWithCalendar interface
messages/en.json Added English translations for display modes, visibility options, and synced shifts dialog
messages/de.json Added German translations for display modes, visibility options, and synced shifts dialog
components/synced-shifts-dialog.tsx New dialog component to display synced shifts when clicking minimal-mode badges
components/icloud-sync-manage-dialog.tsx Added display mode selector and visibility checkboxes with toggle handlers
components/calendar-grid.tsx Implemented logic to render shifts based on display mode (normal vs minimal badge)
app/page.tsx Added iCloud syncs state management and handler for viewing synced shifts
app/api/shifts/route.ts Added filtering to exclude shifts from hidden iCloud syncs
app/api/shifts/stats/route.ts Added filtering to exclude shifts from hidden or stats-excluded iCloud syncs
app/api/icloud-syncs/route.ts Added displayMode field to POST handler
app/api/icloud-syncs/[id]/route.ts Added PATCH support for displayMode, isHidden, and hideFromStats with event emission
hooks/useCalendars.ts Code formatting changes only (no functional changes)

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

🧹 Nitpick comments (5)
components/synced-shifts-dialog.tsx (1)

60-62: Consider using 20% opacity for consistency.

The background uses ${shift.color}10 (~6% opacity), but the coding guidelines specify using 20% opacity (${color}20) for backgrounds. This would provide more visual consistency across the application.

As per coding guidelines, apply this diff:

                 style={{
                   backgroundColor: shift.color
-                    ? `${shift.color}10`
-                    : "#3b82f610",
+                    ? `${shift.color}20`
+                    : "#3b82f620",
                   borderLeft: `3px solid ${shift.color || "#3b82f6"}`,
                 }}
app/api/icloud-syncs/route.ts (1)

40-40: Validate displayMode on create to keep data coherent

POST now accepts displayMode but will store any string; UI expects just "normal" or "minimal". Consider clamping/validating here to avoid bad states:

-    const { calendarId, name, icloudUrl, color, displayMode } = body;
+    const { calendarId, name, icloudUrl, color, displayMode } = body;
@@
-        color: color || "#3b82f6",
-        displayMode: displayMode || "normal",
+        color: color || "#3b82f6",
+        displayMode:
+          displayMode === "minimal" || displayMode === "normal"
+            ? displayMode
+            : "normal",

This keeps persisted values aligned with the rendering logic in the calendar grid.

As per coding guidelines for app/api/**/route.ts, your GET behavior (400 on missing calendarId, error logging) is already correct.

Also applies to: 68-68

app/api/icloud-syncs/[id]/route.ts (1)

47-48: Harden PATCH handling of displayMode

PATCH now updates displayMode, but like POST it will accept any string. To avoid invalid modes leaking into the DB and breaking calendar rendering, mirror the create-side validation here:

-    const { name, icloudUrl, color, displayMode, isHidden, hideFromStats } =
-      body;
+    const { name, icloudUrl, color, displayMode, isHidden, hideFromStats } =
+      body;
@@
-    // Validate iCloud URL if provided
+    // Validate iCloud URL if provided
     if (icloudUrl !== undefined && !isValidICloudUrl(icloudUrl)) {
@@
-    if (name !== undefined) updateData.name = name;
+    if (name !== undefined) updateData.name = name;
     if (icloudUrl !== undefined) updateData.icloudUrl = icloudUrl;
     if (color !== undefined) updateData.color = color;
-    if (displayMode !== undefined) updateData.displayMode = displayMode;
+    if (displayMode !== undefined) {
+      if (displayMode !== "normal" && displayMode !== "minimal") {
+        return NextResponse.json(
+          { error: "Invalid display mode" },
+          { status: 400 }
+        );
+      }
+      updateData.displayMode = displayMode;
+    }

The new visibility event emission for isHidden / hideFromStats looks good and matches the SSE pattern used elsewhere.

As per coding guidelines, dynamic route params are correctly awaited and API errors are logged.

Also applies to: 68-70, 104-112

app/page.tsx (1)

88-113: Deduplicate iCloud sync fetch logic

The fetch to /api/icloud-syncs?calendarId=... is implemented both in the useEffect for selectedCalendar and again inside onSyncComplete. Extracting this into a useCallback (e.g., const refetchIcloudSyncs = useCallback(...)) and reusing it in both places would avoid duplication and keep error handling in one place.

As per coding guidelines, you’re correctly using formatDateToLocal and statsRefreshTrigger for shift toggling and stats refresh.

Also applies to: 897-921

components/calendar-grid.tsx (1)

4-4: iCloud display modes and minimal badges are implemented correctly

The grid cleanly separates regular shifts from iCloud-synced ones, respects each sync’s displayMode when deciding between full entries vs minimal badges, and uses the new onShowSyncedShifts callback and icloud.syncedShiftsCount key as intended. If you ever see performance pressure with many syncs, you could precompute a map of icloudSyncId → { displayMode, color } once per render instead of calling icloudSyncs.find(...) for each shift, but it’s not critical right now.

As per coding guidelines for calendar*.tsx, left-click toggles shifts and right-click opens notes with e.preventDefault(), which you’ve preserved.

Also applies to: 10-24, 26-40, 173-299

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9653dca and d560686.

📒 Files selected for processing (16)
  • app/api/icloud-syncs/[id]/route.ts (3 hunks)
  • app/api/icloud-syncs/route.ts (2 hunks)
  • app/api/shifts/route.ts (4 hunks)
  • app/api/shifts/stats/route.ts (2 hunks)
  • app/page.tsx (8 hunks)
  • components/calendar-grid.tsx (4 hunks)
  • components/icloud-sync-manage-dialog.tsx (13 hunks)
  • components/synced-shifts-dialog.tsx (1 hunks)
  • drizzle/0005_silky_smiling_tiger.sql (1 hunks)
  • drizzle/meta/0005_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • hooks/useCalendars.ts (1 hunks)
  • lib/db/schema.ts (1 hunks)
  • lib/types.ts (1 hunks)
  • messages/de.json (2 hunks)
  • messages/en.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

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

Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Files:

  • lib/types.ts
  • components/icloud-sync-manage-dialog.tsx
  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • components/synced-shifts-dialog.tsx
  • lib/db/schema.ts
  • hooks/useCalendars.ts
  • app/api/icloud-syncs/[id]/route.ts
  • app/api/shifts/stats/route.ts
  • components/calendar-grid.tsx
  • app/page.tsx
components/**/*-dialog.tsx

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

components/**/*-dialog.tsx: Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission
Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Files:

  • components/icloud-sync-manage-dialog.tsx
  • components/synced-shifts-dialog.tsx
components/**/*.tsx

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

Use PRESET_COLORS constant array for all color picker options

Files:

  • components/icloud-sync-manage-dialog.tsx
  • components/synced-shifts-dialog.tsx
  • components/calendar-grid.tsx
app/api/**/*.ts

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

app/api/**/*.ts: Always await dynamic route params in API routes and server components (Next.js 16 breaking change)
Create API route handlers for new database tables at app/api/tablename/route.ts and app/api/tablename/[id]/route.ts
Log all API errors with console.error() for debugging

Files:

  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/route.ts
  • app/api/shifts/stats/route.ts
app/api/**/route.ts

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

API GET routes must validate required query parameters and return 400 status for missing params

Files:

  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/route.ts
  • app/api/shifts/stats/route.ts
app/**/*.{ts,tsx}

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

Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Files:

  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/route.ts
  • app/api/shifts/stats/route.ts
  • app/page.tsx
lib/db/schema.ts

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

lib/db/schema.ts: Database schema changes in lib/db/schema.ts require running npm run db:generate followed by npm run db:migrate
Use cascade delete relationships in database schema (onDelete: "cascade") for dependent tables
Drizzle ORM timestamp fields must use { mode: "timestamp" } and are stored as integers, auto-converted to Date objects
Export type definitions from database schema as TableName = typeof tableName.$inferSelect for use in components and API routes

Files:

  • lib/db/schema.ts
components/**/calendar*.tsx

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

Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Files:

  • components/calendar-grid.tsx
app/page.tsx

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

app/page.tsx: Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password
Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar
Refresh data after mutations by incrementing statsRefreshTrigger counter

Files:

  • app/page.tsx
app/**/*.tsx

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

app/**/*.tsx: Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI
Shift toggle logic must check if matching shift exists: delete if present, create if not
Use locale-specific date formatters in components: locale === "de" ? de : enUS
Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Files:

  • app/page.tsx
🧠 Learnings (13)
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
  • components/calendar-grid.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*.tsx : Use PRESET_COLORS constant array for all color picker options

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.{ts,tsx} : Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission

Applied to files:

  • components/synced-shifts-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/calendar*.tsx : Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Applied to files:

  • components/synced-shifts-dialog.tsx
  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Applied to files:

  • components/synced-shifts-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Shift toggle logic must check if matching shift exists: delete if present, create if not

Applied to files:

  • components/synced-shifts-dialog.tsx
  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use locale-specific date formatters in components: locale === "de" ? de : enUS

Applied to files:

  • components/synced-shifts-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Drizzle ORM timestamp fields must use { mode: "timestamp" } and are stored as integers, auto-converted to Date objects

Applied to files:

  • lib/db/schema.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password

Applied to files:

  • hooks/useCalendars.ts
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar

Applied to files:

  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Refresh data after mutations by incrementing statsRefreshTrigger counter

Applied to files:

  • app/page.tsx
🧬 Code graph analysis (6)
components/icloud-sync-manage-dialog.tsx (4)
components/ui/button.tsx (1)
  • Button (60-60)
components/ui/checkbox.tsx (1)
  • Checkbox (32-32)
components/ui/label.tsx (1)
  • Label (24-24)
components/ui/select.tsx (5)
  • Select (177-177)
  • SelectTrigger (185-185)
  • SelectValue (186-186)
  • SelectContent (178-178)
  • SelectItem (180-180)
app/api/shifts/route.ts (1)
lib/db/schema.ts (3)
  • shifts (43-76)
  • calendars (4-17)
  • icloudSyncs (19-41)
components/synced-shifts-dialog.tsx (3)
lib/types.ts (1)
  • ShiftWithCalendar (14-33)
components/ui/dialog.tsx (5)
  • Dialog (139-139)
  • DialogContent (141-141)
  • DialogHeader (144-144)
  • DialogTitle (147-147)
  • DialogDescription (142-142)
lib/db/schema.ts (1)
  • shifts (43-76)
app/api/icloud-syncs/[id]/route.ts (1)
lib/event-emitter.ts (1)
  • eventEmitter (38-38)
app/api/shifts/stats/route.ts (2)
lib/db/index.ts (1)
  • db (19-19)
lib/db/schema.ts (2)
  • shifts (43-76)
  • icloudSyncs (19-41)
components/calendar-grid.tsx (2)
lib/db/schema.ts (3)
  • ICloudSync (121-121)
  • shifts (43-76)
  • icloudSyncs (19-41)
lib/types.ts (2)
  • ICloudSync (2-2)
  • ShiftWithCalendar (14-33)
🔇 Additional comments (14)
lib/types.ts (1)

29-29: LGTM!

The addition of syncedFromIcloud field to the ShiftWithCalendar interface is appropriate and aligns with the new iCloud synchronization features introduced in this PR.

hooks/useCalendars.ts (1)

44-48: LGTM!

The reformatting of the request body improves readability without affecting functionality.

drizzle/0005_silky_smiling_tiger.sql (1)

1-3: LGTM!

The migration properly adds three new columns to the icloud_syncs table with appropriate defaults and constraints. The NOT NULL constraints paired with default values ensure data integrity for existing and new records.

app/api/shifts/stats/route.ts (1)

58-71: LGTM!

The filtering logic correctly excludes shifts from hidden iCloud syncs or those marked to hide from stats. The use of or(isNull(...), and(...)) properly handles shifts that aren't synced at all while filtering out hidden or stats-excluded synced shifts.

lib/db/schema.ts (1)

29-33: LGTM!

The three new fields for iCloud sync display and visibility options are properly defined with appropriate types, defaults, and constraints. The use of { mode: "boolean" } for boolean fields and proper default values ensures data integrity.

drizzle/meta/0005_snapshot.json (1)

169-192: LGTM!

The snapshot correctly reflects the new columns added to the icloud_syncs table, matching both the migration SQL and schema definition.

components/synced-shifts-dialog.tsx (1)

24-97: LGTM!

The dialog component is well-structured and follows best practices:

  • Properly controls open state via props
  • Uses translations for all user-facing text
  • Implements locale-aware date formatting
  • Handles empty state appropriately
  • No internal state that needs resetting
app/api/shifts/route.ts (2)

45-58: LGTM!

The integration of iCloud sync filtering is correctly implemented. The logic properly excludes shifts from hidden syncs while still including unsynced shifts. Note that this endpoint correctly checks only isHidden (not hideFromStats), which is appropriate for display purposes.


64-70: LGTM!

The calendar-wide query correctly applies the same filtering logic as the date-specific query, ensuring consistent behavior for hidden iCloud syncs across both query paths.

messages/de.json (1)

164-170: New iCloud-related German translations look consistent

Keys, placeholders ({count}, {date}), and wording align with the new UI and the English source, and they follow the existing icloud namespace structure.

As per coding guidelines, user-facing text is correctly sourced from translations.

Also applies to: 183-186

messages/en.json (1)

164-170: English iCloud display/visibility strings are well-structured

The new keys and placeholders match how they’re consumed (e.g., syncedShiftsCount with {count}) and stay consistent with existing icloud copy.

As per coding guidelines, these keys cleanly support useTranslations() usage in the new UI.

Also applies to: 183-186

drizzle/meta/_journal.json (1)

39-45: Migration journal entry for 0005 is consistent

The new entry continues the idx sequence, uses the same version format, and matches the existing metadata structure.

app/page.tsx (1)

133-140: Synced shifts dialog integration is clean

The new showSyncedShiftsDialog / selectedSyncedShifts state, handleShowSyncedShifts callback, and SyncedShiftsDialog rendering are wired correctly and match the calendar grid’s new onShowSyncedShifts API.

Also applies to: 484-491, 934-940

components/icloud-sync-manage-dialog.tsx (1)

14-21: Display mode and visibility controls in iCloud sync dialog are well integrated

formDisplayMode is correctly threaded through create/update flows, the new Select uses the added translation keys, and the visibility toggles (isHidden, hideFromStats) cleanly call back into the API and parent via onSyncComplete, while the dialog still manages its open state and internal resets properly.

As per coding guidelines for dialogs and components, this uses useTranslations(), PRESET_COLORS, and resets internal state on open changes.

Also applies to: 55-56, 91-92, 125-126, 134-135, 165-166, 174-175, 255-256, 265-265, 267-293, 301-302, 323-378, 381-430, 484-510, 523-524

@panteLx panteLx merged commit 5e59f22 into main Dec 3, 2025
1 check passed
@panteLx panteLx deleted the feat/icloud-sync-display-mode-visibility branch December 3, 2025 14:36
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: 0

♻️ Duplicate comments (1)
app/page.tsx (1)

3-3: Remove unused ShiftPreset import

ShiftPreset is not referenced anywhere in this file; keeping it around adds noise and may confuse future readers. Consider dropping it from the schema import.

-import { CalendarNote, ShiftPreset, ICloudSync } from "@/lib/db/schema";
+import { CalendarNote, ICloudSync } from "@/lib/db/schema";

Also applies to: 40-40

🧹 Nitpick comments (3)
app/page.tsx (2)

88-114: iCloud syncs fetch flow is well-factored; consider handling non‑OK responses explicitly

Using a useCallback for fetchICloudSyncs and calling it from both the useEffect and onSyncComplete centralizes the iCloud sync loading nicely and avoids duplication. Behavior on calendar change and after sync completion looks correct and respects the stats refresh guideline.

One minor improvement: when response.ok is false, the function currently does nothing, which silently leaves stale data. You could log or surface a toast in that branch to aid debugging failed loads.

Also applies to: 897-907


133-140: Synced shifts dialog wiring is consistent; optional cleanup on close

The showSyncedShiftsDialog / selectedSyncedShifts / selectedDayDate flow matches how DayShiftsDialog is driven and integrates cleanly with onShowSyncedShifts from CalendarGrid. Behavior is correct.

If you want stricter state hygiene (mirroring handleNoteDialogChange), you could clear the selection when the synced‑shifts dialog closes:

+  const handleSyncedShiftsDialogChange = (open: boolean) => {
+    setShowSyncedShiftsDialog(open);
+    if (!open) {
+      setSelectedSyncedShifts([]);
+      setSelectedDayDate(null);
+    }
+  };
...
-      <SyncedShiftsDialog
-        open={showSyncedShiftsDialog}
-        onOpenChange={setShowSyncedShiftsDialog}
+      <SyncedShiftsDialog
+        open={showSyncedShiftsDialog}
+        onOpenChange={handleSyncedShiftsDialogChange}
         date={selectedDayDate}
         shifts={selectedSyncedShifts}
       />

This is purely optional, as the current behavior is functionally fine. Based on learnings, this keeps dialog-related selection state self-contained.

Also applies to: 484-491, 921-927

components/calendar-grid.tsx (1)

173-296: Synced shifts rendering correctly handles normal/minimal modes; consider precomputing sync lookup map

The new IIFE cleanly separates:

  • minimal-mode synced shifts into syncedShiftsByMode per icloudSyncId, rendered as per-sync badges, and
  • all regular shifts plus synced shifts with displayMode === "normal" or missing sync into normalDisplayShifts.

This fixes the earlier orphan-sync case by treating !sync as normal display and respects the interaction guideline: when selectedPresetId is set, clicks on “+X more” and minimal badges fall through to the day button (toggling shifts) instead of opening dialogs. As per coding guidelines, left-click toggling remains the primary interaction.

One optional refinement: icloudSyncs.find is called multiple times inside the per-day rendering. You could precompute a map once per render for small perf and clarity gains:

-import { useRef, useEffect } from "react";
+import { useRef, useEffect, useMemo } from "react";
...
 export function CalendarGrid({
   ...
 }: CalendarGridProps) {
   const t = useTranslations();
   const pressTimerRef = useRef<Record<string, NodeJS.Timeout>>({});
+
+  const icloudSyncById = useMemo(
+    () => Object.fromEntries(icloudSyncs.map((s) => [s.id, s])),
+    [icloudSyncs]
+  );
...
-                dayShifts.forEach((shift) => {
+                dayShifts.forEach((shift) => {
                   if (shift.syncedFromIcloud && shift.icloudSyncId) {
-                    const sync = icloudSyncs.find(
-                      (s) => s.id === shift.icloudSyncId
-                    );
+                    const sync = icloudSyncById[shift.icloudSyncId];
...
-                  const sync = icloudSyncs.find(
-                    (sync) => sync.id === s.icloudSyncId
-                  );
+                  const sync = s.icloudSyncId
+                    ? icloudSyncById[s.icloudSyncId]
+                    : undefined;
...
-                        const sync = icloudSyncs.find((s) => s.id === syncId);
+                        const sync = icloudSyncById[syncId];

Not required for correctness, but it removes repeated linear searches in the hot rendering path.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d560686 and 4e852c7.

📒 Files selected for processing (2)
  • app/page.tsx (9 hunks)
  • components/calendar-grid.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
components/**/*.tsx

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

Use PRESET_COLORS constant array for all color picker options

Files:

  • components/calendar-grid.tsx
**/*.{ts,tsx}

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

Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Files:

  • components/calendar-grid.tsx
  • app/page.tsx
components/**/calendar*.tsx

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

Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Files:

  • components/calendar-grid.tsx
app/page.tsx

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

app/page.tsx: Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password
Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar
Refresh data after mutations by incrementing statsRefreshTrigger counter

Files:

  • app/page.tsx
app/**/*.{ts,tsx}

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

Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Files:

  • app/page.tsx
app/**/*.tsx

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

app/**/*.tsx: Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI
Shift toggle logic must check if matching shift exists: delete if present, create if not
Use locale-specific date formatters in components: locale === "de" ? de : enUS
Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Files:

  • app/page.tsx
🧠 Learnings (11)
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/calendar*.tsx : Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Applied to files:

  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Shift toggle logic must check if matching shift exists: delete if present, create if not

Applied to files:

  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar

Applied to files:

  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Applied to files:

  • components/calendar-grid.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.{ts,tsx} : Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use locale-specific date formatters in components: locale === "de" ? de : enUS

Applied to files:

  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Refresh data after mutations by incrementing statsRefreshTrigger counter

Applied to files:

  • app/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-push
🔇 Additional comments (1)
components/calendar-grid.tsx (1)

4-5: CalendarGrid public API extension for iCloud syncs looks solid

Adding icloudSyncs: ICloudSync[] and the optional onShowSyncedShifts callback cleanly extends the grid’s responsibilities without breaking existing behavior. Typing is explicit, and the new props are threaded through the component consistently.

Also applies to: 10-24, 26-40

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.

1 participant