import { useState, useMemo } from "react" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import { api } from "../lib/api" import { EmptyState } from "../components/EmptyState" import { LoadingSpinner } from "../components/LoadingSpinner" import type { TimeEntryInsert } from "@emberclone/shared" export default function TimeEntries() { const queryClient = useQueryClient() const [formData, setFormData] = useState({ description: "", startTime: "", endTime: "", projectId: "" }) const [filters, setFilters] = useState({ search: "", from: "", to: "" }) const [selectedIds, setSelectedIds] = useState([]) const { data: entries, isLoading, isError } = useQuery({ queryKey: ["time-entries", filters.from, filters.to], queryFn: () => api.listTimeEntries({ from: filters.from || undefined, to: filters.to || undefined }) }) const createMutation = useMutation({ mutationFn: (data: Partial) => api.createTimeEntry(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["time-entries"] }) setFormData({ description: "", startTime: "", endTime: "", projectId: "" }) } }) const deleteMutation = useMutation({ mutationFn: (id: string) => api.deleteTimeEntry(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["time-entries"] }) } }) const bulkDeleteMutation = useMutation({ mutationFn: (ids: string[]) => api.bulkDeleteTimeEntries(ids), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["time-entries"] }) setSelectedIds([]) } }) const filteredEntries = useMemo(() => { if (!entries) return [] return entries.filter(entry => entry.description?.toLowerCase().includes(filters.search.toLowerCase()) ) }, [entries, filters.search]) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() createMutation.mutate({ description: formData.description, startTime: new Date(formData.startTime) as any, endTime: new Date(formData.endTime) as any, projectId: formData.projectId || undefined }) } const handleExport = () => { const params = new URLSearchParams() if (filters.from) params.append("from", filters.from) if (filters.to) params.append("to", filters.to) window.location.href = `/api/time-entries/export.csv?${params.toString()}` } const toggleSelectAll = () => { if (selectedIds.length === filteredEntries.length) { setSelectedIds([]) } else { setSelectedIds(filteredEntries.map(e => e.id)) } } const toggleSelect = (id: string) => { setSelectedIds(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] ) } if (isError) return
Error loading time entries.
return (

Time Tracking

Manage your work logs and project hours

Log New Entry

setFormData({ ...formData, description: e.target.value })} placeholder="What did you work on?" />
setFormData({ ...formData, startTime: e.target.value })} />
setFormData({ ...formData, endTime: e.target.value })} />
setFormData({ ...formData, projectId: e.target.value })} placeholder="Optional" />
setFilters({ ...filters, search: e.target.value })} /> setFilters({ ...filters, from: e.target.value })} /> setFilters({ ...filters, to: e.target.value })} />
{selectedIds.length > 0 && (
{selectedIds.length} entries selected
)} {isLoading ? (
) : filteredEntries.length === 0 ? ( ) : (
{filteredEntries.map((entry) => { const durationMs = new Date(entry.endTime).getTime() - new Date(entry.startTime).getTime() const durationHrs = (durationMs / (1000 * 60 * 60)).toFixed(2) return ( ) })}
0} onChange={toggleSelectAll} /> Description Start End Duration Actions
toggleSelect(entry.id)} /> {entry.description} {new Date(entry.startTime).toLocaleString()} {new Date(entry.endTime).toLocaleString()} {durationHrs}h
)}
) }