diff --git a/frontend/app/admin/appointments/page.tsx b/frontend/app/admin/appointments/page.tsx index 3ed8d62..dbe7db8 100644 --- a/frontend/app/admin/appointments/page.tsx +++ b/frontend/app/admin/appointments/page.tsx @@ -7,23 +7,11 @@ import { api } from "../../api"; import { Header } from "../../components/Header"; import { useRequireAuth } from "../../hooks/useRequireAuth"; import { components } from "../../generated/api"; +import { formatDateTime } from "../../utils/date"; type AppointmentResponse = components["schemas"]["AppointmentResponse"]; type PaginatedAppointments = components["schemas"]["PaginatedResponse_AppointmentResponse_"]; -// Helper to format datetime -function formatDateTime(isoString: string): string { - const d = new Date(isoString); - return d.toLocaleString("en-US", { - weekday: "short", - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); -} - // Helper to get status display function getStatusDisplay(status: string): { text: string; bgColor: string; textColor: string } { switch (status) { diff --git a/frontend/app/admin/availability/page.tsx b/frontend/app/admin/availability/page.tsx index 7d3d725..bd0d554 100644 --- a/frontend/app/admin/availability/page.tsx +++ b/frontend/app/admin/availability/page.tsx @@ -8,6 +8,7 @@ import { Header } from "../../components/Header"; import { useRequireAuth } from "../../hooks/useRequireAuth"; import { components } from "../../generated/api"; import constants from "../../../../shared/constants.json"; +import { formatDate, formatDisplayDate } from "../../utils/date"; const { slotDurationMinutes, maxAdvanceDays } = constants.booking; @@ -15,14 +16,6 @@ type AvailabilityDay = components["schemas"]["AvailabilityDay"]; type AvailabilityResponse = components["schemas"]["AvailabilityResponse"]; type TimeSlot = components["schemas"]["TimeSlot"]; -// Helper to format date as YYYY-MM-DD in local timezone -function formatDate(d: Date): string { - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, "0"); - const day = String(d.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - // Helper to get next N days starting from tomorrow function getDateRange(): Date[] { const dates: Date[] = []; @@ -223,10 +216,6 @@ export default function AdminAvailabilityPage() { } }; - const formatDisplayDate = (d: Date): string => { - return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" }); - }; - const formatSlotTime = (slot: TimeSlot): string => { return `${slot.start_time.slice(0, 5)} - ${slot.end_time.slice(0, 5)}`; }; diff --git a/frontend/app/appointments/page.tsx b/frontend/app/appointments/page.tsx index 2bafac7..3af0c70 100644 --- a/frontend/app/appointments/page.tsx +++ b/frontend/app/appointments/page.tsx @@ -7,22 +7,10 @@ import { api } from "../api"; import { Header } from "../components/Header"; import { useRequireAuth } from "../hooks/useRequireAuth"; import { components } from "../generated/api"; +import { formatDateTime } from "../utils/date"; type AppointmentResponse = components["schemas"]["AppointmentResponse"]; -// Helper to format datetime -function formatDateTime(isoString: string): string { - const d = new Date(isoString); - return d.toLocaleString("en-US", { - weekday: "short", - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); -} - // Helper to get status display function getStatusDisplay(status: string): { text: string; bgColor: string; textColor: string } { switch (status) { diff --git a/frontend/app/booking/page.tsx b/frontend/app/booking/page.tsx index a0eddaa..13811a4 100644 --- a/frontend/app/booking/page.tsx +++ b/frontend/app/booking/page.tsx @@ -8,6 +8,7 @@ import { Header } from "../components/Header"; import { useRequireAuth } from "../hooks/useRequireAuth"; import { components } from "../generated/api"; import constants from "../../../shared/constants.json"; +import { formatDate, formatTime } from "../utils/date"; const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays, noteMaxLength } = constants.booking; @@ -15,24 +16,6 @@ type BookableSlot = components["schemas"]["BookableSlot"]; type AvailableSlotsResponse = components["schemas"]["AvailableSlotsResponse"]; type AppointmentResponse = components["schemas"]["AppointmentResponse"]; -// Helper to format date as YYYY-MM-DD in local timezone -function formatDate(d: Date): string { - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, "0"); - const day = String(d.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -// Helper to format time from ISO string -function formatTime(isoString: string): string { - const d = new Date(isoString); - return d.toLocaleTimeString("en-US", { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); -} - // Get date range for booking (tomorrow to +30 days) function getBookableDates(): Date[] { const dates: Date[] = []; diff --git a/frontend/app/utils/date.ts b/frontend/app/utils/date.ts new file mode 100644 index 0000000..729475c --- /dev/null +++ b/frontend/app/utils/date.ts @@ -0,0 +1,48 @@ +/** + * Shared date formatting utilities. + */ + +/** + * Format date as YYYY-MM-DD in local timezone. + */ +export function formatDate(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +/** + * Format time from ISO string to HH:MM format. + */ +export function formatTime(isoString: string): string { + const d = new Date(isoString); + return d.toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); +} + +/** + * Format datetime from ISO string to a readable format. + */ +export function formatDateTime(isoString: string): string { + const d = new Date(isoString); + return d.toLocaleString("en-US", { + weekday: "short", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); +} + +/** + * Format date for display (e.g., "Mon, Jan 15"). + */ +export function formatDisplayDate(d: Date): string { + return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" }); +} + diff --git a/frontend/e2e/appointments.spec.ts b/frontend/e2e/appointments.spec.ts index 893829a..a2f4f14 100644 --- a/frontend/e2e/appointments.spec.ts +++ b/frontend/e2e/appointments.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { formatDateLocal, getTomorrowDateStr } from "./helpers/date"; /** * Appointments Page E2E Tests @@ -38,20 +39,6 @@ async function loginUser(page: Page, email: string, password: string) { await page.waitForURL((url) => !url.pathname.includes("/login"), { timeout: 10000 }); } -// Helper to format date as YYYY-MM-DD in local timezone -function formatDateLocal(d: Date): string { - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, "0"); - const day = String(d.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -function getTomorrowDateStr(): string { - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - return formatDateLocal(tomorrow); -} - // Set up availability and create a booking async function createTestBooking(page: Page) { const dateStr = getTomorrowDateStr(); diff --git a/frontend/e2e/availability.spec.ts b/frontend/e2e/availability.spec.ts index bd23d86..9cb4767 100644 --- a/frontend/e2e/availability.spec.ts +++ b/frontend/e2e/availability.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { formatDateLocal, getTomorrowDateStr } from "./helpers/date"; /** * Availability Page E2E Tests @@ -45,20 +46,6 @@ function getTomorrowDisplay(): string { return tomorrow.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" }); } -// Helper to get a date string in YYYY-MM-DD format using local timezone -function formatDateLocal(d: Date): string { - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, "0"); - const day = String(d.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -function getTomorrowDateStr(): string { - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - return formatDateLocal(tomorrow); -} - test.describe("Availability Page - Admin Access", () => { test.beforeEach(async ({ page }) => { await clearAuth(page); diff --git a/frontend/e2e/booking.spec.ts b/frontend/e2e/booking.spec.ts index d382201..ac7a8f4 100644 --- a/frontend/e2e/booking.spec.ts +++ b/frontend/e2e/booking.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { formatDateLocal, getTomorrowDateStr } from "./helpers/date"; /** * Booking Page E2E Tests @@ -38,20 +39,6 @@ async function loginUser(page: Page, email: string, password: string) { await page.waitForURL((url) => !url.pathname.includes("/login"), { timeout: 10000 }); } -// Helper to format date as YYYY-MM-DD in local timezone -function formatDateLocal(d: Date): string { - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, "0"); - const day = String(d.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -function getTomorrowDateStr(): string { - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - return formatDateLocal(tomorrow); -} - // Set up availability for a date using the API with retry logic async function setAvailability(page: Page, dateStr: string, maxRetries = 3) { const cookies = await page.context().cookies(); diff --git a/frontend/e2e/helpers/date.ts b/frontend/e2e/helpers/date.ts new file mode 100644 index 0000000..d6a90d2 --- /dev/null +++ b/frontend/e2e/helpers/date.ts @@ -0,0 +1,23 @@ +/** + * Date formatting helpers for e2e tests. + */ + +/** + * Format date as YYYY-MM-DD in local timezone. + */ +export function formatDateLocal(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +/** + * Get tomorrow's date string in YYYY-MM-DD format. + */ +export function getTomorrowDateStr(): string { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + return formatDateLocal(tomorrow); +} +