diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa1651..12da318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Users can now add tasks directly to an existing archived day from the ArchiveEditDialog — a toggleable "Add Task" button in the task table header opens an inline form (title, description, category, project, start/end time) that appends the task and recalculates the day's total duration on save. Context method `addTaskToArchivedDay` handles the optimistic update and persistence + — `src/components/ArchiveEditDialog.tsx`, `src/contexts/TimeTrackingContext.tsx` - Scaffolded a Tauri iOS build target alongside the existing Mac/Windows desktop app, so the app can now be run in the iOS Simulator (`pnpm tauri:ios:init`, `pnpm tauri:ios:dev`) and eventually shipped to the App Store. Split `src-tauri/src/main.rs` into a shared `lib.rs` (`#[cfg_attr(mobile, tauri::mobile_entry_point)]`) with a thin binary wrapper, and added a `[lib]` crate target (`crate-type = ["staticlib", "cdylib", "rlib"]`) required for mobile builds. The native menu, self-hosted updater, and updater-triggered restart — none of which apply on iOS — are now compiled out on mobile via `#[cfg(desktop)]`; their Tauri permissions moved to a new desktop-only capabilities file (`capabilities/desktop.json`, scoped via `"platforms"`). Added `src-tauri/tauri.ios.conf.json` to override the Tauri `identifier` to the App Store bundle ID (`com.adamjolicoeur.timetraked`) on iOS only, leaving the existing desktop identifier (and its app-data dir/keychain/updater identity) untouched. Also closed a touch-accessibility gap found during the investigation: the command palette (`Cmd/Ctrl+K`) previously had no on-screen trigger, only a keyboard shortcut and a desktop-only menu item — added a header button so it's reachable on iOS, which has neither. `src/lib/platform.ts` gained an `isIOS` check (touch-point-qualified, since iPadOS spoofs a "Macintosh" user agent) used to fix `isMac` misdetecting iPhone/iPad and to gate the startup silent-update check off on iOS. Corrected `index.html`'s CSP, which allowlisted a stale `capacitor://localhost` left over from an earlier Capacitor-based plan (this project ships via Tauri). CI/App Store Connect signing automation is an explicit follow-up, not included here — `src-tauri/src/lib.rs` (new), `src-tauri/src/main.rs`, `src-tauri/Cargo.toml`, `src-tauri/tauri.ios.conf.json` (new), `src-tauri/capabilities/default.json`, `src-tauri/capabilities/desktop.json` (new), `src/lib/platform.ts`, `src/lib/tauriElectronApiShim.ts`, `src/App.tsx`, `index.html`, `package.json` - Manual save now shows a toast confirming the save succeeded, or reporting failure so it isn't silently swallowed. Applies to all three manual-save entry points that share the same handler: the `Cmd/Ctrl+S` keyboard shortcut, the Command Palette's "Save Changes" action, and the Electron menu's Save item. `forceSyncToDatabase` now resolves a `boolean` (`true` on full success, `false` if any save failed) instead of `void`, so callers can react to the outcome diff --git a/src/components/ArchiveEditDialog.tsx b/src/components/ArchiveEditDialog.tsx index 0160e60..b268ecc 100644 --- a/src/components/ArchiveEditDialog.tsx +++ b/src/components/ArchiveEditDialog.tsx @@ -34,6 +34,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -56,6 +57,8 @@ import { Edit, AlertTriangle, RotateCcw, + Plus, + X, } from "lucide-react"; import { formatDuration, @@ -119,15 +122,28 @@ export const ArchiveEditDialog: React.FC = ({ categories, projects, isDayStarted, + addTaskToArchivedDay, } = useTimeTracking(); const { toast } = useToast(); const { confirmDelete } = useUndoableDelete(); const [isSummaryEditing, setIsSummaryEditing] = useState(false); const [expandedTaskId, setExpandedTaskId] = useState(null); + const [isAddingTask, setIsAddingTask] = useState(false); const [isSaving, setIsSaving] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showRestoreDialog, setShowRestoreDialog] = useState(false); + const [newTaskData, setNewTaskData] = useState({ + title: "", + description: "", + project: "none" as string, + category: "none" as string, + }); + const [newTaskTimes, setNewTaskTimes] = useState({ + startTime: "", + endTime: "", + }); + const dayForm = useForm({ resolver: zodResolver(dayFormSchema), mode: "onBlur", @@ -174,6 +190,7 @@ export const ArchiveEditDialog: React.FC = ({ setIsSummaryEditing(false); setExpandedTaskId(null); setShowDeleteConfirm(false); + handleCancelNewTask(); }; // Initialize form data when dialog opens @@ -338,6 +355,53 @@ export const ArchiveEditDialog: React.FC = ({ setExpandedTaskId((cur) => (cur === id ? null : id)); }, []); + const handleAddTask = () => { + const selectedProject = + newTaskData.project !== "none" + ? projects.find((p) => p.id === newTaskData.project) + : undefined; + const selectedCategory = + newTaskData.category !== "none" + ? categories.find((c) => c.id === newTaskData.category) + : undefined; + + const baseDate = day.startTime; + const startTime = parseTimeInput(newTaskTimes.startTime, baseDate); + const endTime = newTaskTimes.endTime + ? parseTimeInput(newTaskTimes.endTime, baseDate) + : undefined; + + const newTask: Task = { + id: crypto.randomUUID(), + title: newTaskData.title.trim(), + description: newTaskData.description.trim() || undefined, + project: selectedProject?.name || undefined, + client: selectedProject?.client || undefined, + category: selectedCategory?.id || undefined, + startTime, + endTime, + duration: endTime + ? endTime.getTime() - startTime.getTime() + : undefined, + }; + + handleCancelNewTask(); + setIsSaving(true); + addTaskToArchivedDay(day.id, newTask) + .catch((error) => { + console.error("Failed to add task to archived day:", error); + }) + .finally(() => { + setIsSaving(false); + }); + }; + + const handleCancelNewTask = () => { + setNewTaskData({ title: "", description: "", project: "none", category: "none" }); + setNewTaskTimes({ startTime: "", endTime: "" }); + setIsAddingTask(false); + }; + const handleCancel = () => { resetFormState(); }; @@ -604,7 +668,30 @@ export const ArchiveEditDialog: React.FC = ({ {/* Tasks Detail */} - Tasks ({tasks.length}) +
+ Tasks ({tasks.length}) + +
@@ -633,6 +720,177 @@ export const ArchiveEditDialog: React.FC = ({ projects={projects} /> ))} + {isAddingTask && ( + + +
+
+ + + setNewTaskData((prev) => ({ ...prev, title: e.target.value })) + } + placeholder="Enter task title" + autoFocus + /> +
+ +
+ + + + Edit + Preview + + +