refactor: replace PlacesView with FacilitiesView in MembershipsView and remove PlacesView component

feat: add RfidTagsTab component for managing RFID tags

refactor: update auth API endpoints to use unified auth endpoint

feat: enhance bookings API with AdHoc booking functionality and new booking management features

refactor: update facilities API to include device assignment and availability checks

delete: remove obsolete places API and model definitions

feat: introduce RFID tag management API and model definitions
This commit is contained in:
2026-09-29 13:02:16 +05:30
parent a1b870fa8a
commit 23ae7b39d0
18 changed files with 1906 additions and 915 deletions
+6 -6
View File
@@ -3,10 +3,10 @@ import { setToken } from "@/lib/auth/token-service"
import type { LoginRequest, LoginResult } from "@/lib/models/auth"
async function login(request: LoginRequest): Promise<LoginResult> {
const data = await authRequest<unknown>("loginUser", {
const data = await authRequest<unknown>("auth", {
method: "POST",
body: {
functionName: "loginUser",
functionName: "LoginUser",
payload: {
identifier: request.identifier,
password: request.password,
@@ -28,7 +28,7 @@ async function login(request: LoginRequest): Promise<LoginResult> {
/** Step 1 of forgot-password: sends an OTP to the account's email/phone. */
async function forgotPassword(identifier: string): Promise<{ referenceNumber: string }> {
const data = await authRequest<unknown>("forgotPassword", {
const data = await authRequest<unknown>("auth", {
method: "POST",
body: {
functionName: "forgotPassword",
@@ -45,7 +45,7 @@ async function forgotPassword(identifier: string): Promise<{ referenceNumber: st
* the generic recovery RPC endpoint (auth-service dispatches by functionName), same as
* resetPassword/resetPasswordWithToken below. */
async function verifyResetOtp(referenceNumber: string, otpCode: string): Promise<void> {
await authRequest<unknown>("recovery", {
await authRequest<unknown>("auth", {
method: "POST",
body: {
functionName: "VerifyOTP",
@@ -61,7 +61,7 @@ async function resetPassword(
newPassword: string,
confirmPassword: string
): Promise<void> {
await authRequest<unknown>("recovery", {
await authRequest<unknown>("auth", {
method: "POST",
body: {
functionName: "resetPassword",
@@ -77,7 +77,7 @@ async function resetPasswordWithToken(
newPassword: string,
confirmPassword: string
): Promise<void> {
await authRequest<unknown>("recovery", {
await authRequest<unknown>("auth", {
method: "POST",
body: {
functionName: "resetPasswordWithToken",
+55 -3
View File
@@ -1,6 +1,20 @@
import { apiRequest } from "@/lib/api/core-http"
import type { Booking, CreateGuestBookingInput } from "@/lib/models/booking"
import type {
Booking,
CreateAdhocBookingInput,
CreateGuestAdhocBookingInput,
CreateGuestBookingInput,
GetBookingsFilters,
ReassignBookingInput,
RescheduleBookingInput,
} from "@/lib/models/booking"
/** Unified roster across both Scheduled and AdHoc bookings. */
function getBookings(filters: GetBookingsFilters = {}) {
return apiRequest<Booking[]>("client/bookings", { query: filters })
}
/** Kept for the Scheduled-session roster view; prefer getBookings({ facilitySessionId }) elsewhere. */
function getSessionBookings(facilitySessionId: string) {
return apiRequest<Booking[]>(`client/bookings/session/${facilitySessionId}`)
}
@@ -10,8 +24,21 @@ function createGuestBooking(input: CreateGuestBookingInput) {
return apiRequest<Booking>("public/bookings/guest", { method: "POST", body: input })
}
function checkInBooking(otpCode: string) {
return apiRequest<Booking>("client/bookings/check-in", { method: "POST", body: { otpCode } })
/** Member self-service AdHoc booking. */
function createAdhocBooking(input: CreateAdhocBookingInput) {
return apiRequest<Booking>("app/bookings/adhoc", { method: "POST", body: input })
}
/** Staff-initiated walk-in AdHoc booking — same public endpoint the guest AdHoc flow uses. */
function createGuestAdhocBooking(input: CreateGuestAdhocBookingInput) {
return apiRequest<Booking>("public/bookings/guest/adhoc", { method: "POST", body: input })
}
function checkInBooking(otpCode: string, rfidTagId?: string) {
return apiRequest<Booking>("client/bookings/check-in", {
method: "POST",
body: { otpCode, rfidTagId },
})
}
function markGuestBookingPaid(id: string, amountCharged: number) {
@@ -25,10 +52,35 @@ function cancelBooking(id: string) {
return apiRequest<Booking>(`client/bookings/${id}/cancel`, { method: "POST" })
}
/** Guest-only — hands a Confirmed booking to a different name/phone before check-in. */
function reassignBooking(id: string, input: ReassignBookingInput) {
return apiRequest<Booking>(`client/bookings/${id}/reassign`, { method: "PUT", body: input })
}
/** Moves a Confirmed booking to a new time (AdHoc) or a new session (Scheduled) — only onto a free slot. */
function rescheduleBooking(id: string, input: RescheduleBookingInput) {
return apiRequest<Booking>(`client/bookings/${id}/reschedule`, { method: "PUT", body: input })
}
function markNoShow(id: string) {
return apiRequest<Booking>(`client/bookings/${id}/no-show`, { method: "POST" })
}
function completeBooking(id: string) {
return apiRequest<Booking>(`client/bookings/${id}/complete`, { method: "POST" })
}
export {
getBookings,
getSessionBookings,
createGuestBooking,
createAdhocBooking,
createGuestAdhocBooking,
checkInBooking,
markGuestBookingPaid,
cancelBooking,
reassignBooking,
rescheduleBooking,
markNoShow,
completeBooking,
}
+15
View File
@@ -1,6 +1,7 @@
import { apiRequest } from "@/lib/api/core-http"
import type {
Facility,
FacilityAvailability,
FacilityInput,
FacilitySession,
FacilitySessionInput,
@@ -25,6 +26,14 @@ function setFacilityStatus(id: string, isActive: boolean) {
})
}
/** Absorbed from the old PlaceService.AssignDevice — null unassigns the facility's current device. */
function assignFacilityDevice(id: string, deviceId: string | null) {
return apiRequest<Facility>(`client/facilities/${id}/device`, {
method: "PUT",
body: { deviceId },
})
}
function getFacilitySessions(params: { facilityId?: string; fromDate?: string; toDate?: string } = {}) {
return apiRequest<FacilitySession[]>("client/facilities/sessions", { query: params })
}
@@ -37,12 +46,18 @@ function cancelFacilitySession(id: string) {
return apiRequest<unknown>(`client/facilities/sessions/${id}`, { method: "DELETE" })
}
function getFacilityAvailability(params: { facilityId?: string; startTime: string; durationMinutes: number }) {
return apiRequest<FacilityAvailability[]>("client/facilities/availability", { query: params })
}
export {
getFacilities,
createFacility,
updateFacility,
setFacilityStatus,
assignFacilityDevice,
getFacilitySessions,
createFacilitySession,
cancelFacilitySession,
getFacilityAvailability,
}
-30
View File
@@ -1,30 +0,0 @@
import { apiRequest } from "@/lib/api/core-http"
import type { Place, PlaceInput } from "@/lib/models/place"
function getPlaces(includeInactive = true) {
return apiRequest<Place[]>("client/places", { query: { includeInactive } })
}
function createPlace(input: PlaceInput) {
return apiRequest<Place>("client/places", { method: "POST", body: input })
}
function updatePlace(id: string, input: Partial<PlaceInput>) {
return apiRequest<Place>(`client/places/${id}`, { method: "PUT", body: input })
}
function setPlaceStatus(id: string, isActive: boolean) {
return apiRequest<Place>(`client/places/${id}/status`, {
method: "PATCH",
body: { isActive },
})
}
function assignPlaceDevice(id: string, deviceId: string | null) {
return apiRequest<Place>(`client/places/${id}/device`, {
method: "PUT",
body: { deviceId },
})
}
export { getPlaces, createPlace, updatePlace, setPlaceStatus, assignPlaceDevice }
+21
View File
@@ -0,0 +1,21 @@
import { apiRequest } from "@/lib/api/core-http"
import type { RfidTag, RfidTagInput, RfidTagStatus } from "@/lib/models/rfid-tag"
function getRfidTags(status?: RfidTagStatus) {
return apiRequest<RfidTag[]>("client/rfid-tags", { query: status ? { status } : {} })
}
function createRfidTag(input: RfidTagInput) {
return apiRequest<RfidTag>("client/rfid-tags", { method: "POST", body: input })
}
function updateRfidTag(id: string, input: Partial<RfidTagInput>) {
return apiRequest<RfidTag>(`client/rfid-tags/${id}`, { method: "PUT", body: input })
}
/** Available/Lost/Retired only — InUse is only ever reached via a booking check-in. */
function setRfidTagStatus(id: string, status: RfidTagStatus) {
return apiRequest<RfidTag>(`client/rfid-tags/${id}/status`, { method: "PATCH", body: { status } })
}
export { getRfidTags, createRfidTag, updateRfidTag, setRfidTagStatus }
+55 -5
View File
@@ -1,10 +1,18 @@
/** Mirrors BookingDto. */
export type BookingStatus = "Confirmed" | "CheckedIn" | "Cancelled" | "NoShow" | "Completed"
/** Mirrors BookingDto. Covers both Scheduled (facilitySessionId set) and AdHoc
* (facilitySessionId null, exclusive-unit) bookings. */
export interface Booking {
bookingId: string
facilitySessionId: string
/** Null for an AdHoc booking — no pre-created session. */
facilitySessionId?: string | null
facilityId: string
facilityName?: string | null
sessionStartTime?: string | null
sessionEndTime?: string | null
bookingMode: "Scheduled" | "AdHoc"
startTime: string
endTime: string
/** Informational headcount, AdHoc only. */
partySize?: number | null
isGuest: boolean
customerId?: string | null
@@ -15,18 +23,60 @@ export interface Booking {
otpCode?: string | null
otpVerified: boolean
rfidTagId?: string | null
rfidTagLabel?: string | null
rfidAssignedAt?: string | null
rfidReturnedAt?: string | null
paymentStatus: string
amountCharged: number
status: string
status: BookingStatus
bookedAt: string
checkedInAt?: string | null
checkedOutAt?: string | null
/** Public, unauthenticated link to this booking's confirmation page. */
shareUrl?: string | null
}
export type GetBookingsFilters = {
facilityId?: string
facilitySessionId?: string
fromDate?: string
toDate?: string
status?: string
}
export interface CreateGuestBookingInput {
facilitySessionId: string
guestName: string
guestPhone: string
}
export interface CreateAdhocBookingInput {
facilityId: string
startTime: string
durationMinutes: number
partySize?: number
}
export interface CreateGuestAdhocBookingInput {
facilityId: string
startTime: string
durationMinutes: number
guestName: string
guestPhone: string
partySize?: number
}
export interface ReassignBookingInput {
guestName?: string
guestPhone?: string
}
/** Exactly one shape applies, matching the booking's current bookingMode. */
export interface RescheduleBookingInput {
startTime?: string
durationMinutes?: number
facilitySessionId?: string
}
+24 -4
View File
@@ -1,21 +1,31 @@
export type FacilityType = "Gym" | "Pool" | "Court" | "Class"
export type BookingMode = "Scheduled" | "AdHoc"
/** Mirrors FacilityDto. */
/** Mirrors FacilityDto. A bookable area (gym floor, pool, court, class) that's also the
* access-control zone for its optional fingerprint/face terminal — merged from the former
* separate Facility/Place concepts. */
export interface Facility {
facilityId: string
name: string
facilityType: FacilityType
bookingMode: BookingMode
description?: string | null
/** Meaningful when bookingMode === "Scheduled" — shared capacity per session. */
defaultCapacity: number
/** Meaningful when bookingMode === "AdHoc" — e.g. 3 for "3 courts", each independently bookable. */
numberOfUnits: number
pricePerHour: number
isActive: boolean
createdAt: string
deviceId?: string | null
deviceName?: string | null
}
export interface FacilityInput {
name: string
facilityType: FacilityType
bookingMode: BookingMode
description?: string
defaultCapacity: number
numberOfUnits: number
pricePerHour: number
}
/** Mirrors FacilitySessionDto. */
@@ -37,3 +47,13 @@ export interface FacilitySessionInput {
/** Overrides the facility's default capacity for this specific session. */
capacity?: number
}
/** Mirrors FacilityAvailabilityDto. */
export interface FacilityAvailability {
facilityId: string
facilityName: string
bookingMode: BookingMode
totalUnits: number
unitsFree: number
pricePerHour: number
}
-16
View File
@@ -1,16 +0,0 @@
/** Mirrors PlaceDto. A physical access zone (e.g. "Pool Area", "Indoor Gym") with one
* fingerprint/face terminal for now. */
export interface Place {
placeId: string
name: string
description?: string | null
isActive: boolean
createdAt: string
deviceId?: string | null
deviceName?: string | null
}
export interface PlaceInput {
name: string
description?: string
}
+17
View File
@@ -0,0 +1,17 @@
export type RfidTagStatus = "Available" | "InUse" | "Lost" | "Retired"
/** Mirrors RfidTagDto. A managed inventory tag handed to a booked party at check-in
* (Booking.rfidTagId) — staff pick from this list, never free-type a tag. */
export interface RfidTag {
rfidTagId: string
tagCode: string
label?: string | null
status: RfidTagStatus
createdAt: string
updatedAt: string
}
export interface RfidTagInput {
tagCode: string
label?: string
}