From bb8101fd7a994ea2899315df5827d7755345d0fb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Jan 2026 17:27:41 +0000 Subject: [PATCH 1/2] fix: Connect Settings Panel preferences to persistent store - Add onChange handlers for Weekly Hours Target input - Add onChange handler for Timesheet Reminders toggle - Connect inputs to useSettingsStore which persists to localStorage - Use weeklyHoursTarget from settings store in Team page calculations Closes #10 --- src/components/settings/SettingsPanel.tsx | 18 ++++++++++++++++-- src/components/team/TeamList.tsx | 11 ++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/settings/SettingsPanel.tsx b/src/components/settings/SettingsPanel.tsx index 99b241f..ea28f0b 100644 --- a/src/components/settings/SettingsPanel.tsx +++ b/src/components/settings/SettingsPanel.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'; import { Card } from '@/components/ui'; import { bcClient } from '@/services/bc/bcClient'; import { useAuth } from '@/services/auth'; +import { useSettingsStore } from '@/hooks/useSettingsStore'; import { BuildingOffice2Icon, EnvelopeIcon, @@ -26,6 +27,7 @@ interface CompanyInfo { export function SettingsPanel() { const { account } = useAuth(); + const { weeklyHoursTarget, notificationsEnabled, updateSettings } = useSettingsStore(); const [companyInfo, setCompanyInfo] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -136,7 +138,14 @@ export function SettingsPanel() { + updateSettings({ + weeklyHoursTarget: Math.max(0, parseInt(e.target.value) || 0), + }) + } className="w-20 rounded-lg border border-dark-600 bg-dark-800 px-3 py-2 text-dark-100 focus:outline-none focus:ring-2 focus:ring-thyme-500" /> @@ -146,7 +155,12 @@ export function SettingsPanel() {

Get reminded to fill in your timesheet

diff --git a/src/components/team/TeamList.tsx b/src/components/team/TeamList.tsx index 9c46c3a..0c5b244 100644 --- a/src/components/team/TeamList.tsx +++ b/src/components/team/TeamList.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'; import { TeamMemberCard } from './TeamMemberCard'; import { Card } from '@/components/ui'; import { bcClient } from '@/services/bc/bcClient'; +import { useSettingsStore } from '@/hooks/useSettingsStore'; import type { BCEmployee } from '@/types'; interface TeamMember { @@ -16,13 +17,12 @@ interface TeamMember { status: 'on-track' | 'behind' | 'ahead'; } -function mapEmployeeToMember(employee: BCEmployee): TeamMember { +function mapEmployeeToMember(employee: BCEmployee, hoursTarget: number): TeamMember { // TODO: Fetch actual hours from timeRegistrationEntries const hoursThisWeek = 0; - const hoursTarget = 40; let status: 'on-track' | 'behind' | 'ahead' = 'on-track'; - const progress = hoursThisWeek / hoursTarget; + const progress = hoursTarget > 0 ? hoursThisWeek / hoursTarget : 0; if (progress < 0.6) status = 'behind'; else if (progress > 1) status = 'ahead'; @@ -38,6 +38,7 @@ function mapEmployeeToMember(employee: BCEmployee): TeamMember { } export function TeamList() { + const { weeklyHoursTarget } = useSettingsStore(); const [isLoading, setIsLoading] = useState(true); const [members, setMembers] = useState([]); const [error, setError] = useState(null); @@ -46,7 +47,7 @@ export function TeamList() { async function fetchEmployees() { try { const employees = await bcClient.getEmployees("status eq 'Active'"); - const teamMembers = employees.map(mapEmployeeToMember); + const teamMembers = employees.map((emp) => mapEmployeeToMember(emp, weeklyHoursTarget)); setMembers(teamMembers); } catch (err) { console.error('Failed to fetch employees:', err); @@ -56,7 +57,7 @@ export function TeamList() { } } fetchEmployees(); - }, []); + }, [weeklyHoursTarget]); const totalHours = members.reduce((sum, m) => sum + m.hoursThisWeek, 0); const totalTarget = members.reduce((sum, m) => sum + m.hoursTarget, 0); From 9dac34ef9ab46602f4fb741b7acffde527aac7a4 Mon Sep 17 00:00:00 2001 From: Ben Weeks Date: Tue, 6 Jan 2026 13:47:16 +0000 Subject: [PATCH 2/2] fix: Address Copilot review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SettingsPanel: Enforce min/max (0-168) in onChange handler, not just HTML - SettingsPanel: Add aria-label attributes for accessibility - SettingsPanel: Use parseFloat to support decimal hours (e.g., 37.5) - TeamList: Use useMemo to derive members instead of refetching API - useSettingsStore: Change default weeklyHoursTarget to 37.5 (7.5 × 5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/components/settings/SettingsPanel.tsx | 13 +++++++++---- src/components/team/TeamList.tsx | 17 +++++++++++------ src/hooks/useSettingsStore.ts | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/settings/SettingsPanel.tsx b/src/components/settings/SettingsPanel.tsx index ea28f0b..6ab9e0c 100644 --- a/src/components/settings/SettingsPanel.tsx +++ b/src/components/settings/SettingsPanel.tsx @@ -137,15 +137,19 @@ export function SettingsPanel() {

