feat: Add shift duration tracking to stats and enhance UI#61
Conversation
Adds duration tracking to shift statistics and surfaces total and per-type durations in the UI. Introduces duration calculation (handles overnight shifts and excludes all-day shifts) and aggregates totalMinutes per shift title and overall. Updates the stats API shape to return count and totalMinutes instead of a raw SQL count, and removes an unused SQL import. Adds utility functions to format durations and uses them in the UI. Enhances the stats component to show total shifts, formatted total hours, per-shift progress bars, count badges, and duration badges for clearer visibility of time spent. Improves readability and usability of shift metrics for easier analysis of distribution and time allocation.
|
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. WalkthroughAPI and UI now compute and expose per-shift durations. The stats route returns counts and totalMinutes per title plus an overall totalMinutes. A date-utils module adds duration calculation/formatting. The UI and messages are updated to display durations and use consolidated translation keys. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Browser / UI
participant Server as app/api/shifts/stats
participant DB as Database
Client->>Server: GET /api/shifts/stats
Server->>DB: SELECT id, title, startTime, endTime, isAllDay FROM shifts...
DB-->>Server: rows[]
Note over Server: iterate rows, for each row\nif not isAllDay -> calculateShiftDuration(startTime,endTime)\naccumulate count & totalMinutes per title
Server-->>Client: 200 { stats: { title: {count,totalMinutes} }, totalMinutes }
Client->>Client: render ShiftStats (counts, formatted durations)
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)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Actionable comments posted: 2
🧹 Nitpick comments (4)
lib/date-utils.ts (2)
18-34: Missing input validation for malformed time strings.The function assumes valid
HH:MMformat but doesn't guard against malformed input. IfstartTimeorendTimeis empty, malformed, or missing the colon,split(":").map(Number)will produceNaNvalues, leading to incorrect calculations.Consider adding validation or defensive parsing:
export function calculateShiftDuration( startTime: string, endTime: string ): number { + if (!startTime || !endTime || !startTime.includes(":") || !endTime.includes(":")) { + return 0; + } + const [startHour, startMinute] = startTime.split(":").map(Number); const [endHour, endMinute] = endTime.split(":").map(Number); + + if (isNaN(startHour) || isNaN(startMinute) || isNaN(endHour) || isNaN(endMinute)) { + return 0; + } const startMinutes = startHour * 60 + startMinute;
41-45: Handle negative minutes input.If a negative
minutesvalue is passed (e.g., due to a bug upstream),Math.flooron a negative number and the modulo operator will produce unexpected results (e.g.,-90→"-2:30"instead of a meaningful fallback).export function formatDuration(minutes: number): string { + if (minutes < 0) { + return "0:00"; + } const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours}:${String(mins).padStart(2, "0")}`; }app/api/shifts/stats/route.ts (1)
151-160: Consider usingObject.fromEntriesfor cleaner transformation.The
reducepattern works butObject.fromEntriesis more idiomatic for this use case.- const stats = Array.from(statsMap.entries()).reduce( - (acc, [title, data]) => { - acc[title] = { - count: data.count, - totalMinutes: data.totalMinutes, - }; - return acc; - }, - {} as Record<string, { count: number; totalMinutes: number }> - ); + const stats: Record<string, { count: number; totalMinutes: number }> = + Object.fromEntries(statsMap);components/shift-stats.tsx (1)
263-270: Progress bar width calculation may cause division by zero.If
totalShiftsis0, the width calculation(data.count / totalShifts) * 100will result inNaNorInfinity. Although the parent conditionObject.keys(stats.stats).length > 0suggests there are stats, it's possibletotalShiftscould still be0if all entries havecount: 0.style={{ - width: `${(data.count / totalShifts) * 100}%`, + width: `${totalShifts > 0 ? (data.count / totalShifts) * 100 : 0}%`, }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/api/shifts/stats/route.ts(3 hunks)components/shift-stats.tsx(4 hunks)lib/date-utils.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,json}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All code, comments, variable names, and messages must be in English
Files:
lib/date-utils.tscomponents/shift-stats.tsxapp/api/shifts/stats/route.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Only add comments for complex logic or non-obvious behavior; prefer self-documenting code with clear variable and function names
Files:
lib/date-utils.tscomponents/shift-stats.tsxapp/api/shifts/stats/route.ts
components/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
components/**/*.tsx: All dialog components must follow the unified design pattern with gradient backgrounds, consistent padding (p-6,px-6,pb-6), border styling (border-border/50), backdrop blur effects, and gradient text for titles
Dialog components must control state via props (open,onOpenChange), reset local state whenopenchanges to false, and includeonSubmitand optionalonDeletecallbacks
Files:
components/shift-stats.tsx
components/**/*shift*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Shift dialog must have
saveAsPresetenabled by default
Files:
components/shift-stats.tsx
app/api/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/api/**/*.ts: Store passwords as SHA-256 hashes in thepasswordHashfield and verify using utilities fromlib/password-utils.ts
For GET endpoints on password-protected calendars, check bothcalendar.passwordHash && calendar.isLockedbefore requiring password; for mutation endpoints (POST/PUT/PATCH/DELETE), check onlycalendar.passwordHash
For new API routes, follow request pattern: extract search params/body, validate required fields, return 400 for missing data, return 401 for auth failures
Files:
app/api/shifts/stats/route.ts
🧠 Learnings (13)
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Shift toggle logic: delete if shift exists, create if not (no modifications to existing shifts on simple toggle)
Applied to files:
components/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/page.tsx : Use `statsRefreshTrigger` counter state in main page to track and trigger mutation-related data refreshes
Applied to files:
components/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/page.tsx : State management on main page (`app/page.tsx`) must use `useState` for shifts, presets, notes, calendars and `useEffect` for data fetching on calendar/date changes
Applied to files:
components/shift-stats.tsxapp/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to components/**/*calendar*.tsx : Calendar interactions must support left-click to toggle shift with selected preset, right-click to open note dialog (prevent default context menu), and display `<StickyNote>` icon for days with notes
Applied to files:
components/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to hooks/*.ts : All data-fetching hooks (`useShifts`, `usePresets`, `useNotes`) must automatically call `getCachedPassword(calendarId)` before each fetch and return empty arrays on 401 responses for graceful degradation
Applied to files:
components/shift-stats.tsxapp/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Client-side components must use `getCachedPassword(calendarId)` to retrieve and `verifyAndCachePassword(calendarId, password)` to verify and cache passwords from `lib/password-cache.ts`
Applied to files:
components/shift-stats.tsxapp/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to lib/db/schema.ts : Define cascade delete relationships in the schema (e.g., `calendars` → `shifts`, `shiftPresets`, `calendarNotes` cascade delete; `shiftPresets` → `shifts` set null on delete)
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to lib/db/schema.ts : Generate database migrations with `npm run db:generate` and apply with `npm run db:migrate` after schema changes in `lib/db/schema.ts`; never use `db:push`
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.808Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.808Z
Learning: Use Drizzle Studio (`npm run db:studio`) for local database inspection and `npm run build` to validate TypeScript before deployment
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/api/**/*.ts : For GET endpoints on password-protected calendars, check both `calendar.passwordHash && calendar.isLocked` before requiring password; for mutation endpoints (POST/PUT/PATCH/DELETE), check only `calendar.passwordHash`
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to lib/db/schema.ts : Store timestamps in SQLite as integers using `{ mode: 'timestamp' }` in schema; they auto-convert to Date objects
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.808Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.808Z
Learning: When creating new database tables, add table definition to `lib/db/schema.ts`, export type with `$inferSelect`, run migrations, create API routes, and add translations
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : For mutations on password-protected calendars, check cached password before the mutation: `const result = await verifyAndCachePassword(calendarId, password)`. If `result.protected && !result.valid`, set `pendingAction` and show `PasswordDialog`
Applied to files:
app/api/shifts/stats/route.ts
🧬 Code graph analysis (2)
components/shift-stats.tsx (1)
lib/date-utils.ts (1)
formatDuration(41-45)
app/api/shifts/stats/route.ts (1)
lib/db/schema.ts (1)
shifts(49-82)
⏰ 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). (3)
- GitHub Check: Agent
- GitHub Check: build-dev
- GitHub Check: Tests & Checks
🔇 Additional comments (3)
app/api/shifts/stats/route.ts (1)
142-146: Potential issue with nullisAllDayfrom database.The schema shows
isAllDayhas a default offalse, but for older records or edge cases, accessingshift.isAllDaymight returnnullinstead offalse. The fallback|| falsehandles this, but TypeScript may not catch potential issues if the type suggests it's always boolean.The
|| falsefallback is appropriate defensive coding here.components/shift-stats.tsx (2)
8-8: LGTM!The import of
formatDurationand the updatedShiftStatsinterface correctly align with the API changes inroute.ts. The interface now properly reflects the enriched stats structure withcountandtotalMinutesper shift type.Also applies to: 14-15
250-252: LGTM on sorting logic.Sorting by
b.count - a.countcorrectly orders shift types by count in descending order, showing the most frequent shifts first.
There was a problem hiding this comment.
Pull request overview
This PR adds shift duration tracking capabilities to the statistics feature, calculating and displaying total hours worked in addition to shift counts. The implementation adds duration calculation utilities, updates the stats API to aggregate duration data per shift type, and enhances the UI with formatted duration displays using progress bars and badges.
Key Changes:
- Introduces
calculateShiftDurationandformatDurationutility functions for time calculations - Refactors stats API to fetch individual shift times and compute durations client-side instead of using SQL aggregation
- Enhances stats UI with separate badges for shift counts and total hours, plus improved visual styling
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| lib/date-utils.ts | Adds utility functions for calculating shift duration in minutes and formatting duration to HH:MM format |
| app/api/shifts/stats/route.ts | Removes SQL aggregation, fetches individual shift times, calculates durations excluding all-day shifts, and returns count + totalMinutes per shift type |
| components/shift-stats.tsx | Updates interface to include totalMinutes, displays duration badges alongside count badges with enhanced gradient styling |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
app/api/shifts/stats/route.ts (1)
128-137: Object transformation and totalMinutes computation align with new API contractReducing the
statsMapinto a plain object and separately summingtotalMinutesproduces a clear, serializable{ [title]: { count, totalMinutes } }plus an overalltotalMinutesfield that the UI can consume directly.If this endpoint ever becomes a hot path, you could micro-optimize by tracking a running total while building
statsMapinstead of re-reducing the object, but as written it’s perfectly readable and efficient for typical calendar sizes.Also applies to: 140-143, 145-151
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.github/copilot-instructions.md(1 hunks)app/api/shifts/stats/route.ts(3 hunks)components/day-shifts-dialog.tsx(1 hunks)components/shift-stats.tsx(4 hunks)components/shifts-list.tsx(2 hunks)lib/date-utils.ts(1 hunks)messages/de.json(2 hunks)messages/en.json(2 hunks)messages/it.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/date-utils.ts
🧰 Additional context used
📓 Path-based instructions (7)
components/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
components/**/*.tsx: All dialog components must follow the unified design pattern with gradient backgrounds, consistent padding (p-6,px-6,pb-6), border styling (border-border/50), backdrop blur effects, and gradient text for titles
Dialog components must control state via props (open,onOpenChange), reset local state whenopenchanges to false, and includeonSubmitand optionalonDeletecallbacks
Files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsxcomponents/shift-stats.tsx
**/*.{ts,tsx,js,json}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All code, comments, variable names, and messages must be in English
Files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsxcomponents/shift-stats.tsxmessages/de.jsonapp/api/shifts/stats/route.tsmessages/en.jsonmessages/it.json
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Only add comments for complex logic or non-obvious behavior; prefer self-documenting code with clear variable and function names
Files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsxcomponents/shift-stats.tsxapp/api/shifts/stats/route.ts
components/**/*shift*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Shift dialog must have
saveAsPresetenabled by default
Files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsxcomponents/shift-stats.tsx
messages/de.json
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use German translations with informal 'du' form (never formal 'Sie' form) - e.g., 'Möchtest du...' not 'Möchten Sie...'
Files:
messages/de.json
messages/{de,en}.json
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
messages/{de,en}.json: Never duplicate CRUD success/error messages in translation files; always usecommon.created,common.updated,common.deleted,common.createError,common.updateError,common.deleteErrorwith{item}parameter
Centralize validation messages invalidation.*namespace and form field labels inform.*namespace in translation files to prevent duplication
Files:
messages/de.jsonmessages/en.json
app/api/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/api/**/*.ts: Store passwords as SHA-256 hashes in thepasswordHashfield and verify using utilities fromlib/password-utils.ts
For GET endpoints on password-protected calendars, check bothcalendar.passwordHash && calendar.isLockedbefore requiring password; for mutation endpoints (POST/PUT/PATCH/DELETE), check onlycalendar.passwordHash
For new API routes, follow request pattern: extract search params/body, validate required fields, return 400 for missing data, return 401 for auth failures
Files:
app/api/shifts/stats/route.ts
🧠 Learnings (22)
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Shift toggle logic: delete if shift exists, create if not (no modifications to existing shifts on simple toggle)
Applied to files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsxcomponents/shift-stats.tsxapp/api/shifts/stats/route.tsmessages/en.json
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to components/**/*shift*.tsx : Shift dialog must have `saveAsPreset` enabled by default
Applied to files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : All user-facing text and messages in components must use `useTranslations()` from next-intl and reference translation keys
Applied to files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsxcomponents/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.808Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.808Z
Learning: When creating new dialog components, place in `components/` directory using shadcn/ui Dialog, use `useTranslations()` for all text, and integrate in `app/page.tsx`
Applied to files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Use `showMobileCalendarDialog` to separate calendar selector on mobile UI
Applied to files:
components/day-shifts-dialog.tsxcomponents/shifts-list.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to messages/{de,en}.json : Never duplicate CRUD success/error messages in translation files; always use `common.created`, `common.updated`, `common.deleted`, `common.createError`, `common.updateError`, `common.deleteError` with `{item}` parameter
Applied to files:
.github/copilot-instructions.mdcomponents/shift-stats.tsxmessages/de.jsonmessages/en.jsonmessages/it.json
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Always use `formatDateToLocal()` for displaying dates in YYYY-MM-DD format in UI components
Applied to files:
components/shifts-list.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to components/**/*calendar*.tsx : Calendar interactions must support left-click to toggle shift with selected preset, right-click to open note dialog (prevent default context menu), and display `<StickyNote>` icon for days with notes
Applied to files:
components/shifts-list.tsxcomponents/shift-stats.tsxmessages/de.jsonmessages/en.jsonmessages/it.json
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Use locale-specific date formatting: `locale === 'de' ? de : enUS` from date-fns for calendar displays
Applied to files:
components/shifts-list.tsxcomponents/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/page.tsx : Use `statsRefreshTrigger` counter state in main page to track and trigger mutation-related data refreshes
Applied to files:
components/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to **/*.{ts,tsx,js,json} : All code, comments, variable names, and messages must be in English
Applied to files:
components/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to messages/de.json : Use German translations with informal 'du' form (never formal 'Sie' form) - e.g., 'Möchtest du...' not 'Möchten Sie...'
Applied to files:
components/shift-stats.tsx
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to messages/{de,en}.json : Centralize validation messages in `validation.*` namespace and form field labels in `form.*` namespace in translation files to prevent duplication
Applied to files:
components/shift-stats.tsxmessages/de.jsonmessages/it.json
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/page.tsx : State management on main page (`app/page.tsx`) must use `useState` for shifts, presets, notes, calendars and `useEffect` for data fetching on calendar/date changes
Applied to files:
components/shift-stats.tsxapp/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to hooks/*.ts : All data-fetching hooks (`useShifts`, `usePresets`, `useNotes`) must automatically call `getCachedPassword(calendarId)` before each fetch and return empty arrays on 401 responses for graceful degradation
Applied to files:
components/shift-stats.tsxapp/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : Client-side components must use `getCachedPassword(calendarId)` to retrieve and `verifyAndCachePassword(calendarId, password)` to verify and cache passwords from `lib/password-cache.ts`
Applied to files:
components/shift-stats.tsxapp/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to lib/db/schema.ts : Define cascade delete relationships in the schema (e.g., `calendars` → `shifts`, `shiftPresets`, `calendarNotes` cascade delete; `shiftPresets` → `shifts` set null on delete)
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to lib/db/schema.ts : Generate database migrations with `npm run db:generate` and apply with `npm run db:migrate` after schema changes in `lib/db/schema.ts`; never use `db:push`
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.808Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.808Z
Learning: Use Drizzle Studio (`npm run db:studio`) for local database inspection and `npm run build` to validate TypeScript before deployment
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to lib/db/schema.ts : Store timestamps in SQLite as integers using `{ mode: 'timestamp' }` in schema; they auto-convert to Date objects
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/api/**/*.ts : For GET endpoints on password-protected calendars, check both `calendar.passwordHash && calendar.isLocked` before requiring password; for mutation endpoints (POST/PUT/PATCH/DELETE), check only `calendar.passwordHash`
Applied to files:
app/api/shifts/stats/route.ts
📚 Learning: 2025-12-07T04:12:10.807Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T04:12:10.807Z
Learning: Applies to app/**/*.tsx : For mutations on password-protected calendars, check cached password before the mutation: `const result = await verifyAndCachePassword(calendarId, password)`. If `result.protected && !result.valid`, set `pendingAction` and show `PasswordDialog`
Applied to files:
app/api/shifts/stats/route.ts
🧬 Code graph analysis (2)
components/shift-stats.tsx (1)
lib/date-utils.ts (1)
formatDuration(63-73)
app/api/shifts/stats/route.ts (2)
lib/db/schema.ts (1)
shifts(49-82)lib/date-utils.ts (1)
calculateShiftDuration(18-56)
⏰ 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-dev
🔇 Additional comments (10)
components/shifts-list.tsx (1)
61-61: Consistent use of sharedcommon.shiftstranslation keyBoth header and per-day plural labels now use
t("common.shifts"), matching the updated locale files and keeping i18n usage consistent across components.Also applies to: 150-151
components/day-shifts-dialog.tsx (1)
51-53: Pluralization now aligned with sharedcommon.shiftskeyUsing
t("shift.shift_one")for singular andt("common.shifts")for plural keeps wording correct and consistent with the updated translation structure..github/copilot-instructions.md (1)
165-174: Example toast now matches centralizedcommon.*+common.shiftspattern
toast.success(t("common.created", { item: t("common.shifts") }))correctly demonstrates the shared CRUD message pattern with a reusable item label.messages/it.json (1)
26-28: Italian keys forcommon.shiftsandstats.hoursmatch UI usageThe added
"common.shifts": "Turni"and"stats.hours": "Ore"align with the new stats UI and keep terminology consistent with the rest of the Italian locale.Also applies to: 180-182
messages/de.json (1)
26-28: German translations forcommon.shiftsandstats.hoursare consistentAdding
"common.shifts": "Schichten"and"stats.hours": "Stunden"fits existing wording and supports the updated stats UI without introducing any formal “Sie” phrasing.Also applies to: 181-182
messages/en.json (1)
26-28: Englishcommon.shiftsandstats.hourskeys correctly support stats UIThe new
"common.shifts": "Shifts"and"stats.hours": "Hours"entries provide the labels needed by the updated stats components and stay consistent with the rest of the locale structure.Also applies to: 181-182
app/api/shifts/stats/route.ts (1)
9-12: Duration-aware stats aggregation is correct and reuses shared utilityThe handler now:
- Selects
title,startTime,endTime, andisAllDayfor each shift.- Aggregates per title into a
Map<string, { count; totalMinutes }>usingcalculateShiftDuration, treating all-day shifts as zero minutes.- Honors both external sync and preset
hideFromStatsflags in the query filter.This removes the earlier duplicated duration logic and centralizes time calculations via
calculateShiftDuration, which is a solid improvement for maintainability and correctness.Also applies to: 85-125
components/shift-stats.tsx (3)
8-16: Client stats model correctly matches the new API and aggregates totals safelyUpdating
ShiftStatstostats: Record<string, { count; totalMinutes }>plus a top-leveltotalMinutesand deriving:
totalShiftsviasum(data.count)andtotalMinutesviastats?.totalMinutes || 0keeps the client in sync with the API and handles the “no data” case without runtime issues.
Also applies to: 82-88
105-120: Collapsed header summary cleanly surfaces total shifts and durationWhen collapsed, the component now shows:
- A badge for
totalShifts, and- A conditional badge for formatted total duration via
formatDuration(totalMinutes),giving a quick overview without extra text and without impacting loading behavior.
210-247: Expanded stats UI provides clear total and per-type breakdown with proper i18nThe new expanded view:
- Adds a “Total” card that shows:
- Overall shift count labeled with
t("common.shifts")- Overall duration labeled with
t("stats.hours")and rendered viaformatDuration(totalMinutes)- Renders per-title rows sorted by
countwith:
- A progress bar proportional to
data.count / totalShifts- Count and (when > 0) duration badges, both using
formatDuration(data.totalMinutes).This fixes the earlier hardcoded German labels, fully adheres to the translation guidelines, and gives a very readable visualization of shift distribution and time spent.
Also applies to: 249-289
Adds duration tracking to shift statistics and surfaces total and per-type durations in the UI.
Introduces duration calculation (handles overnight shifts and excludes all-day shifts) and aggregates totalMinutes per shift title and overall. Updates the stats API shape to return count and totalMinutes instead of a raw SQL count, and removes an unused SQL import.
Adds utility functions to format durations and uses them in the UI. Enhances the stats component to show total shifts, formatted total hours, per-shift progress bars, count badges, and duration badges for clearer visibility of time spent.
Improves readability and usability of shift metrics for easier analysis of distribution and time allocation.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.