Skip to content

feat: Add advanced shift statistics with charts and i18n updates#80

Merged
panteLx merged 2 commits intomainfrom
feat/enhanced-statistics
Dec 16, 2025
Merged

feat: Add advanced shift statistics with charts and i18n updates#80
panteLx merged 2 commits intomainfrom
feat/enhanced-statistics

Conversation

@panteLx
Copy link
Owner

@panteLx panteLx commented Dec 16, 2025

Adds richer shift analytics and visualization support.

Adds daily breakdown and trend data, total/min/max durations, and averaged metrics (per-shift and per-day) in the stats API while improving date handling and excluding all-day shifts from duration metrics.

Enhances the UI with new view modes (overview, pie, bar, radar), prepared datasets and custom tooltips, summary cards and detailed lists for better insights, and integrates interactive charts via Recharts. Also centralizes weekday translations to the common namespace.

Includes Recharts in dependencies and updates lockfile to ship the new charting library, enabling better trend visualization and more actionable shift statistics.

Summary by CodeRabbit

  • New Features

    • Multi-view shift statistics dashboard: Overview, Pie, Bar and Radar views with responsive visuals.
    • Expanded analytics: per-type breakdowns, daily trends, totals, averages, min/max durations and trend data for visualization.
  • Localization

    • Updated translation keys and weekday labels in German, English and Italian to support the new stats views and labels.

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

Adds richer shift analytics and visualization support.

Adds daily breakdown and trend data, total/min/max durations, and averaged metrics (per-shift and per-day) in the stats API while improving date handling and excluding all-day shifts from duration metrics.

Enhances the UI with new view modes (overview, pie, bar, radar), prepared datasets and custom tooltips, summary cards and detailed lists for better insights, and integrates interactive charts via Recharts. Also centralizes weekday translations to the common namespace.

Includes Recharts in dependencies and updates lockfile to ship the new charting library, enabling better trend visualization and more actionable shift statistics.
Copilot AI review requested due to automatic review settings December 16, 2025 23:00
@coderabbitai
Copy link

coderabbitai bot commented Dec 16, 2025

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

The PR adds per-shift duration and daily-trend aggregations to the stats API, expands the ShiftStats UI with overview/pie/bar/radar chart views (using Recharts), restructures weekday and stats i18n keys, and adds recharts to dependencies.

Changes

Cohort / File(s) Summary
API Statistics Enhancement
app/api/shifts/stats/route.ts
Add id/date selection; compute per-shift durations (exclude all-day), per-title aggregates (count, totalMinutes), min/max duration tracking; build dailyStats map and trendData (date, count, totalMinutes); compute totals and averages; return additional metrics (totalShifts, avgMinutesPerShift, avgShiftsPerDay, avgMinutesPerDay, minDuration, maxDuration, daysWithShifts, trendData).
UI Charts Expansion
components/shift-stats.tsx
Introduce viewMode (overview, pie, bar, radar); prepare pieData/barData/radarData and a CustomTooltip; add responsive radius hook and CHART_COLORS; render Recharts-based PieChart, BarChart, RadarChart and an expanded Overview with summary cards and per-type micro-bars; adapt loading/skeleton states.
i18n / Calendar Headers
components/calendar-grid.tsx, messages/en.json, messages/de.json, messages/it.json
Migrate weekday keys from calendar_grid to common.weekday; remove old calendar_grid block; expand view and stats translation keys with new metric labels (overview, distribution, comparison, radar, totalShifts, totalHours, avgPerShift, shortestShift, longestShift, byType, avgHoursPerShift, radarDescription); remove old stats.total key.
Dependency Addition
package.json
Add runtime dependency recharts ^3.6.0.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Browser UI (ShiftStats)
  participant API as app/api/shifts/stats/route
  participant DB as Database
  Note over Client,API: User opens ShiftStats (viewMode selected)
  Client->>API: GET /api/shifts/stats?range...
  API->>DB: Query shifts (include id, date, start, end, allDay, title...)
  DB-->>API: shifts[]
  API->>API: compute durations, per-title aggregates, dailyStats, totals/averages
  API-->>Client: JSON { stats, totalShifts, trendData, ... }
  Client->>Client: build pieData/barData/radarData from stats
  Client->>Recharts: render selected chart (pie/bar/radar) or Overview
  Note over Client,Recharts: Interactive tooltip/legend driven by prepared data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Review points:
    • app/api/shifts/stats/route.ts — date parsing edge cases, all-day exclusions, min/max initialization, rounding and empty-result guards.
    • components/shift-stats.tsx — correctness of data transforms for each chart, SSR-safe sizing hook, and loading/empty states.
    • i18n files — ensure keys renamed consistently and no missing translations.