Your target hours per week for tracking

+ onChange={(e) => { + const parsed = parseFloat(e.target.value); + const safeValue = isNaN(parsed) ? 0 : parsed; updateSettings({ - weeklyHoursTarget: Math.max(0, parseInt(e.target.value) || 0), - }) - } + weeklyHoursTarget: Math.min(168, Math.max(0, safeValue)), + }); + }} + aria-label="Weekly hours target" className="w-20 rounded-lg border border-dark-600 bg-dark-800 px-3 py-2 text-dark-100 focus:outline-none focus:ring-2 focus:ring-thyme-500" /> @@ -160,6 +164,7 @@ export function SettingsPanel() { checked={notificationsEnabled} onChange={(e) => updateSettings({ notificationsEnabled: e.target.checked })} className="peer sr-only" + aria-label="Timesheet reminders" />
diff --git a/src/components/team/TeamList.tsx b/src/components/team/TeamList.tsx index 0c5b244..a35856d 100644 --- a/src/components/team/TeamList.tsx +++ b/src/components/team/TeamList.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { TeamMemberCard } from './TeamMemberCard'; import { Card } from '@/components/ui'; import { bcClient } from '@/services/bc/bcClient'; @@ -40,15 +40,14 @@ function mapEmployeeToMember(employee: BCEmployee, hoursTarget: number): TeamMem export function TeamList() { const { weeklyHoursTarget } = useSettingsStore(); const [isLoading, setIsLoading] = useState(true); - const [members, setMembers] = useState([]); + const [employees, setEmployees] = useState([]); const [error, setError] = useState(null); useEffect(() => { async function fetchEmployees() { try { - const employees = await bcClient.getEmployees("status eq 'Active'"); - const teamMembers = employees.map((emp) => mapEmployeeToMember(emp, weeklyHoursTarget)); - setMembers(teamMembers); + const data = await bcClient.getEmployees("status eq 'Active'"); + setEmployees(data); } catch (err) { console.error('Failed to fetch employees:', err); setError('Failed to load team members'); @@ -57,7 +56,13 @@ export function TeamList() { } } fetchEmployees(); - }, [weeklyHoursTarget]); + }, []); + + // Derive members from employees when weeklyHoursTarget changes (no API refetch) + const members = useMemo( + () => employees.map((emp) => mapEmployeeToMember(emp, weeklyHoursTarget)), + [employees, weeklyHoursTarget] + ); const totalHours = members.reduce((sum, m) => sum + m.hoursThisWeek, 0); const totalTarget = members.reduce((sum, m) => sum + m.hoursTarget, 0); diff --git a/src/hooks/useSettingsStore.ts b/src/hooks/useSettingsStore.ts index 93f93b9..abdf6cf 100644 --- a/src/hooks/useSettingsStore.ts +++ b/src/hooks/useSettingsStore.ts @@ -10,7 +10,7 @@ interface SettingsStore extends UserSettings { const defaultSettings: UserSettings = { defaultProjectId: undefined, defaultTaskId: undefined, - weeklyHoursTarget: 40, + weeklyHoursTarget: 37.5, notificationsEnabled: true, theme: 'system', };