Skip to content

Commit 92dd393

Browse files
authored
feat(admin-sems-accordion): add component, stories, and tests (#947)
* feat(date-utils): add `getDateTimeStatus` function - Also use fn for existing dupe logic * fix(vitest-config): use `UTC` as timezone during tests * fix(biome): include `.config.ts` files * feat(admin-semesters-table): add component, stories, and tests * feat(game-session-schedule-card): add component, test, and stories * fix(admin-sems): align text and change input props for delete actions * feat(admin-sems-accordion): add component, stories, and tests * fix(game-schedule-card): add missing `use client` * fix(admin-semesters): parse capacities as numbers
1 parent ad4d2e6 commit 92dd393

File tree

19 files changed

+847
-12
lines changed

19 files changed

+847
-12
lines changed

apps/backend/src/utils/date.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import {
22
formatDate,
33
formatTime,
4+
GameSessionStatus,
5+
getDateTimeStatus,
46
getDaysBetweenWeekdays,
57
getGameSessionOpenDay,
68
isSameDate,
79
Weekday,
810
} from "@repo/shared"
911
import { semesterMock } from "@repo/shared/mocks"
1012
import type { Semester } from "@repo/shared/payload-types"
13+
import { vi } from "vitest"
1114

1215
describe("getDaysBetweenWeekdays", () => {
1316
it("should return 0 when fromDay and toDay are the same", () => {
@@ -218,3 +221,55 @@ describe("formatDate", () => {
218221
expect(formatDate(new Date("2025-06-15T09:30:00Z"))).toBe("Sunday, 15/06/25")
219222
})
220223
})
224+
225+
describe("getDateTimeStatus", () => {
226+
beforeEach(() => {
227+
vi.useFakeTimers()
228+
})
229+
230+
afterEach(() => {
231+
vi.useRealTimers()
232+
})
233+
234+
it("should return UPCOMING when now is before startTime", () => {
235+
const startTime = "2025-01-21T10:00:00Z" // Session starts at 10:00 AM UTC on Jan 21, 2025
236+
const endTime = "2025-01-21T12:00:00Z" // Session ends at 12:00 PM UTC on Jan 21, 2025
237+
vi.setSystemTime(new Date("2025-01-20T10:00:00Z")) // Set current time to 10:00 AM UTC on Jan 20, 2025 (before start time)
238+
expect(getDateTimeStatus(startTime, endTime)).toBe(GameSessionStatus.UPCOMING)
239+
})
240+
241+
it("should return ONGOING when now is between startTime and endTime", () => {
242+
const startTime = "2025-01-21T10:00:00Z" // Session starts at 10:00 AM UTC on Jan 21, 2025
243+
const endTime = "2025-01-21T12:00:00Z" // Session ends at 12:00 PM UTC on Jan 21, 2025
244+
vi.setSystemTime(new Date("2025-01-21T11:00:00Z")) // Set current time to 11:00 AM UTC on Jan 21, 2025 (during session)
245+
expect(getDateTimeStatus(startTime, endTime)).toBe(GameSessionStatus.ONGOING)
246+
})
247+
248+
it("should return PAST when now is after endTime", () => {
249+
const startTime = "2025-01-21T10:00:00Z" // Session starts at 10:00 AM UTC on Jan 21, 2025
250+
const endTime = "2025-01-21T12:00:00Z" // Session ends at 12:00 PM UTC on Jan 21, 2025
251+
vi.setSystemTime(new Date("2025-01-21T13:00:00Z")) // Set current time to 1:00 PM UTC on Jan 21, 2025 (after end time)
252+
expect(getDateTimeStatus(startTime, endTime)).toBe(GameSessionStatus.PAST)
253+
})
254+
255+
it("should return ONGOING when now is exactly at startTime", () => {
256+
const startTime = "2025-01-21T10:00:00Z" // Session starts at 10:00 AM UTC on Jan 21, 2025
257+
const endTime = "2025-01-21T12:00:00Z" // Session ends at 12:00 PM UTC on Jan 21, 2025
258+
vi.setSystemTime(new Date("2025-01-21T10:00:00Z")) // Set current time to exactly 10:00 AM UTC on Jan 21, 2025 (start of session)
259+
expect(getDateTimeStatus(startTime, endTime)).toBe(GameSessionStatus.ONGOING)
260+
})
261+
262+
it("should return ONGOING when now is exactly at endTime", () => {
263+
const startTime = "2025-01-21T10:00:00Z" // Session starts at 10:00 AM UTC on Jan 21, 2025
264+
const endTime = "2025-01-21T12:00:00Z" // Session ends at 12:00 PM UTC on Jan 21, 2025
265+
vi.setSystemTime(new Date("2025-01-21T12:00:00Z")) // Set current time to exactly 12:00 PM UTC on Jan 21, 2025 (end of session)
266+
expect(getDateTimeStatus(startTime, endTime)).toBe(GameSessionStatus.ONGOING)
267+
})
268+
269+
it("should handle startTime and endTime on different days", () => {
270+
const startTime = "2025-01-20T22:00:00Z" // Session starts at 10:00 PM UTC on Jan 20, 2025
271+
const endTime = "2025-01-21T02:00:00Z" // Session ends at 2:00 AM UTC on Jan 21, 2025
272+
vi.setSystemTime(new Date("2025-01-21T01:00:00Z")) // Set current time to 1:00 AM UTC on Jan 21, 2025 (during multi-day session)
273+
expect(getDateTimeStatus(startTime, endTime)).toBe(GameSessionStatus.ONGOING)
274+
})
275+
})

apps/backend/vitest.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { mergeConfig } from "vitest/config"
33

44
export default mergeConfig(backendConfig, {
55
test: {
6+
env: {
7+
TZ: "UTC",
8+
},
69
// You can add backend-specific overrides here
710
setupFiles: ["./src/test-config/vitest.setup.ts"],
811
coverage: {

apps/frontend/src/components/client/admin/tabs/admin-sessions/AdminSessions.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
"use client"
22

33
import type { AdminGameSession, GameSessionWithCounts } from "@repo/shared"
4-
import { dayjs, GameSessionStatus, type PlayLevel, Popup, Weekday } from "@repo/shared"
4+
import { dayjs, type GameSessionStatus, type PlayLevel, Popup, Weekday } from "@repo/shared"
55
import type { Booking, User } from "@repo/shared/payload-types"
6-
import { formatDateToISOString, isSameDate, parseISOStringToDate } from "@repo/shared/utils/date"
6+
import {
7+
formatDateToISOString,
8+
getDateTimeStatus,
9+
isSameDate,
10+
parseISOStringToDate,
11+
} from "@repo/shared/utils/date"
712
import {
813
AdminGameSessionCard,
914
AdminSessionsCalendar,
@@ -56,15 +61,7 @@ const transformToAdminGameSession = (session: GameSessionWithCounts): AdminGameS
5661
]
5762

5863
// Determine session status based on current time
59-
const now = new Date()
60-
let status: GameSessionStatus
61-
if (now < new Date(session.openTime)) {
62-
status = GameSessionStatus.UPCOMING
63-
} else if (now > new Date(session.endTime)) {
64-
status = GameSessionStatus.PAST
65-
} else {
66-
status = GameSessionStatus.ONGOING
67-
}
64+
const status: GameSessionStatus = getDateTimeStatus(session.openTime, session.endTime)
6865

6966
return {
7067
...session,

biome.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"files": {
44
"includes": [
55
"**",
6+
"**/*.config.ts",
67
"!**/src/payload-types.ts",
78
"!pnpm-lock.yaml",
89
"!**/*.md",

packages/shared/src/utils/date.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { format } from "date-fns"
22
import { getWeekdayFromDayIndex } from "../constants"
33
import type { Semester } from "../payload-types"
4-
import { Weekday } from "../types"
4+
import { GameSessionStatus, Weekday } from "../types"
55
import { dayjs } from "./dayjs"
66

77
/**
@@ -181,3 +181,26 @@ export function parseISOStringToDate(dateString: string): Date | undefined {
181181
export function formatDateToString(date: Date): string {
182182
return dayjs(date).format("YYYY-MM-DD")
183183
}
184+
185+
/**
186+
* Method used to get the status of a date
187+
*
188+
* @param startTime The start time to check the status for
189+
* @param endTime The end time to check the status for
190+
* @returns The date {@link GameSessionStatus}
191+
*/
192+
export function getDateTimeStatus(startTime: string, endTime: string): GameSessionStatus {
193+
const now = new Date()
194+
195+
let status: GameSessionStatus
196+
197+
if (now < new Date(startTime)) {
198+
status = GameSessionStatus.UPCOMING
199+
} else if (now > new Date(endTime)) {
200+
status = GameSessionStatus.PAST
201+
} else {
202+
status = GameSessionStatus.ONGOING
203+
}
204+
205+
return status
206+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { gameSessionScheduleMock, semesterMock } from "@repo/shared/mocks"
2+
import type { Meta, StoryObj } from "@storybook/react"
3+
import type { ComponentProps } from "react"
4+
import { AdminSemestersAccordion } from "./AdminSemestersAccordion"
5+
6+
type AdminSemestersAccordionArgs = ComponentProps<typeof AdminSemestersAccordion>
7+
8+
const meta: Meta<AdminSemestersAccordionArgs> = {
9+
title: "Composite Components / AdminSemestersAccordion",
10+
component: AdminSemestersAccordion,
11+
argTypes: {
12+
onAddSchedule: { action: "add-schedule" },
13+
onEditSchedule: { action: "edit-schedule" },
14+
onDeleteSchedule: { action: "delete-schedule" },
15+
onEditSemester: { action: "edit-semester" },
16+
onDeleteSemester: { action: "delete-semester" },
17+
},
18+
}
19+
20+
export default meta
21+
type Story = StoryObj<AdminSemestersAccordionArgs>
22+
23+
export const Default: Story = {
24+
render: (args) => (
25+
<AdminSemestersAccordion
26+
onAddSchedule={args.onAddSchedule}
27+
onDeleteSchedule={args.onDeleteSchedule}
28+
onDeleteSemester={args.onDeleteSemester}
29+
onEditSchedule={args.onEditSchedule}
30+
onEditSemester={args.onEditSemester}
31+
rows={[gameSessionScheduleMock, gameSessionScheduleMock, gameSessionScheduleMock]}
32+
semester={semesterMock}
33+
/>
34+
),
35+
}
36+
37+
export const Expanded: Story = {
38+
render: (args) => (
39+
<AdminSemestersAccordion
40+
defaultExpanded
41+
onAddSchedule={args.onAddSchedule}
42+
onDeleteSchedule={args.onDeleteSchedule}
43+
onDeleteSemester={args.onDeleteSemester}
44+
onEditSchedule={args.onEditSchedule}
45+
onEditSemester={args.onEditSemester}
46+
rows={[gameSessionScheduleMock, gameSessionScheduleMock, gameSessionScheduleMock]}
47+
semester={semesterMock}
48+
/>
49+
),
50+
}
51+
52+
export const MultipleSemesters: Story = {
53+
render: (args) => (
54+
<>
55+
<AdminSemestersAccordion
56+
defaultExpanded
57+
onAddSchedule={args.onAddSchedule}
58+
onDeleteSchedule={args.onDeleteSchedule}
59+
onDeleteSemester={args.onDeleteSemester}
60+
onEditSchedule={args.onEditSchedule}
61+
onEditSemester={args.onEditSemester}
62+
rows={[gameSessionScheduleMock, gameSessionScheduleMock, gameSessionScheduleMock]}
63+
semester={semesterMock}
64+
/>
65+
<AdminSemestersAccordion
66+
onAddSchedule={args.onAddSchedule}
67+
onDeleteSchedule={args.onDeleteSchedule}
68+
onDeleteSemester={args.onDeleteSemester}
69+
onEditSchedule={args.onEditSchedule}
70+
onEditSemester={args.onEditSemester}
71+
rows={[gameSessionScheduleMock, gameSessionScheduleMock]}
72+
semester={{ ...semesterMock, id: "semester-2", name: "Semester 2 2025" }}
73+
/>
74+
</>
75+
),
76+
}

0 commit comments

Comments
 (0)