diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d15c110 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,67 @@ +# Changelog + +All notable changes to TimeTracker Pro will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Native HTML5 time picker component (`TimePicker`) following web standards and a11y best practices + - Uses `` for familiar, intuitive UX + - **15-minute intervals**: Time selection restricted to :00, :15, :30, and :45 using HTML5 `step` attribute + - Automatic native time pickers on mobile devices (iOS/Android) + - Keyboard-accessible time inputs on desktop + - Full ARIA label support for screen readers + - Styled with shadcn/ui design tokens for consistency + +### Changed +- **Improved time selection UX**: Replaced custom scroll-wheel time picker with native HTML5 time inputs + - Follows standard web conventions for familiar, intuitive user experience + - Better desktop experience with keyboard-accessible inputs + - Mobile browsers provide native time pickers automatically + - Full accessibility (a11y) support with proper ARIA labels and keyboard navigation + - Consistent with existing date input pattern in the application + - Eliminates custom scroll logic in favor of browser-native functionality + - **Start Day Dialog**: 1 time picker for day start time + - **Task Edit Dialog**: 2 time pickers for task start/end times + - **Archive Edit Dialog**: 4 time pickers (2 for day start/end, 2 for task start/end) +- Removed duplicate `generateTimeOptions()` helper functions from all dialog components + +### Fixed +- Resolved merge conflicts in `src/index.css` + +## [0.21.1] - 2026-02-06 + +### Initial Release Features +- Daily time tracking with start/stop functionality +- Task management with real-time duration tracking +- Rich text support with GitHub Flavored Markdown +- Projects & clients organization with hourly rates +- Custom categories with color coding +- Archive system for completed work days +- Revenue tracking and automatic calculations +- Invoice generation and export (CSV, JSON) +- CSV import for existing time data +- Progressive Web App with offline support +- Cross-platform compatibility (Windows, Mac, Linux, iOS, Android) +- Dual storage mode (guest/local or authenticated/cloud sync) +- Print-friendly archive views +- Mobile-optimized interface with touch navigation +- Dark mode support +- Authentication via Supabase (optional) +- Real-time sync across devices (when authenticated) + +--- + +## Version History + +### Versioning Guidelines +- **Major** (X.0.0): Breaking changes, major feature overhauls +- **Minor** (0.X.0): New features, significant improvements, non-breaking changes +- **Patch** (0.0.X): Bug fixes, minor improvements, documentation updates + +### Links +- [Unreleased Changes](https://github.com/AdamJ/TimeTrackerPro/compare/v0.21.1...HEAD) +- [Version 0.21.1](https://github.com/AdamJ/TimeTrackerPro/releases/tag/v0.21.1) diff --git a/CLAUDE.md b/CLAUDE.md index 58096b2..10e9dde 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # CLAUDE.md - AI Assistant Codebase Guide -**Last Updated:** 2026-02-02 -**Version:** 1.0.2 +**Last Updated:** 2026-02-06 +**Version:** 1.0.4 This document provides comprehensive guidance for AI assistants working with the TimeTracker Pro codebase. It covers architecture, conventions, workflows, and best practices. @@ -196,6 +196,7 @@ TimeTrackerPro/ ├── src/ │ ├── components/ # React components │ │ ├── ui/ # shadcn/ui base components (49 files) +│ │ │ └── scroll-time-picker.tsx # Custom scroll-wheel time picker │ │ ├── ArchiveEditDialog.tsx # Archive entry editing │ │ ├── ArchiveItem.tsx # Archive display component │ │ ├── AuthDialog.tsx # Authentication modal @@ -627,6 +628,81 @@ const MyPage = lazy(() => import("./pages/MyPage")); } /> ``` +### Using the TimePicker Component + +The `TimePicker` is a native HTML5 time input wrapped with shadcn/ui styling for consistent appearance and accessibility. + +**Component File**: `src/components/ui/scroll-time-picker.tsx` + +**Props Interface**: +```typescript +interface TimePickerProps { + value: string; // "HH:MM" 24-hour format (e.g., "14:30") + onValueChange: (value: string) => void; + disabled?: boolean; + className?: string; + id?: string; + "aria-label"?: string; + "aria-describedby"?: string; +} +``` + +**Usage Example**: +```typescript +import { TimePicker } from "@/components/ui/scroll-time-picker"; +import { Label } from "@/components/ui/label"; +import { useState } from "react"; + +const MyComponent = () => { + const [time, setTime] = useState("09:00"); + + return ( +
+ + +
+ ); +}; +``` + +**Features**: +- Native HTML5 `` for standard web UX +- **15-minute intervals**: Time selection restricted to :00, :15, :30, :45 using `step={900}` +- Mobile browsers display native time pickers automatically +- Desktop browsers provide keyboard-accessible spinners or typed input +- Full accessibility with ARIA labels, keyboard navigation, and screen reader support +- Styled with shadcn/ui design tokens (matches Input component) +- Supports dark mode via CSS variables +- Compatible with all modern browsers + +**Accessibility (A11y)**: +- Proper label association via `htmlFor` and `id` +- ARIA labels for screen readers +- Keyboard navigable (Tab, Arrow keys, Enter) +- Focus visible indicators +- Works with browser's native date/time accessibility features + +**Used In**: +- `StartDayDialog.tsx` - Day start time selection (1 picker) +- `TaskEditDialog.tsx` - Task start/end time selection (2 pickers) +- `ArchiveEditDialog.tsx` - Day and task time editing (4 pickers total) + +**Design Decision**: Native HTML5 inputs were chosen over custom implementations because: +1. Standard web pattern users already understand +2. Automatic mobile optimization (native pickers on iOS/Android) +3. Built-in accessibility features +4. Consistent with the app's date input approach +5. No custom scroll/wheel logic to maintain +6. Better keyboard navigation +7. Follows shadcn/ui philosophy of enhancing web standards + +**Migration Note**: This component replaced a custom scroll-wheel picker and earlier dropdown approach, providing better UX through browser-native functionality. + ### Adding a New Context Method ```typescript @@ -951,6 +1027,7 @@ Before making changes, verify: ### Documentation - **Main README**: `README.md` - User-facing documentation +- **Changelog**: `CHANGELOG.md` - Version history and changes - **CLAUDE.md**: `CLAUDE.md` - This file - AI assistant guide - **Agent Guidelines**: `AGENTS.md` - Quick agent instructions - **Archive System**: `docs/ARCHIVING_DAYS.md` - Archive system guide @@ -986,6 +1063,8 @@ Before making changes, verify: | Version | Date | Changes | |---------|------|---------| +| 1.0.4 | 2026-02-06 | Replaced custom scroll picker with native HTML5 time inputs for better UX and a11y | +| 1.0.3 | 2026-02-06 | Added ScrollTimePicker component, replaced time dropdowns with scroll-wheel UI | | 1.0.2 | 2026-02-02 | Added auto-open New Task form feature when day starts | | 1.0.1 | 2025-11-21 | Updated component list, documentation references, and current state | | 1.0.0 | 2025-11-18 | Initial CLAUDE.md creation | diff --git a/README.md b/README.md index 3183d7a..c4f6529 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ A modern, feature-rich Progressive Web App (PWA) for time tracking built with Re **Additional:** +- [Changelog](#-changelog) - [License](#-license) - [Credits](#-credits) @@ -60,6 +61,7 @@ TimeTracker Pro is a professional time tracking application that helps you monit - **Daily Time Tracking** - Start/stop your workday with clear boundaries - **Task Management** - Create, edit, and delete tasks with real-time duration tracking +- **Intuitive Time Selection** - Native browser time inputs for familiar, accessible time entry - **Rich Text Support** - Add detailed notes with GitHub Flavored Markdown (tables, lists, formatting) - **Automatic Calculations** - Duration and revenue calculated automatically - **Archive System** - Permanent record of all completed work days @@ -1059,6 +1061,18 @@ const MyPage = lazy(() => import("./pages/MyPage")); --- +## 📋 Changelog + +For a detailed list of changes, new features, and bug fixes, see [CHANGELOG.md](CHANGELOG.md). + +**Recent Updates:** +- Native HTML5 time inputs for intuitive, accessible time selection +- Consistent UX with date inputs across all dialogs +- Mobile-optimized with browser-native time pickers +- Full keyboard navigation and screen reader support + +--- + ## 📱 iOS Screenshots | View | Image | diff --git a/dev-dist/sw.js b/dev-dist/sw.js index 7be56bf..e86b285 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -79,7 +79,7 @@ define(['./workbox-21a80088'], (function (workbox) { 'use strict'; */ workbox.precacheAndRoute([{ "url": "index.html", - "revision": "0.0lr8hurrrmo" + "revision": "0.vtj3k2q5obg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/docs/plans/2026-02-06-scroll-time-picker-design.md b/docs/plans/2026-02-06-scroll-time-picker-design.md new file mode 100644 index 0000000..8b49157 --- /dev/null +++ b/docs/plans/2026-02-06-scroll-time-picker-design.md @@ -0,0 +1,45 @@ +# Scroll Time Picker Design + +**Date:** 2026-02-06 +**Status:** Approved + +## Problem + +Time selection uses Select dropdowns with 96 options (15-minute intervals across 24 hours). Users must scroll through a long list to find their desired time. Poor UX especially for times later in the day. + +## Solution + +Replace Select dropdowns with a custom scroll-wheel time picker (iOS drum-picker style). Three columns: Hour (1-12), Minute (00/15/30/45), Period (AM/PM). CSS scroll-snap locks selection. Produces the same `"HH:MM"` 24-hour string format. + +## Component + +**File:** `src/components/ui/scroll-time-picker.tsx` + +**Props:** + +- `value: string` — "HH:MM" 24-hour format +- `onValueChange: (value: string) => void` +- `disabled?: boolean` +- `className?: string` + +**Wheels:** + +- Hour: 1–12 (scroll-snap) +- Minute: 00, 15, 30, 45 (scroll-snap) +- Period: AM, PM (scroll-snap) + +**Styling:** shadcn/ui design tokens, dark mode via theme variables. + +## Integration Points + +1. `StartDayDialog.tsx` — 1 picker (start time) +2. `TaskEditDialog.tsx` — 2 pickers (start + end) +3. `ArchiveEditDialog.tsx` — 2 pickers (day start/end) + 2 per task (task start/end) + +Drop-in replacement: same value/onValueChange interface as current Select. Remove duplicated `generateTimeOptions()` from all files. + +## No Changes To + +- Data storage format (Date objects in memory, HH:MM strings in UI) +- `parseTimeInput()`, `formatTimeForInput()`, `formatTime12Hour()` utilities +- Task interface or data service layer diff --git a/docs/plans/2026-02-06-scroll-time-picker-plan.md b/docs/plans/2026-02-06-scroll-time-picker-plan.md new file mode 100644 index 0000000..354623a --- /dev/null +++ b/docs/plans/2026-02-06-scroll-time-picker-plan.md @@ -0,0 +1,467 @@ +# Scroll Time Picker Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Replace all time-selection Select dropdowns with a scroll-wheel time picker (hour/minute/period columns) for better UX. + +**Architecture:** A single reusable `ScrollTimePicker` component in `src/components/ui/scroll-time-picker.tsx` with the same `value`/`onValueChange` interface as the current Select dropdowns. Three CSS scroll-snap columns (Hour 1-12, Minute 00/15/30/45, Period AM/PM). Drop-in replacement across 3 files (4 dialog components). + +**Tech Stack:** React, TypeScript, Tailwind CSS, shadcn/ui design tokens + +--- + +## Task 1: Create the ScrollTimePicker component + +**Files:** + +- Create: `src/components/ui/scroll-time-picker.tsx` + +**Step 1: Create the component file** + +Create `src/components/ui/scroll-time-picker.tsx` with the following implementation: + +```tsx +import React, { useRef, useEffect, useCallback } from 'react'; +import { cn } from '@/lib/util'; + +interface ScrollTimePickerProps { + value: string; // "HH:MM" 24-hour format + onValueChange: (value: string) => void; + disabled?: boolean; + className?: string; +} + +const HOURS = Array.from({ length: 12 }, (_, i) => i + 1); // 1-12 +const MINUTES = [0, 15, 30, 45]; +const PERIODS = ['AM', 'PM'] as const; + +const ITEM_HEIGHT = 40; // px per item +const VISIBLE_ITEMS = 5; // show 5 items, center is selected + +function parse24Hour(value: string): { + hour12: number; + minute: number; + period: 'AM' | 'PM'; +} { + const [h, m] = value.split(':').map(Number); + const period = h >= 12 ? 'PM' : 'AM'; + let hour12 = h % 12; + hour12 = hour12 === 0 ? 12 : hour12; + // Round minute to nearest 15 + const minute = Math.round(m / 15) * 15 === 60 ? 0 : Math.round(m / 15) * 15; + return { hour12, minute, period }; +} + +function to24Hour(hour12: number, minute: number, period: 'AM' | 'PM'): string { + let h = hour12; + if (period === 'AM' && h === 12) h = 0; + if (period === 'PM' && h !== 12) h += 12; + return `${h.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; +} + +interface WheelColumnProps { + items: (string | number)[]; + selectedIndex: number; + onSelect: (index: number) => void; + disabled?: boolean; + formatItem?: (item: string | number) => string; +} + +const WheelColumn: React.FC = ({ + items, + selectedIndex, + onSelect, + disabled, + formatItem = String +}) => { + const containerRef = useRef(null); + const isScrollingRef = useRef(false); + const scrollTimeoutRef = useRef>(); + + // Scroll to selected index on mount and when selectedIndex changes externally + useEffect(() => { + if (containerRef.current && !isScrollingRef.current) { + const scrollTop = selectedIndex * ITEM_HEIGHT; + containerRef.current.scrollTo({ top: scrollTop, behavior: 'smooth' }); + } + }, [selectedIndex]); + + const handleScroll = useCallback(() => { + if (!containerRef.current) return; + isScrollingRef.current = true; + + if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current); + scrollTimeoutRef.current = setTimeout(() => { + if (!containerRef.current) return; + const scrollTop = containerRef.current.scrollTop; + const index = Math.round(scrollTop / ITEM_HEIGHT); + const clampedIndex = Math.max(0, Math.min(items.length - 1, index)); + + // Snap to position + containerRef.current.scrollTo({ + top: clampedIndex * ITEM_HEIGHT, + behavior: 'smooth' + }); + + isScrollingRef.current = false; + if (clampedIndex !== selectedIndex) { + onSelect(clampedIndex); + } + }, 100); + }, [items.length, selectedIndex, onSelect]); + + const handleItemClick = (index: number) => { + if (disabled) return; + if (containerRef.current) { + containerRef.current.scrollTo({ + top: index * ITEM_HEIGHT, + behavior: 'smooth' + }); + } + onSelect(index); + }; + + const paddingHeight = Math.floor(VISIBLE_ITEMS / 2) * ITEM_HEIGHT; + + return ( +
+ {/* Selection highlight band */} +
+ {/* Fade overlays */} +
+
+
+ {/* Top padding */} +
+ {items.map((item, index) => ( +
handleItemClick(index)} + > + {formatItem(item)} +
+ ))} + {/* Bottom padding */} +
+
+
+ ); +}; + +export const ScrollTimePicker: React.FC = ({ + value, + onValueChange, + disabled = false, + className +}) => { + const { hour12, minute, period } = parse24Hour(value || '09:00'); + + const hourIndex = HOURS.indexOf(hour12); + const minuteIndex = MINUTES.indexOf(minute); + const periodIndex = PERIODS.indexOf(period); + + const handleHourChange = (index: number) => { + onValueChange(to24Hour(HOURS[index], minute, period)); + }; + + const handleMinuteChange = (index: number) => { + onValueChange(to24Hour(hour12, MINUTES[index], period)); + }; + + const handlePeriodChange = (index: number) => { + onValueChange(to24Hour(hour12, minute, PERIODS[index])); + }; + + return ( +
+ = 0 ? hourIndex : 0} + onSelect={handleHourChange} + disabled={disabled} + /> +
:
+ = 0 ? minuteIndex : 0} + onSelect={handleMinuteChange} + disabled={disabled} + formatItem={item => String(item).padStart(2, '0')} + /> + = 0 ? periodIndex : 0} + onSelect={handlePeriodChange} + disabled={disabled} + /> +
+ ); +}; +``` + +**Step 2: Add scrollbar-hide utility** + +Check `src/index.css` or Tailwind config for a `.scrollbar-hide` utility. If it doesn't exist, add to `src/index.css`: + +```css +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} +.scrollbar-hide::-webkit-scrollbar { + display: none; +} +``` + +**Step 3: Verify the component builds** + +Run: `npm run build` +Expected: No TypeScript or build errors + +**Step 4: Commit** + +```bash +git add src/components/ui/scroll-time-picker.tsx src/index.css +git commit -m "feat: add ScrollTimePicker component with scroll-wheel UI" +``` + +--- + +## Task 2: Integrate into StartDayDialog + +**Files:** + +- Modify: `src/components/StartDayDialog.tsx` + +**Step 1: Replace Select with ScrollTimePicker** + +In `src/components/StartDayDialog.tsx`: + +1. Remove imports: `Select`, `SelectContent`, `SelectItem`, `SelectTrigger`, `SelectValue` +2. Add import: `import { ScrollTimePicker } from "@/components/ui/scroll-time-picker";` +3. Remove: `formatTime12Hour` function (lines 42-50) +4. Remove: `TimeOption` type and `generateTimeOptions` function (lines 52-65) +5. Remove: `const timeOptions = generateTimeOptions();` (line 75) +6. Replace the Select block (lines 122-133) with: + +```tsx + +``` + +**Step 2: Verify build** + +Run: `npm run build` +Expected: PASS + +**Step 3: Commit** + +```bash +git add src/components/StartDayDialog.tsx +git commit -m "feat: use ScrollTimePicker in StartDayDialog" +``` + +--- + +## Task 3: Integrate into TaskEditDialog + +**Files:** + +- Modify: `src/components/TaskEditDialog.tsx` + +**Step 1: Replace Select time pickers with ScrollTimePicker** + +In `src/components/TaskEditDialog.tsx`: + +1. Remove Select-related imports (lines 12-18) — only if Select is no longer used (category and project still use Select, so keep them) +2. Add import: `import { ScrollTimePicker } from "@/components/ui/scroll-time-picker";` +3. Remove: `TimeOption` type, `formatTime12Hour` function, and `generateTimeOptions` function (lines 128-152) +4. Remove: `const timeOptions: TimeOption[] = generateTimeOptions();` (line 154) +5. Replace the Start Time Select block (lines 419-435) with: + +```tsx + setTimeData(prev => ({ ...prev, startTime: value }))} +/> +``` + +6. Replace the End Time Select block (lines 442-463) with: + +```tsx + setTimeData(prev => ({ ...prev, endTime: value }))} + disabled={!task.endTime} +/> +``` + +7. Keep the Label for End Time as-is (with the "Currently Active" text) + +**Step 2: Verify build** + +Run: `npm run build` +Expected: PASS + +**Step 3: Commit** + +```bash +git add src/components/TaskEditDialog.tsx +git commit -m "feat: use ScrollTimePicker in TaskEditDialog" +``` + +--- + +## Task 4: Integrate into ArchiveEditDialog + +**Files:** + +- Modify: `src/components/ArchiveEditDialog.tsx` + +**Step 1: Replace all Select time pickers with ScrollTimePicker** + +In `src/components/ArchiveEditDialog.tsx`: + +1. Remove Select-related imports (lines 16-22) — category and project Selects in `TaskEditInArchiveDialog` still need them, so keep the imports +2. Add import: `import { ScrollTimePicker } from "@/components/ui/scroll-time-picker";` +3. Remove: `TimeOption` type and `generateTimeOptions` function (lines 88-103) +4. Remove: `const timeOptions = generateTimeOptions();` at line 129 +5. Remove: `const timeOptions = generateTimeOptions();` at line 657 + +**In ArchiveEditDialog (day start/end):** + +6. Replace day Start Time Select (lines 361-377) with: + +```tsx + setDayData(prev => ({ ...prev, startTime: value }))} +/> +``` + +7. Replace day End Time Select (lines 383-397) with: + +```tsx + setDayData(prev => ({ ...prev, endTime: value }))} +/> +``` + +**In TaskEditInArchiveDialog (task start/end):** + +8. Replace task Start Time Select (lines 841-857) with: + +```tsx + setTimeData(prev => ({ ...prev, startTime: value }))} +/> +``` + +9. Replace task End Time Select (lines 862-878) with: + +```tsx + setTimeData(prev => ({ ...prev, endTime: value }))} +/> +``` + +**Step 2: Verify build** + +Run: `npm run build` +Expected: PASS + +**Step 3: Commit** + +```bash +git add src/components/ArchiveEditDialog.tsx +git commit -m "feat: use ScrollTimePicker in ArchiveEditDialog" +``` + +--- + +## Task 5: Final verification and cleanup + +**Step 1: Run full lint** + +Run: `npm run lint` +Expected: PASS (no new errors) + +**Step 2: Run full build** + +Run: `npm run build` +Expected: PASS + +**Step 3: Verify no remaining generateTimeOptions references** + +Search codebase for `generateTimeOptions` — should return 0 results. + +**Step 4: Manual testing checklist** + +- [ ] StartDayDialog: scroll wheel appears, can select time, starts day correctly +- [ ] TaskEditDialog: both start/end pickers work, end time disabled when task active +- [ ] ArchiveEditDialog: day start/end pickers work +- [ ] ArchiveEditDialog TaskEdit: task start/end pickers work +- [ ] Scroll-snap locks to items properly +- [ ] Mouse wheel scrolling works +- [ ] Click-to-select works +- [ ] Dark mode renders correctly +- [ ] Mobile viewport works (responsive) + +**Step 5: Update CHANGELOG.md** + +Add under `[Unreleased]` > `Changed`: + +```markdown +- Replaced time selection dropdowns with scroll-wheel time picker for better UX + — `src/components/ui/scroll-time-picker.tsx` (new), `StartDayDialog.tsx`, `TaskEditDialog.tsx`, `ArchiveEditDialog.tsx` +``` + +**Step 6: Final commit** + +```bash +git add CHANGELOG.md +git commit -m "chore: update CHANGELOG for scroll time picker" +``` diff --git a/package-lock.json b/package-lock.json index 863ff8c..a897da7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "@types/react-dom": "^18.3.0", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.20", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.9.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", @@ -4186,7 +4187,7 @@ "version": "1.13.5", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4228,7 +4229,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4245,7 +4245,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4262,7 +4261,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4279,7 +4277,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4296,7 +4293,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4313,7 +4309,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4330,7 +4325,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4347,7 +4341,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4364,7 +4357,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4381,7 +4373,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4395,14 +4386,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -5531,9 +5522,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", - "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 74103ad..a110e2f 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@types/react-dom": "^18.3.0", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.20", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.9.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", diff --git a/src/components/ArchiveEditDialog.tsx b/src/components/ArchiveEditDialog.tsx index c32f82b..b00d315 100644 --- a/src/components/ArchiveEditDialog.tsx +++ b/src/components/ArchiveEditDialog.tsx @@ -1,25 +1,26 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect } from 'react'; import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle -} from "@/components/ui/dialog"; -import { Callout } from "@/components/ui/callout"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { MarkdownDisplay } from "@/components/MarkdownDisplay"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Callout } from '@/components/ui/callout'; +import { InfoCircledIcon } from '@radix-ui/react-icons'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { MarkdownDisplay } from '@/components/MarkdownDisplay'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue -} from "@/components/ui/select"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { TimePicker } from '@/components/ui/scroll-time-picker'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Table, @@ -85,23 +86,6 @@ function formatTime12Hour(date: Date | undefined): string { return `${hours}:${minutes.toString().padStart(2, '0')} ${ampm}`; } -type TimeOption = { value: string; label: string }; -function generateTimeOptions(): TimeOption[] { - const options: TimeOption[] = []; - for (let hour = 0; hour < 24; hour++) { - for (let minute = 0; minute < 60; minute += 15) { - const value = `${hour.toString().padStart(2, '0')}:${minute - .toString() - .padStart(2, '0')}`; - const date = new Date(); - date.setHours(hour, minute, 0, 0); - const label = formatTime12Hour(date); - options.push({ value, label }); - } - } - return options; -} - export const ArchiveEditDialog: React.FC = ({ day, isOpen, @@ -126,7 +110,6 @@ export const ArchiveEditDialog: React.FC = ({ }); const [tasks, setTasks] = useState([]); - const timeOptions = generateTimeOptions(); // Initialize form data when dialog opens useEffect(() => { @@ -168,7 +151,7 @@ export const ArchiveEditDialog: React.FC = ({ const handleSaveDay = async () => { // Parse the new date from the input (same as StartDayDialog) - const [year, month, dayOfMonth] = dayData.date.split("-").map(Number); + const [year, month, dayOfMonth] = dayData.date.split('-').map(Number); const selectedDate = new Date(year, month - 1, dayOfMonth); // Create new start/end times with the selected date but original times @@ -216,8 +199,8 @@ export const ArchiveEditDialog: React.FC = ({ await updateArchivedDay(day.id, updatedDay); setIsEditing(false); } catch (error) { - console.error("Failed to save archived day:", error); - alert("Failed to save changes. Please try again."); + console.error('Failed to save archived day:', error); + alert('Failed to save changes. Please try again.'); } }; @@ -245,7 +228,7 @@ export const ArchiveEditDialog: React.FC = ({ }; const handleTaskSave = (updatedTask: Task) => { - const updatedTasks = tasks.map((t) => + const updatedTasks = tasks.map(t => t.id === updatedTask.id ? updatedTask : t ); setTasks(updatedTasks); @@ -253,7 +236,7 @@ export const ArchiveEditDialog: React.FC = ({ }; const handleTaskDelete = (taskId: string) => { - const updatedTasks = tasks.filter((t) => t.id !== taskId); + const updatedTasks = tasks.filter(t => t.id !== taskId); setTasks(updatedTasks); }; @@ -280,7 +263,6 @@ export const ArchiveEditDialog: React.FC = ({ {formatDate(day.startTime)}
-
@@ -349,85 +331,69 @@ export const ArchiveEditDialog: React.FC = ({ - setDayData((prev) => ({ ...prev, date: e.target.value })) + onChange={e => + setDayData(prev => ({ ...prev, date: e.target.value })) } className="w-full" />
- - + aria-label="Day start time" + />
- - + aria-label="Day end time" + />
-
- - - - Edit - Preview - - -