feat: add iCloud sync display modes and manage visibility options#8
feat: add iCloud sync display modes and manage visibility options#8
Conversation
- 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.
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
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. Comment |
There was a problem hiding this comment.
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) andhideFromStats(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) |
There was a problem hiding this comment.
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: ValidatedisplayModeon create to keep data coherentPOST now accepts
displayModebut 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 missingcalendarId, error logging) is already correct.Also applies to: 68-68
app/api/icloud-syncs/[id]/route.ts (1)
47-48: Harden PATCH handling ofdisplayModePATCH 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/hideFromStatslooks 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 logicThe fetch to
/api/icloud-syncs?calendarId=...is implemented both in theuseEffectforselectedCalendarand again insideonSyncComplete. Extracting this into auseCallback(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
formatDateToLocalandstatsRefreshTriggerfor 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 correctlyThe grid cleanly separates regular shifts from iCloud-synced ones, respects each sync’s
displayModewhen deciding between full entries vs minimal badges, and uses the newonShowSyncedShiftscallback andicloud.syncedShiftsCountkey as intended. If you ever see performance pressure with many syncs, you could precompute a map oficloudSyncId → { displayMode, color }once per render instead of callingicloudSyncs.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 withe.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
📒 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.tscomponents/icloud-sync-manage-dialog.tsxapp/api/shifts/route.tsapp/api/icloud-syncs/route.tscomponents/synced-shifts-dialog.tsxlib/db/schema.tshooks/useCalendars.tsapp/api/icloud-syncs/[id]/route.tsapp/api/shifts/stats/route.tscomponents/calendar-grid.tsxapp/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.tsxcomponents/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.tsxcomponents/synced-shifts-dialog.tsxcomponents/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.tsapp/api/icloud-syncs/route.tsapp/api/icloud-syncs/[id]/route.tsapp/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.tsapp/api/icloud-syncs/route.tsapp/api/icloud-syncs/[id]/route.tsapp/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.tsapp/api/icloud-syncs/route.tsapp/api/icloud-syncs/[id]/route.tsapp/api/shifts/stats/route.tsapp/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.tsxcomponents/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.tsxapp/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.tsxcomponents/calendar-grid.tsxapp/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.tsxapp/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.tsxcomponents/calendar-grid.tsxapp/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.tsxapp/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.tsapp/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.tsxapp/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
syncedFromIcloudfield to theShiftWithCalendarinterface 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_syncstable 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_syncstable, 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(nothideFromStats), 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 consistentKeys, 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-structuredThe new keys and placeholders match how they’re consumed (e.g.,
syncedShiftsCountwith{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 consistentThe 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 cleanThe new
showSyncedShiftsDialog/selectedSyncedShiftsstate,handleShowSyncedShiftscallback, andSyncedShiftsDialogrendering are wired correctly and match the calendar grid’s newonShowSyncedShiftsAPI.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
formDisplayModeis 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 viaonSyncComplete, 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 onopenchanges.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
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/page.tsx (1)
3-3: Remove unusedShiftPresetimport
ShiftPresetis 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 explicitlyUsing a
useCallbackforfetchICloudSyncsand calling it from both theuseEffectandonSyncCompletecentralizes 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.okisfalse, 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 closeThe
showSyncedShiftsDialog/selectedSyncedShifts/selectedDayDateflow matches howDayShiftsDialogis driven and integrates cleanly withonShowSyncedShiftsfromCalendarGrid. 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 mapThe new IIFE cleanly separates:
- minimal-mode synced shifts into
syncedShiftsByModepericloudSyncId, rendered as per-sync badges, and- all regular shifts plus synced shifts with
displayMode === "normal"or missing sync intonormalDisplayShifts.This fixes the earlier orphan-sync case by treating
!syncas normal display and respects the interaction guideline: whenselectedPresetIdis 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.findis 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
📒 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.tsxapp/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.tsxapp/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.tsxapp/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.tsxapp/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.tsxapp/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 solidAdding
icloudSyncs: ICloudSync[]and the optionalonShowSyncedShiftscallback 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
Summary by CodeRabbit
New Features
Improvements
Localization
Chores
✏️ Tip: You can customize this high-level summary in your review settings.