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:
+6
-6
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user