diff --git a/app/admin/data-browser/contracts/page.tsx b/app/admin/data-browser/contracts/page.tsx index bc6c9a4..d43ecbf 100644 --- a/app/admin/data-browser/contracts/page.tsx +++ b/app/admin/data-browser/contracts/page.tsx @@ -8,8 +8,10 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ArrowLeft, Table2 } from 'lucide-react'; import Link from 'next/link'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; export default function ContractsBrowserPage() { + const tz = useUserTimezone(); const [contracts, setContracts] = useState([]); const [totalCount, setTotalCount] = useState(0); const [page, setPage] = useState(1); @@ -85,7 +87,7 @@ export default function ContractsBrowserPage() { label: 'Start Date', sortable: true, render: (value: string) => ( - value ? new Date(value).toLocaleDateString() : '-' + value ? new Date(value).toLocaleDateString(undefined, { timeZone: tz }) : '-' ), }, { @@ -93,7 +95,7 @@ export default function ContractsBrowserPage() { label: 'End Date', sortable: true, render: (value: string) => ( - value ? new Date(value).toLocaleDateString() : '-' + value ? new Date(value).toLocaleDateString(undefined, { timeZone: tz }) : '-' ), }, ]; diff --git a/app/admin/data-browser/projects/page.tsx b/app/admin/data-browser/projects/page.tsx index ec2c77c..a824094 100644 --- a/app/admin/data-browser/projects/page.tsx +++ b/app/admin/data-browser/projects/page.tsx @@ -8,8 +8,10 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ArrowLeft, FolderKanban } from 'lucide-react'; import Link from 'next/link'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; export default function ProjectsBrowserPage() { + const tz = useUserTimezone(); const [projects, setProjects] = useState([]); const [totalCount, setTotalCount] = useState(0); const [page, setPage] = useState(1); @@ -85,7 +87,7 @@ export default function ProjectsBrowserPage() { label: 'Start Date', sortable: true, render: (value: string) => ( - value ? new Date(value).toLocaleDateString() : '-' + value ? new Date(value).toLocaleDateString(undefined, { timeZone: tz }) : '-' ), }, { diff --git a/app/admin/data-browser/tasks/page.tsx b/app/admin/data-browser/tasks/page.tsx index 19d2658..e3adc87 100644 --- a/app/admin/data-browser/tasks/page.tsx +++ b/app/admin/data-browser/tasks/page.tsx @@ -8,8 +8,10 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ArrowLeft, CheckSquare } from 'lucide-react'; import Link from 'next/link'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; export default function TasksBrowserPage() { + const tz = useUserTimezone(); const [tasks, setTasks] = useState([]); const [totalCount, setTotalCount] = useState(0); const [page, setPage] = useState(1); @@ -97,7 +99,7 @@ export default function TasksBrowserPage() { label: 'Created', sortable: true, render: (value: string) => ( - value ? new Date(value).toLocaleDateString() : '-' + value ? new Date(value).toLocaleDateString(undefined, { timeZone: tz }) : '-' ), }, ]; diff --git a/app/admin/data-browser/ticket-notes/page.tsx b/app/admin/data-browser/ticket-notes/page.tsx index ae5ce9c..59cde6c 100644 --- a/app/admin/data-browser/ticket-notes/page.tsx +++ b/app/admin/data-browser/ticket-notes/page.tsx @@ -11,6 +11,7 @@ import DataTable from '@/components/admin/DataTable'; import DetailModal from '@/components/admin/DetailModal'; import { MessageSquare, ArrowLeft, RefreshCw, Filter, Calendar, User } from 'lucide-react'; import Link from 'next/link'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; const NOTE_TYPE: Record = { 1: 'Task Detail', 2: 'Time Entry Note', 3: 'Ticket Detail', @@ -23,6 +24,7 @@ const PUBLISH_MAP: Record = { }; export default function TicketNotesPage() { + const tz = useUserTimezone(); const [notes, setNotes] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -83,7 +85,7 @@ export default function TicketNotesPage() { label: 'Date', sortable: true, render: (v: string) => v - ? {new Date(v).toLocaleDateString()} + ? {new Date(v).toLocaleDateString(undefined, { timeZone: tz })} : , }, { diff --git a/app/admin/data-browser/tickets/page.tsx b/app/admin/data-browser/tickets/page.tsx index fc00eef..dca1da8 100644 --- a/app/admin/data-browser/tickets/page.tsx +++ b/app/admin/data-browser/tickets/page.tsx @@ -9,9 +9,11 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ArrowLeft, Ticket } from 'lucide-react'; import Link from 'next/link'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; export default function TicketsBrowserPage() { const router = useRouter(); + const tz = useUserTimezone(); const [tickets, setTickets] = useState([]); const [totalCount, setTotalCount] = useState(0); const [page, setPage] = useState(1); @@ -100,7 +102,7 @@ export default function TicketsBrowserPage() { key: 'create_date', label: 'Created', sortable: true, - render: (value: string) => value ? new Date(value).toLocaleDateString() : '-', + render: (value: string) => value ? new Date(value).toLocaleDateString(undefined, { timeZone: tz }) : '-', }, { key: 'is_deleted', diff --git a/app/admin/data-browser/time-entries/page.tsx b/app/admin/data-browser/time-entries/page.tsx index b0e8476..380d2b7 100644 --- a/app/admin/data-browser/time-entries/page.tsx +++ b/app/admin/data-browser/time-entries/page.tsx @@ -23,6 +23,7 @@ import { TicketX } from 'lucide-react'; import Link from 'next/link'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; import { TimeEntry } from '@/lib/types/database'; function cn(...classes: string[]) { @@ -30,6 +31,7 @@ function cn(...classes: string[]) { } export default function TimeEntriesPage() { + const tz = useUserTimezone(); const [timeEntries, setTimeEntries] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -293,7 +295,7 @@ export default function TimeEntriesPage() { render: (value: string) => (
- {new Date(value).toLocaleDateString()} + {new Date(value).toLocaleDateString(undefined, { timeZone: tz })}
), }, diff --git a/app/admin/data-browser/time-entries/page.tsx.backup b/app/admin/data-browser/time-entries/page.tsx.backup deleted file mode 100644 index 9e30d14..0000000 --- a/app/admin/data-browser/time-entries/page.tsx.backup +++ /dev/null @@ -1,393 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import DataTable from '@/components/admin/DataTable'; -import DetailModal from '@/components/admin/DetailModal'; -import { - Clock, - Users, - Ticket, - Calendar, - ArrowLeft, - Home, - RefreshCw, - Download, - Filter -} from 'lucide-react'; -import Link from 'next/link'; -import { TimeEntry } from '@/lib/types/database'; - -export default function TimeEntriesPage() { - const [timeEntries, setTimeEntries] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedEntry, setSelectedEntry] = useState(null); - const [showDetailModal, setShowDetailModal] = useState(false); - - // Pagination states - const [totalCount, setTotalCount] = useState(0); - const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(100); - - // Filter states - const [search, setSearch] = useState(''); - const [startDate, setStartDate] = useState(''); - const [endDate, setEndDate] = useState(''); - const [billable, setBillable] = useState('all'); - const [approved, setApproved] = useState('all'); - const [limit, setLimit] = useState('100'); - - const fetchTimeEntries = async (page: number = 1) => { - setLoading(true); - setError(null); - - try { - const offset = (page - 1) * pageSize; - const params = new URLSearchParams(); - - if (search) params.append('search', search); - if (startDate) params.append('start_date', startDate); - if (endDate) params.append('end_date', endDate); - if (billable !== 'all') params.append('billable', billable); - if (approved !== 'all') params.append('approved', approved); - params.append('limit', pageSize.toString()); - params.append('offset', offset.toString()); - - const response = await fetch(`/api/data/time-entries?${params}`); - - if (!response.ok) { - throw new Error(`Failed to fetch time entries: ${response.statusText}`); - } - - const data = await response.json(); - setTimeEntries(data.timeEntries || []); - setTotalCount(data.pagination?.total || 0); - setCurrentPage(page); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - setError(errorMessage); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchTimeEntries(1); - }, [pageSize]); - - const handleRefresh = () => { - fetchTimeEntries(currentPage); - }; - - const handlePageChange = (newPage: number) => { - fetchTimeEntries(newPage); - }; - - const handleExport = async () => { - try { - const params = new URLSearchParams({ format: 'csv' }); - - if (search) params.append('search', search); - if (startDate) params.append('start_date', startDate); - if (endDate) params.append('end_date', endDate); - if (billable !== 'all') params.append('billable', billable); - if (approved !== 'all') params.append('approved', approved); - - const response = await fetch(`/api/data/time-entries/export?${params}`); - - if (!response.ok) { - throw new Error(`Failed to export data: ${response.statusText}`); - } - - // Download file - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `time-entries-${new Date().toISOString().split('T')[0]}.csv`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - setError(errorMessage); - } - }; - - const handleRowClick = (entry: TimeEntry) => { - setSelectedEntry(entry); - setShowDetailModal(true); - }; - - const columns = [ - { - key: 'id', - label: 'ID', - sortable: true, - }, - { - key: 'resource_id', - label: 'Resource', - sortable: true, - render: (value: number) => ( - - - {value} - - ), - }, - { - key: 'ticket_id', - label: 'Ticket', - sortable: true, - render: (value: number) => ( - - - {value} - - ), - }, - { - key: 'entry_date', - label: 'Date', - sortable: true, - render: (value: string) => ( -
- - {new Date(value).toLocaleDateString()} -
- ), - }, - { - key: 'hours_worked', - label: 'Hours', - sortable: true, - render: (value: number | string) => { - const hours = typeof value === 'string' ? parseFloat(value) : value; - return ( - 4 ? 'destructive' : 'secondary'}> - - {hours.toFixed(1)}h - - ); - }, - }, - { - key: 'title', - label: 'Title', - sortable: true, - render: (value: string) => ( -
- {value || 'No title'} -
- ), - }, - { - key: 'billable', - label: 'Billable', - sortable: true, - render: (value: boolean) => ( - - {value ? 'Yes' : 'No'} - - ), - }, - { - key: 'approved', - label: 'Approved', - sortable: true, - render: (value: boolean) => ( - - {value ? 'Yes' : 'No'} - - ), - }, - ]; - - return ( -
- {/* Header */} -
-
- - - - -
-

Time Entries

-

Browse and analyze time tracking data

-
-
- -
- - - - - -
-
- - {/* Filters */} - - - - - Filters - - - -
-
- - setSearch(e.target.value)} - /> -
- -
- - setStartDate(e.target.value)} - /> -
- -
- - setEndDate(e.target.value)} - /> -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
-
- -
-
-
- - {/* Data Table */} - - - Time Entries ({timeEntries.length}) - - Click on any row to view detailed information - - - - {error && ( -
-

{error}

-
- )} - - -
-
- - {/* Detail Modal */} - setShowDetailModal(open)} - title={`Time Entry #${selectedEntry?.id}`} - data={selectedEntry} - /> -
- ); -} - -function cn(...classes: string[]) { - return classes.filter(Boolean).join(' '); -}