Possibly related PRs

Suggested labels

enhancement

Poem

🐰
Charts hop in, numbers bright and spry,
Minutes counted, trends that reach the sky.
Pie, bar, radar — colors in a row,
Translated weekdays gently steal the show.
A tiny rabbit claps—analytics, go! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 clearly describes the main change: adding advanced shift statistics with charts and i18n updates, which aligns with the core modifications across the API, UI, and translation files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/enhanced-statistics

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.

@panteLx panteLx linked an issue Dec 16, 2025 that may be closed by this pull request
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: 3

🧹 Nitpick comments (3)
components/shift-stats.tsx (3)

176-206: Extract CustomTooltip outside the component to avoid re-creation on each render.

Defining CustomTooltip inside the component creates a new function reference on every render, which can cause unnecessary re-renders of chart components that use it.

Move the tooltip component and its type definition outside ShiftStats:

interface TooltipPayloadEntry {
  name: string;
  value: number;
  color: string;
  dataKey?: string;
  payload: { fullName?: string; name?: string };
}

const CustomTooltip = ({
  active,
  payload,
}: {
  active?: boolean;
  payload?: TooltipPayloadEntry[];
}) => {
  if (active && payload && payload.length) {
    return (
      <div className="bg-card/95 backdrop-blur-sm border border-border rounded-lg p-3 shadow-lg">
        <p className="font-semibold text-sm mb-1">
          {payload[0].payload.fullName || payload[0].name}
        </p>
        {payload.map((entry) => (
          <p key={entry.name} className="text-xs text-muted-foreground">
            <span style={{ color: entry.color }}>{entry.name}:</span>{" "}
            {entry.value}
            {entry.name === "hours" || entry.dataKey === "hours" ? "h" : ""}
          </p>
        ))}
      </div>
    );
  }
  return null;
};

501-527: Consider the UX of mixing shifts count and hours on the same axis.

The bar chart displays "shifts" (count) and "hours" (duration) on the same Y-axis. These have different units and scales, which may be confusing for users when comparing bars.

This is a minor UX consideration — the current approach works but a dual-axis chart or separate charts could provide clearer comparisons.


573-594: Legend items all display the same color, which doesn't add visual distinction.

