+
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
);
}
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..f1d8c73
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
new file mode 100644
index 0000000..996cbc7
--- /dev/null
+++ b/frontend/src/lib/api.ts
@@ -0,0 +1,153 @@
+import { z } from "zod";
+
+// --- Zod schemas ---
+
+export const UserSchema = z.object({
+ id: z.number(),
+ name: z.string(),
+ defaultHourlyRateCents: z.number(),
+});
+
+export const ProjectSchema = z.object({
+ id: z.number(),
+ name: z.string(),
+ timeBudgetMinutes: z.number(),
+ costBudgetCents: z.number(),
+ hourlyRateCents: z.number().nullable(),
+ minutesLogged: z.number(),
+ costLoggedCents: z.number(),
+ timeExceeded: z.boolean(),
+ costExceeded: z.boolean(),
+});
+
+export const TimeEntrySchema = z.object({
+ id: z.number(),
+ userId: z.number(),
+ userName: z.string(),
+ projectId: z.number(),
+ projectName: z.string(),
+ entryDate: z.string(),
+ durationMinutes: z.number(),
+});
+
+export const DayReportSchema = z.object({
+ date: z.string(),
+ totalMinutes: z.number(),
+ totalCostCents: z.number(),
+});
+
+export const WeeklyReportSchema = z.object({
+ days: z.array(DayReportSchema),
+ weekTotalMinutes: z.number(),
+ weekTotalCostCents: z.number(),
+});
+
+export const ProjectMonthReportSchema = z.object({
+ projectId: z.number(),
+ name: z.string(),
+ minutes: z.number(),
+ costCents: z.number(),
+ timeBudgetMinutes: z.number(),
+ costBudgetCents: z.number(),
+ timeBudgetPct: z.number(),
+ costBudgetPct: z.number(),
+ breached: z.boolean(),
+});
+
+export const MonthlyReportSchema = z.object({
+ projects: z.array(ProjectMonthReportSchema),
+ monthTotalMinutes: z.number(),
+ monthTotalCostCents: z.number(),
+});
+
+// --- Inferred types ---
+
+export type User = z.infer