import { useState, useEffect, useMemo } from 'react'; import { Button, Paper, Text, Menu, ActionIcon, Textarea, Pagination, NativeSelect, Group, Divider, useMantineTheme, Progress, Badge, ThemeIcon, Alert } from '@mantine/core'; import { json, LoaderArgs, MetaFunction, redirect } from '@remix-run/node'; import { Form, Link, Outlet, useCatch, useLoaderData, useSearchParams } from '@remix-run/react'; import { AlertTriangle, Edit, Edit3, Play, Power, Settings, Square, Trash } from 'react-feather'; import { requireUserId } from '~/session.server'; import { getTimeEntries, TimeEntry } from '~/models/timeEntry.server'; import TimeElapsed from '~/components/TimeElapsed'; import SectionTimeElapsed from '~/components/SectionTimeElapsed'; export const meta: MetaFunction = () => { return { title: 'Time entries | WorkTimer', description: 'Manage your time entries. You must be logged in to do this.' }; }; export async function loader({ request }: LoaderArgs) { const userId = await requireUserId(request); if (!userId) return redirect('/login'); const url = new URL(request.url); const page = url.searchParams.get('page') ? parseInt(url.searchParams.get('page')!, 10) : 1; const size = url.searchParams.get('size') ? parseInt(url.searchParams.get('size')!, 10) : 25; const orderBy = url.searchParams.get('orderBy') || 'createdAt'; const order = url.searchParams.get('order') || 'desc'; return json({ ...(await getTimeEntries({ page, size, userId, orderBy, order: order === 'asc' ? 'asc' : 'desc' })) }); } export default function TimeEntriesPage() { const data = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const theme = useMantineTheme(); const pageSize = useMemo(() => { return parseInt(searchParams.get('size') || '25', 10); }, [searchParams]); const page = useMemo(() => { return parseInt(searchParams.get('page') || '1', 10); }, [searchParams]); const timeEntriesPerDay = useMemo(() => { const timeEntriesPerDay: Record< string, { entries: typeof data.timeEntries; total: number } > = {}; data.timeEntries.forEach((timeEntry) => { const date = Intl.DateTimeFormat('it-IT', { year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(timeEntry.startTime)); if (!timeEntriesPerDay[date]) timeEntriesPerDay[date] = { entries: [], total: 0 }; timeEntriesPerDay[date].total += (timeEntry.endTime ? new Date(timeEntry.endTime).getTime() - new Date(timeEntry.startTime).getTime() : Date.now() - new Date(timeEntry.startTime).getTime()) / 1000; timeEntriesPerDay[date].entries.push(timeEntry); }); return timeEntriesPerDay; }, [data.timeEntries]); return (
{ setSearchParams({ page: page.toString(), size: event.currentTarget.value }); }} /> {data.total / pageSize > 1 && ( { setSearchParams({ page: page.toString(), size: pageSize.toString() }); }} /> )}
{data.total} entries new Date(t.startTime) >= new Date( new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0 ) ) as any as TimeEntry[] } size="sm" additionalLabel="today" /> new Date(t.startTime) >= new Date(new Date().getFullYear(), new Date().getMonth(), 1) ) as any as TimeEntry[] } size="sm" additionalLabel="this month" />
{Object.entries(timeEntriesPerDay).map(([date, timeEntries]) => (

{date}

{timeEntries.entries.map((timeEntry) => (
{timeEntry.description} {timeEntry.projectId && timeEntry.project && ( {timeEntry.project.name} )} Edit time entry } > Edit
} > Delete
{timeEntry.endTime ? (
) : (
)}
))}
))}
); } export function ErrorBoundary({ error }: { error: Error }) { console.error(error); return ( } title="Error" color="red"> An unexpected error occurred: {error.message} ); } export function CatchBoundary() { const caught = useCatch(); if (caught.status === 404) { return ( } title="Error" color="red"> Not found ); } throw new Error(`Unexpected caught response with status: ${caught.status}`); }