All radar legend items use CHART_COLORS[0] (blue), making the color indicators redundant. Consider either:

  1. Assigning different colors per entry (like in the pie chart legend)
  2. Removing the color indicator since the radar chart distinguishes by series color, not by entry
                     {radarData.map((entry, index) => (
                       <div
                         key={entry.fullName}
                         className="flex items-center gap-2 p-2 rounded-lg bg-card/50 border border-border/30"
                       >
                         <div
                           className="w-3 h-3 rounded-sm flex-shrink-0"
                           style={{
-                            backgroundColor: CHART_COLORS[0],
+                            backgroundColor: CHART_COLORS[index % CHART_COLORS.length],
                           }}
                         />
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9cc8eb1 and ed21a10.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • app/api/shifts/stats/route.ts (3 hunks)
  • components/calendar-grid.tsx (1 hunks)
  • components/shift-stats.tsx (6 hunks)
  • messages/de.json (3 hunks)
  • messages/en.json (3 hunks)
  • messages/it.json (3 hunks)
  • package.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use formatDateToLocal() utility function for consistent date formatting in YYYY-MM-DD format
Centralize validation messages using validation.* namespace keys (e.g., validation.passwordMatch, validation.fileTooLarge) instead of creating message-specific keys
When checking calendar lock status, use isLocked field to determine if read access requires password; when checking write protection, check only passwordHash field
Hash passwords using SHA-256 via utilities in lib/password-utils.ts
Only add comments for complex logic or non-obvious behavior - write self-documenting code with clear variable and function names

Files:

  • components/calendar-grid.tsx
  • app/api/shifts/stats/route.ts
  • components/shift-stats.tsx
components/**/*.tsx

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

components/**/*.tsx: Store hex colors in format #3b82f6 and apply 20% opacity for backgrounds using ${color}20 pattern
All dialogs must follow the unified design pattern with gradient backgrounds, consistent padding (p-6, px-6, pb-6), border-border/50 styling, backdrop blur, and gradient text for titles
Centralize form field labels and placeholders using form.* namespace keys (e.g., form.nameLabel, form.namePlaceholder) instead of duplicating across components
Control dialog state via props (open, onOpenChange), reset local state when open changes to false, and use onSubmit and optional onDelete callbacks
Implement calendar interactions with left-click to toggle shift with selected preset, right-click to open note dialog (prevent default context menu), and delete if exists/create if not logic
Use PRESET_COLORS array for color selection in components
Use showMobileCalendarDialog to separate calendar selector logic for mobile UI

Files:

  • components/calendar-grid.tsx
  • components/shift-stats.tsx
**/*.tsx

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

Use centralized common messages with parametrized {item} parameter for CRUD operations instead of creating message-specific keys - use common.created, common.updated, common.deleted, common.createError, common.updateError, common.deleteError

Files:

  • components/calendar-grid.tsx
  • components/shift-stats.tsx
**/*.{ts,tsx,js,jsx}

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

All code, comments, variable names, and user-facing messages must be in English

Files:

  • components/calendar-grid.tsx
  • app/api/shifts/stats/route.ts
  • components/shift-stats.tsx
app/api/**/route.ts

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

app/api/**/route.ts: Implement password protection in API routes following two-tier model: GET endpoints check both passwordHash AND isLocked, mutation endpoints (POST/PUT/PATCH/DELETE) check only passwordHash
All API endpoints must handle requests and return NextResponse.json() with appropriate error status codes (400 for bad requests, 401 for authentication failures)

Files:

  • app/api/shifts/stats/route.ts
messages/{de,en,it}.json

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

Add all new translation keys to all three message files: messages/de.json, messages/en.json, and messages/it.json simultaneously

Files:

  • messages/en.json
  • messages/de.json
  • messages/it.json
messages/de.json

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

Use informal 'du' form (never formal 'Sie' form) in all German translations - examples: 'Möchtest du...' not 'Möchten Sie...', 'Bitte entsperre...' not 'Bitte entsperren Sie...'

Files:

  • messages/de.json
🧠 Learnings (9)
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to **/*.{ts,tsx} : Use `formatDateToLocal()` utility function for consistent date formatting in YYYY-MM-DD format

Applied to files:

  • components/calendar-grid.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to **/*.tsx : Use centralized common messages with parametrized `{item}` parameter for CRUD operations instead of creating message-specific keys - use `common.created`, `common.updated`, `common.deleted`, `common.createError`, `common.updateError`, `common.deleteError`

Applied to files:

  • components/calendar-grid.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to components/**/*.tsx : Use `showMobileCalendarDialog` to separate calendar selector logic for mobile UI

Applied to files:

  • components/calendar-grid.tsx
  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to app/**/*.tsx : Use `useTranslations()` hook from next-intl for all user-facing text in React components

Applied to files:

  • components/calendar-grid.tsx
  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to lib/**/*.ts : Use locale-specific date formatting: `de` for German dates, `it` for Italian dates, `enUS` for English dates in date formatting utilities

Applied to files:

  • components/calendar-grid.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to components/**/*.tsx : Implement calendar interactions with left-click to toggle shift with selected preset, right-click to open note dialog (prevent default context menu), and delete if exists/create if not logic

Applied to files:

  • components/calendar-grid.tsx
  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to app/page.tsx : Use `useState` for shifts, presets, notes, and calendars; `useEffect` for data fetching on calendar/date changes; `useRouter().replace()` for URL state sync; `statsRefreshTrigger` counter for mutation tracking

Applied to files:

  • app/api/shifts/stats/route.ts
  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to app/**/*.tsx : Listen to Server-Sent Events (SSE) for real-time updates on shift, preset, note, and sync-log changes, using silent refresh patterns with `fetchData(false)` and counter-based refresh triggers

Applied to files:

  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to messages/{de,en,it}.json : Add all new translation keys to all three message files: `messages/de.json`, `messages/en.json`, and `messages/it.json` simultaneously

Applied to files:

  • messages/en.json
  • messages/it.json
🧬 Code graph analysis (2)
app/api/shifts/stats/route.ts (2)
lib/db/schema.ts (1)
  • shifts (49-82)
lib/date-utils.ts (1)
  • calculateShiftDuration (18-56)
components/shift-stats.tsx (3)
components/ui/button.tsx (1)
  • Button (60-60)
components/ui/skeleton.tsx (1)
  • Skeleton (13-13)
lib/date-utils.ts (1)
  • formatDuration (63-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: build-dev
🔇 Additional comments (15)
messages/de.json (2)

27-36: LGTM! Weekday translations centralized correctly.

The weekday abbreviations are properly moved to common.weekday namespace with correct German abbreviations (Mo, Di, Mi, Do, Fr, Sa, So), aligning with the other locale files.


187-199: Stats translations look good.

The new statistics keys are properly translated to German with appropriate informal tone. The ⌀ symbol usage is consistent across locales.

messages/it.json (2)

27-36: LGTM! Italian weekday translations correctly centralized.

The weekday abbreviations are properly placed in common.weekday namespace with correct Italian abbreviations.


186-198: Stats translations are complete and consistent.

All new statistics keys are present and properly translated to Italian, maintaining consistency with the other locale files.

components/calendar-grid.tsx (1)

109-117: LGTM! Translation keys correctly updated to centralized namespace.

The weekday translation keys are properly migrated from calendar_grid.* to common.weekday.*, aligning with the i18n restructuring in this PR.

messages/en.json (2)

27-36: LGTM! Weekday translations properly centralized.

The English weekday abbreviations are correctly placed in the common.weekday namespace, providing a single source of truth for weekday labels across the application.


187-199: Stats translations complete with all required keys.

All new statistics view keys are present and properly structured to support the enhanced statistics UI with multiple chart views.

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

86-88: LGTM! Extended select fields for enhanced statistics.

Adding id and date fields enables proper shift tracking for trend analysis and min/max calculations.


116-121: Good initialization for tracking daily stats and duration bounds.

The data structures are properly initialized for accumulating daily statistics and tracking min/max durations.


182-220: LGTM! Comprehensive statistics calculation and response.

The metrics calculations are well-structured:

  • avgMinutesPerShift is rounded to whole numbers
  • avgShiftsPerDay preserves one decimal place for accuracy
  • minDuration correctly defaults to 0 when no valid durations exist
  • trendData is sorted chronologically for visualization

The response structure provides all necessary data for the enhanced charts UI.

package.json (1)

47-47: Recharts 3.6.0 requires special handling for React 19 compatibility.

Recharts with React 19 requires overriding the react-is dependency, and the react-is version must match the version of React 19 being used. Add this to package.json overrides:

"overrides": {
  "react-is": "^19.2.1"
}

Alternatively, if using yarn, use resolutions. Recharts currently works with React 17, 18 and 19, but the react-is override is necessary for proper React 19 support.

components/shift-stats.tsx (4)

6-33: LGTM on imports.

The imports are well-organized, separating lucide-react icons from Recharts components. The aliasing of Radar to RadarIcon from lucide-react avoids conflict with Recharts' Radar component.


59-72: LGTM on color constants.

The CHART_COLORS array follows the coding guidelines with hex colors in #3b82f6 format, providing a good variety for chart elements.


142-174: LGTM on chart data preparation.

The data transformations are well-structured. The truncation of long names with ... suffix provides good UX for chart labels. Sorting and limiting radar data to top 6 types keeps the chart readable.


41-48: Several fetched metrics are not displayed in the UI.

The interface defines avgShiftsPerDay, avgMinutesPerDay, daysWithShifts, and trendData, which are fetched from the API (as shown by the debug log) but not rendered anywhere.

Is this intentional for future use, or should these metrics be displayed? Consider either:

  • Adding UI elements to show these metrics
  • Removing unused fields from the interface if not planned
  • Adding a TODO comment if this is planned for a follow-up

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 pull request adds advanced shift statistics with interactive charts and visualization capabilities to BetterShift. It introduces the Recharts library to enable rich data visualization (pie charts, bar charts, and radar charts) and enhances the stats API to provide comprehensive metrics including daily breakdown, trend data, min/max durations, and averaged metrics per shift and per day. The PR also improves i18n consistency by centralizing weekday translations from the calendar_grid namespace to the common namespace, making them reusable across components.

Key Changes:

  • Adds Recharts library (v3.6.0) with all dependencies for data visualization
  • Enhances stats API with 7 new metrics: totalShifts, avgMinutesPerShift, avgShiftsPerDay, avgMinutesPerDay, minDuration, maxDuration, daysWithShifts, and trendData array
  • Introduces 4 view modes in shift statistics component: overview, pie chart, bar chart, and radar chart for different data perspectives

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Adds recharts dependency (v3.6.0) to enable chart visualization features
package-lock.json Updates lockfile with recharts and all transitive dependencies including d3 libraries, Redux toolkit, and react-redux
messages/de.json Moves weekday translations to common namespace and adds 12 new statistics translation keys for German
messages/en.json Moves weekday translations to common namespace and adds 12 new statistics translation keys for English
messages/it.json Moves weekday translations to common namespace and adds 12 new statistics translation keys for Italian
components/shift-stats.tsx Major refactor: adds 4 visualization modes (overview/pie/bar/radar), integrates Recharts components with custom tooltips, and displays enhanced statistics with summary cards
components/calendar-grid.tsx Updates weekday translation keys from calendar_grid namespace to common.weekday namespace
app/api/shifts/stats/route.ts Enhances stats calculation with daily breakdown tracking, min/max duration computation, and trend data generation for visualization

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 (3)
app/api/shifts/stats/route.ts (1)

141-166: Past issue resolved: daily breakdown now uses correct date field.

The code now correctly uses shift.date as the sole date source for daily breakdown, addressing the previous concern about incorrectly using startTime/endTime (which are HH:MM time strings). The date parsing logic properly handles Date objects, Unix timestamps in seconds, and string values.

Optional: Consider adding error logging for invalid dates.

The empty catch block at line 163 silently skips invalid dates. While this is acceptable for the current use case, adding a console.warn or conditional debug logging could help diagnose data quality issues in production.

Apply this diff if you want to add debug logging:

        } catch {
          // Skip invalid dates
+         if (process.env.NODE_ENV === 'development') {
+           console.warn('Invalid date encountered for shift:', shift.id, shiftDate);
+         }
        }
components/shift-stats.tsx (2)

17-39: Excellent SSR-safe implementation!

This hook properly addresses the previous concern about direct window.innerWidth access causing SSR hydration issues. The safe default value (120) ensures server-side rendering works correctly, and the resize listener enables responsive behavior on the client.


163-196: LGTM! Safe data preparation with proper division guards.

The chart data preparation correctly handles division by zero at lines 192-194 with the guard data.count ? ... : 0, addressing the previous concern. All three data pipelines (pie, bar, radar) properly transform the stats into chart-friendly formats with appropriate name truncation for UI display.

🧹 Nitpick comments (1)
components/shift-stats.tsx (1)

453-513: Pie chart implementation is functional with legend fallback.

The pie chart correctly uses the SSR-safe outerRadius hook and provides a legend grid below for accessibility. While SVG text labels (lines 468-469) may have limited screen reader support, the legend grid provides a text-based alternative.

Optional: Consider ARIA enhancements for improved accessibility.

The legend grid partially addresses screen reader accessibility, but adding explicit ARIA attributes to the chart container could further improve the experience for assistive technology users.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed21a10 and ed92368.

📒 Files selected for processing (2)
  • app/api/shifts/stats/route.ts (3 hunks)
  • components/shift-stats.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
app/api/**/route.ts

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

app/api/**/route.ts: Implement password protection in API routes following two-tier model: GET endpoints check both passwordHash AND isLocked, mutation endpoints (POST/PUT/PATCH/DELETE) check only passwordHash
All API endpoints must handle requests and return NextResponse.json() with appropriate error status codes (400 for bad requests, 401 for authentication failures)

Files:

  • app/api/shifts/stats/route.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use formatDateToLocal() utility function for consistent date formatting in YYYY-MM-DD format
Centralize validation messages using validation.* namespace keys (e.g., validation.passwordMatch, validation.fileTooLarge) instead of creating message-specific keys
When checking calendar lock status, use isLocked field to determine if read access requires password; when checking write protection, check only passwordHash field
Hash passwords using SHA-256 via utilities in lib/password-utils.ts
Only add comments for complex logic or non-obvious behavior - write self-documenting code with clear variable and function names

Files:

  • app/api/shifts/stats/route.ts
  • components/shift-stats.tsx
**/*.{ts,tsx,js,jsx}

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

All code, comments, variable names, and user-facing messages must be in English

Files:

  • app/api/shifts/stats/route.ts
  • components/shift-stats.tsx
components/**/*.tsx

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

components/**/*.tsx: Store hex colors in format #3b82f6 and apply 20% opacity for backgrounds using ${color}20 pattern
All dialogs must follow the unified design pattern with gradient backgrounds, consistent padding (p-6, px-6, pb-6), border-border/50 styling, backdrop blur, and gradient text for titles
Centralize form field labels and placeholders using form.* namespace keys (e.g., form.nameLabel, form.namePlaceholder) instead of duplicating across components
Control dialog state via props (open, onOpenChange), reset local state when open changes to false, and use onSubmit and optional onDelete callbacks
Implement calendar interactions with left-click to toggle shift with selected preset, right-click to open note dialog (prevent default context menu), and delete if exists/create if not logic
Use PRESET_COLORS array for color selection in components
Use showMobileCalendarDialog to separate calendar selector logic for mobile UI

Files:

  • components/shift-stats.tsx
**/*.tsx

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

Use centralized common messages with parametrized {item} parameter for CRUD operations instead of creating message-specific keys - use common.created, common.updated, common.deleted, common.createError, common.updateError, common.deleteError

Files:

  • components/shift-stats.tsx
🧠 Learnings (6)
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to app/page.tsx : Use `useState` for shifts, presets, notes, and calendars; `useEffect` for data fetching on calendar/date changes; `useRouter().replace()` for URL state sync; `statsRefreshTrigger` counter for mutation tracking

Applied to files:

  • app/api/shifts/stats/route.ts
  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to **/*.{ts,tsx} : Use `formatDateToLocal()` utility function for consistent date formatting in YYYY-MM-DD format

Applied to files:

  • app/api/shifts/stats/route.ts
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to app/**/*.tsx : Listen to Server-Sent Events (SSE) for real-time updates on shift, preset, note, and sync-log changes, using silent refresh patterns with `fetchData(false)` and counter-based refresh triggers

Applied to files:

  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to app/**/*.tsx : Use `useTranslations()` hook from next-intl for all user-facing text in React components

Applied to files:

  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to components/**/*.tsx : Implement calendar interactions with left-click to toggle shift with selected preset, right-click to open note dialog (prevent default context menu), and delete if exists/create if not logic

Applied to files:

  • components/shift-stats.tsx
📚 Learning: 2025-12-15T00:16:49.617Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T00:16:49.617Z
Learning: Applies to components/**/*.tsx : Use `showMobileCalendarDialog` to separate calendar selector logic for mobile UI

Applied to files:

  • components/shift-stats.tsx
🧬 Code graph analysis (1)
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 (7)
app/api/shifts/stats/route.ts (2)

181-196: LGTM! Safe division operations and proper averaging logic.

All division operations are properly guarded against zero divisors. The calculations for per-shift and per-day averages correctly use daysWithShifts for accurate daily metrics rather than calendar days in the period.


206-220: LGTM! Comprehensive response with proper guards.

The response correctly includes all new metrics with appropriate rounding. The minDuration === Infinity ? 0 : minDuration guard at line 216 prevents invalid values when no timed shifts exist.

components/shift-stats.tsx (5)

41-97: LGTM! Well-structured types and color palette.

The extended ShiftStats interface correctly matches the API response, and the CHART_COLORS array follows the coding guideline format (#3b82f6) with sufficient variety for chart visualizations.


198-228: LGTM! Well-typed and styled tooltip component.

The CustomTooltip component provides clear type definitions and consistent styling with the app's design system. The payload type is appropriately specific for the current chart data structures.


301-341: LGTM! Responsive view mode selector with proper conditional rendering.

The view mode selector correctly renders only when data exists and provides responsive layout for mobile and desktop viewports.


351-451: LGTM! Comprehensive overview mode with polished UI.

The overview mode provides clear metric cards and a detailed per-type breakdown with visual progress indicators. The conditional rendering of min/max durations and responsive design are well-implemented.


515-624: LGTM! Well-implemented bar and radar visualizations.

Both chart modes provide clear data comparisons with appropriate responsive containers, tooltips, and legends. The radar chart's focus on top 6 shift types with dual metrics (total hours and average per shift) provides valuable performance insights.

@panteLx panteLx merged commit 0775259 into main Dec 16, 2025
3 checks passed
@panteLx panteLx deleted the feat/enhanced-statistics branch December 16, 2025 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: More enhanced statistics

1 participant