diff --git a/src/app/dashboard/workforce/timesheets/page.tsx b/src/app/dashboard/workforce/timesheets/page.tsx index db090eb..68b980f 100644 --- a/src/app/dashboard/workforce/timesheets/page.tsx +++ b/src/app/dashboard/workforce/timesheets/page.tsx @@ -1,7 +1,10 @@ +import Link from "next/link"; +import { ClipboardList } from "lucide-react"; import { pbAdmin } from "@/lib/pb"; import { Topbar } from "@/components/layout/sidebar"; +import { Button } from "@/components/ui/button"; import { ImportTimesheetsButton } from "@/components/import-timesheets-button"; -import { TimesheetsView, type TimesheetEntryRow, type UploadRow } from "./timesheets-view"; +import { TimesheetsView, type TimesheetEntryRow } from "./timesheets-view"; export const dynamic = "force-dynamic"; @@ -16,9 +19,7 @@ export default async function TimesheetsPage() { pb.collection("employees").getFullList({ sort: "name" }), pb.collection("sites").getFullList({ sort: "name" }), pb.collection("timesheets").getFullList({ - sort: "-date_uploaded", - fields: - "id,source_file,date_uploaded,processing_status,rows_created,rows_failed,last_error", + fields: "id,processing_status", }), ]); @@ -48,34 +49,31 @@ export default async function TimesheetsPage() { }; }); - const uploads: UploadRow[] = documents.map((d) => ({ - id: d.id, - sourceFile: (d.source_file as string) ?? "", - dateUploaded: (d.date_uploaded as string) ?? "", - processingStatus: (d.processing_status as string) ?? "", - rowsCreated: (d.rows_created as number) ?? 0, - rowsFailed: (d.rows_failed as number) ?? 0, - lastError: (d.last_error as string) ?? "", - })); - return ( <> ({ - id: s.id, - name: s.name, - project_code: s.project_code, - }))} - /> + <> + + ({ + id: s.id, + name: s.name, + project_code: s.project_code, + }))} + /> + } /> ({ id: s.id, name: s.name, diff --git a/src/app/dashboard/workforce/timesheets/timesheets-view.tsx b/src/app/dashboard/workforce/timesheets/timesheets-view.tsx index 17aa8af..375a6cd 100644 --- a/src/app/dashboard/workforce/timesheets/timesheets-view.tsx +++ b/src/app/dashboard/workforce/timesheets/timesheets-view.tsx @@ -47,24 +47,11 @@ export interface TimesheetEntryRow { documentStatus?: string | null; } -export interface UploadRow { - id: string; - sourceFile: string; - /** ISO date string (YYYY-MM-DD) or empty. */ - dateUploaded: string; - processingStatus: string; - rowsCreated: number; - rowsFailed: number; - lastError: string; -} - export function TimesheetsView({ rows, - uploads, sites, }: { rows: TimesheetEntryRow[]; - uploads: UploadRow[]; sites: { id: string; name: string; project_code: string }[]; }) { const [siteFilter, setSiteFilter] = React.useState("all"); @@ -197,68 +184,6 @@ export function TimesheetsView({ - - - Upload status -

- Every uploaded sheet and whether it processed cleanly — separate from - the individual hour entries below. -

-
- - {uploads.length === 0 ? ( -
- No timesheets uploaded yet. -
- ) : ( - - - - File - Uploaded - Status - Rows created - Rows failed - - - - {uploads.map((u) => ( - - - {u.sourceFile || "—"} - - - {u.dateUploaded ? format(parseISO(u.dateUploaded), "yyyy-MM-dd") : "—"} - - - - {u.processingStatus || "pending"} - - - - {formatNumber(u.rowsCreated)} - - - {formatNumber(u.rowsFailed)} - - - ))} - -
- )} -
-
-
diff --git a/src/app/dashboard/workforce/timesheets/upload-status/page.tsx b/src/app/dashboard/workforce/timesheets/upload-status/page.tsx new file mode 100644 index 0000000..d09f1ce --- /dev/null +++ b/src/app/dashboard/workforce/timesheets/upload-status/page.tsx @@ -0,0 +1,45 @@ +import Link from "next/link"; +import { ArrowLeft } from "lucide-react"; +import { pbAdmin } from "@/lib/pb"; +import { Topbar } from "@/components/layout/sidebar"; +import { Button } from "@/components/ui/button"; +import { UploadStatusView, type UploadRow } from "./upload-status-view"; + +export const dynamic = "force-dynamic"; + +export default async function UploadStatusPage() { + const pb = await pbAdmin(); + + const documents = await pb.collection("timesheets").getFullList({ + sort: "-date_uploaded", + fields: "id,source_file,date_uploaded,processing_status,rows_created,rows_failed,last_error", + }); + + const uploads: UploadRow[] = documents.map((d) => ({ + id: d.id, + sourceFile: (d.source_file as string) ?? "", + dateUploaded: (d.date_uploaded as string) ?? "", + processingStatus: (d.processing_status as string) ?? "", + rowsCreated: (d.rows_created as number) ?? 0, + rowsFailed: (d.rows_failed as number) ?? 0, + lastError: (d.last_error as string) ?? "", + })); + + return ( + <> + + + + Back to Timesheets + + + } + /> + + + ); +} diff --git a/src/app/dashboard/workforce/timesheets/upload-status/upload-status-view.tsx b/src/app/dashboard/workforce/timesheets/upload-status/upload-status-view.tsx new file mode 100644 index 0000000..0de660e --- /dev/null +++ b/src/app/dashboard/workforce/timesheets/upload-status/upload-status-view.tsx @@ -0,0 +1,91 @@ +import { format, parseISO } from "date-fns"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { cn, formatNumber } from "@/lib/utils"; + +export interface UploadRow { + id: string; + sourceFile: string; + /** ISO date string (YYYY-MM-DD) or empty. */ + dateUploaded: string; + processingStatus: string; + rowsCreated: number; + rowsFailed: number; + lastError: string; +} + +export function UploadStatusView({ uploads }: { uploads: UploadRow[] }) { + return ( +
+ + + Upload status +

+ Every uploaded sheet and whether it processed cleanly — separate from + the individual hour entries on the Timesheets page. +

+
+ + {uploads.length === 0 ? ( +
+ No timesheets uploaded yet. +
+ ) : ( + + + + File + Uploaded + Status + Rows created + Rows failed + + + + {uploads.map((u) => ( + + + {u.sourceFile || "—"} + + + {u.dateUploaded ? format(parseISO(u.dateUploaded), "yyyy-MM-dd") : "—"} + + + + {u.processingStatus || "pending"} + + + + {formatNumber(u.rowsCreated)} + + + {formatNumber(u.rowsFailed)} + + + ))} + +
+ )} +
+
+
+ ); +}