Extract duplicate date formatting functions to shared utilities
- Created frontend/app/utils/date.ts with formatDate, formatTime, formatDateTime, formatDisplayDate - Created frontend/e2e/helpers/date.ts with formatDateLocal, getTomorrowDateStr - Updated all frontend pages and e2e tests to use shared utilities - Removed duplicate date formatting code from 6 files
This commit is contained in:
parent
eefdfd714f
commit
6ff3c0a133
9 changed files with 78 additions and 98 deletions
|
|
@ -7,23 +7,11 @@ import { api } from "../../api";
|
||||||
import { Header } from "../../components/Header";
|
import { Header } from "../../components/Header";
|
||||||
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
||||||
import { components } from "../../generated/api";
|
import { components } from "../../generated/api";
|
||||||
|
import { formatDateTime } from "../../utils/date";
|
||||||
|
|
||||||
type AppointmentResponse = components["schemas"]["AppointmentResponse"];
|
type AppointmentResponse = components["schemas"]["AppointmentResponse"];
|
||||||
type PaginatedAppointments = components["schemas"]["PaginatedResponse_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
|
// Helper to get status display
|
||||||
function getStatusDisplay(status: string): { text: string; bgColor: string; textColor: string } {
|
function getStatusDisplay(status: string): { text: string; bgColor: string; textColor: string } {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Header } from "../../components/Header";
|
||||||
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
||||||
import { components } from "../../generated/api";
|
import { components } from "../../generated/api";
|
||||||
import constants from "../../../../shared/constants.json";
|
import constants from "../../../../shared/constants.json";
|
||||||
|
import { formatDate, formatDisplayDate } from "../../utils/date";
|
||||||
|
|
||||||
const { slotDurationMinutes, maxAdvanceDays } = constants.booking;
|
const { slotDurationMinutes, maxAdvanceDays } = constants.booking;
|
||||||
|
|
||||||
|
|
@ -15,14 +16,6 @@ type AvailabilityDay = components["schemas"]["AvailabilityDay"];
|
||||||
type AvailabilityResponse = components["schemas"]["AvailabilityResponse"];
|
type AvailabilityResponse = components["schemas"]["AvailabilityResponse"];
|
||||||
type TimeSlot = components["schemas"]["TimeSlot"];
|
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
|
// Helper to get next N days starting from tomorrow
|
||||||
function getDateRange(): Date[] {
|
function getDateRange(): Date[] {
|
||||||
const dates: 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 => {
|
const formatSlotTime = (slot: TimeSlot): string => {
|
||||||
return `${slot.start_time.slice(0, 5)} - ${slot.end_time.slice(0, 5)}`;
|
return `${slot.start_time.slice(0, 5)} - ${slot.end_time.slice(0, 5)}`;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,10 @@ import { api } from "../api";
|
||||||
import { Header } from "../components/Header";
|
import { Header } from "../components/Header";
|
||||||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||||
import { components } from "../generated/api";
|
import { components } from "../generated/api";
|
||||||
|
import { formatDateTime } from "../utils/date";
|
||||||
|
|
||||||
type AppointmentResponse = components["schemas"]["AppointmentResponse"];
|
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
|
// Helper to get status display
|
||||||
function getStatusDisplay(status: string): { text: string; bgColor: string; textColor: string } {
|
function getStatusDisplay(status: string): { text: string; bgColor: string; textColor: string } {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Header } from "../components/Header";
|
||||||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||||
import { components } from "../generated/api";
|
import { components } from "../generated/api";
|
||||||
import constants from "../../../shared/constants.json";
|
import constants from "../../../shared/constants.json";
|
||||||
|
import { formatDate, formatTime } from "../utils/date";
|
||||||
|
|
||||||
const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays, noteMaxLength } = constants.booking;
|
const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays, noteMaxLength } = constants.booking;
|
||||||
|
|
||||||
|
|
@ -15,24 +16,6 @@ type BookableSlot = components["schemas"]["BookableSlot"];
|
||||||
type AvailableSlotsResponse = components["schemas"]["AvailableSlotsResponse"];
|
type AvailableSlotsResponse = components["schemas"]["AvailableSlotsResponse"];
|
||||||
type AppointmentResponse = components["schemas"]["AppointmentResponse"];
|
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)
|
// Get date range for booking (tomorrow to +30 days)
|
||||||
function getBookableDates(): Date[] {
|
function getBookableDates(): Date[] {
|
||||||
const dates: Date[] = [];
|
const dates: Date[] = [];
|
||||||
|
|
|
||||||
48
frontend/app/utils/date.ts
Normal file
48
frontend/app/utils/date.ts
Normal file
|
|
@ -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" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { test, expect, Page } from "@playwright/test";
|
import { test, expect, Page } from "@playwright/test";
|
||||||
|
import { formatDateLocal, getTomorrowDateStr } from "./helpers/date";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appointments Page E2E Tests
|
* 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 });
|
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
|
// Set up availability and create a booking
|
||||||
async function createTestBooking(page: Page) {
|
async function createTestBooking(page: Page) {
|
||||||
const dateStr = getTomorrowDateStr();
|
const dateStr = getTomorrowDateStr();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { test, expect, Page } from "@playwright/test";
|
import { test, expect, Page } from "@playwright/test";
|
||||||
|
import { formatDateLocal, getTomorrowDateStr } from "./helpers/date";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Availability Page E2E Tests
|
* Availability Page E2E Tests
|
||||||
|
|
@ -45,20 +46,6 @@ function getTomorrowDisplay(): string {
|
||||||
return tomorrow.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
|
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.describe("Availability Page - Admin Access", () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await clearAuth(page);
|
await clearAuth(page);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { test, expect, Page } from "@playwright/test";
|
import { test, expect, Page } from "@playwright/test";
|
||||||
|
import { formatDateLocal, getTomorrowDateStr } from "./helpers/date";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Booking Page E2E Tests
|
* 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 });
|
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
|
// Set up availability for a date using the API with retry logic
|
||||||
async function setAvailability(page: Page, dateStr: string, maxRetries = 3) {
|
async function setAvailability(page: Page, dateStr: string, maxRetries = 3) {
|
||||||
const cookies = await page.context().cookies();
|
const cookies = await page.context().cookies();
|
||||||
|
|
|
||||||
23
frontend/e2e/helpers/date.ts
Normal file
23
frontend/e2e/helpers/date.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue