first commit

This commit is contained in:
Dhananjaya99
2026-09-28 11:48:14 +05:30
commit a1b870fa8a
242 changed files with 39483 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
/** Mirrors the tenant-scoped response from GET client/access-devices. */
export interface AccessDevice {
deviceId: string
deviceName: string
isActive: boolean
lastSeenAt?: string | null
isOnline: boolean
presenceExpiresAt?: string | null
isClaimed: boolean
createdAt: string
}
export interface AccessAgent {
agentId: string
name: string
isActive: boolean
lastSeenAt?: string | null
isOnline: boolean
createdAt: string
}
export interface ClientAccessDevices {
agents: AccessAgent[]
devices: AccessDevice[]
}
+5
View File
@@ -0,0 +1,5 @@
export interface ApiResponse<T> {
statusCode: number
message: string
data: T
}
+31
View File
@@ -0,0 +1,31 @@
/** Attendance entity rows from GET client/attendance. */
export interface AttendanceRecord {
attendanceId: string
fkCustomerId?: string
fkUserId?: string
fkAccessProfileId?: number
deviceUserId: number
eventTime: string
eventType: string
customer?: { cusId: string; cusName: string } | null
user?: { userId: string; userName?: string | null } | null
accessProfile?: {
accessProfileId: number
displayName: string
personType: string
} | null
}
/** Mirrors TodayAttendanceDTO. */
export interface TodayAttendance {
customerName: string
eventTime: string
eventType?: string | null
}
/** Mirrors AttendanceCountDTO. */
export interface AttendanceCount {
customerId: string
customerName: string
attendanceCount: number
}
+12
View File
@@ -0,0 +1,12 @@
export interface LoginRequest {
identifier: string
password: string
userTypeId?: string
deviceName?: string
rememberMe?: boolean
}
export interface LoginResult {
accessToken: string | null
refreshToken: string | null
}
+32
View File
@@ -0,0 +1,32 @@
/** Mirrors BookingDto. */
export interface Booking {
bookingId: string
facilitySessionId: string
facilityName?: string | null
sessionStartTime?: string | null
sessionEndTime?: string | null
isGuest: boolean
customerId?: string | null
guestName?: string | null
guestPhone?: string | null
/** Only populated right after booking creation — the guest's receipt. */
otpCode?: string | null
otpVerified: boolean
paymentStatus: string
amountCharged: number
status: string
bookedAt: string
checkedInAt?: string | null
/** Public, unauthenticated link to this booking's confirmation page. */
shareUrl?: string | null
}
export interface CreateGuestBookingInput {
facilitySessionId: string
guestName: string
guestPhone: string
}
+171
View File
@@ -0,0 +1,171 @@
import type { GymPlan } from "@/lib/models/gym-plan"
import type { PaymentStatus, SubscriptionSummary } from "@/lib/models/subscription"
/** One weekly weigh-in (CusWeightLossProgress). */
export interface WeightProgressPoint {
weeklyProgressId: string
weight?: number | null
recordedAt: string
}
/** A member's goal/measurements (CustomerTarget) with their weight history. */
export interface CustomerTarget {
customerTargetId: string
currentWeight?: number | null
currentHeight?: number | null
targetWeight?: number | null
goal?: string | null
timePeriod?: string | null
weeklyProgresses?: WeightProgressPoint[] | null
}
/** Customer entity as serialized by the backend (camelCase of the EF model). */
export interface MemberProfile {
cusId: string
cusName?: string | null
gender?: string | null
/** Date of birth, "yyyy-MM-dd". Backend property is BoD. */
boD?: string | null
primaryPH?: string | null
secondaryPH?: string | null
email?: string | null
nic?: string | null
address?: string | null
fkUserId?: string
isActive: boolean
createdAt: string
isLocked?: boolean
lockReason?: string | null
lockedAt?: string | null
customerTargets?: CustomerTarget[] | null
user?: {
userId: string
userName?: string | null
email?: string | null
phoneNumber?: string | null
} | null
}
/** A workout/meal plan the member is assigned to. */
export interface FollowedPlan {
planId: string
name?: string | null
isFollowing: boolean
completedDate?: string | null
}
/** GET client/customers/{id}/full — the "More Info" aggregate. */
export interface MemberFullDetail {
customer: MemberProfile
subscriptions?: SubscriptionSummary[]
workoutPlans: FollowedPlan[]
mealPlans: FollowedPlan[]
}
/** POST client/customers/{id}/medical (password-gated). */
export interface MedicalDetails {
bloodGroup?: string | null
disabilities?: string | null
}
/**
* Row shape of GET client/customers (MemberListRowDto): one row per member.
* Group members each get a row pointing at their shared subscription.
*/
export interface MemberSubscription {
subscriptionId: string
fkCustomerPlanId: string
fkCustomerId: string
onboardDate: string
/** Frozen at creation, never changes again -- use renewalDate for current status/listing. */
expiryDate?: string | null
renewalDate?: string | null
isActive: boolean
createdAt: string
customer?: MemberProfile | null
isPrimary: boolean
/** False when unassigned from the plan but still a member of the gym — re-assignable. */
isPlanAssigned?: boolean
planName?: string | null
/** "None" means this member was created without a plan yet — assign one to make them active. */
planScope?: "Individual" | "Group" | "None"
memberCount?: number
memberLimit?: number
invoiceAmount?: number
paidAmount?: number
balance?: number
paymentStatus?: PaymentStatus
allowSplitPayments?: boolean
fingerprintId?: number | null
fingerprintName?: string | null
accessProfileId?: number | null
accessNumber?: string | null
accessProvisionStatus?: "NotConfigured" | "NoDevice" | "Pending" | "Synced" | "Failed"
actualAccessState?: "Unknown" | "Enabled" | "Disabled" | "Mixed"
desiredAccessState?: "Enabled" | "Disabled"
fingerprintCount?: number
accessLastError?: string | null
isLocked?: boolean
lockReason?: string | null
/** Face-photo enrolment on the gym's access terminals. */
hasFacePhoto?: boolean
facePhotoStatus?: "None" | "Pending" | "Synced" | "Failed"
facePhotoError?: string | null
}
/** GET client/customers/unlinked-fingerprints — a device enrollment not yet linked to a Customer. */
export interface UnlinkedFingerprint {
id: string
deviceUserId: number
deviceEmployeeNo: string
deviceReportedName?: string | null
deviceUserType?: string | null
fingerprintCount: number
deviceId: string
deviceName?: string | null
discoveredAt: string
}
export interface AccessCommandStatus {
accessProfileId: number
accessNumber: string
personType: "Customer" | "Staff"
desiredAccessState: "Enabled" | "Disabled"
provisionStatus: "NoDevice" | "Pending" | "Synced" | "Failed"
actualAccessState: "Unknown" | "Enabled" | "Disabled" | "Mixed"
fingerprintCount: number
lastError?: string | null
lastSyncedAt?: string | null
deviceCount: number
syncedDeviceCount: number
}
/** GET client/customers/{id} response. */
export interface MemberDetail {
customer: MemberProfile
plans: GymPlan[]
subscriptions?: SubscriptionSummary[]
}
export interface MemberInput {
fullname: string
userName: string
email: string
mobileNumber: string
gender: string
/** "yyyy-MM-dd" */
birthday: string
nic?: string
customerPlanId?: string
workoutPlanIds?: string[]
mealPlanIds?: string[]
/** ISO datetime. Only meaningful alongside customerPlanId. */
onboardDate?: string
/** false bills the plan at Rs. 0.00 and skips the automatic payment link. */
chargePayment?: boolean
discountType?: "Percentage" | "Flat"
discountValue?: number
/** Set true only on the resubmit after staff has confirmed linking an existing platform
* customer to this gym (see CreateMemberResult.requiresConfirmation). */
confirmLink?: boolean
}
+39
View File
@@ -0,0 +1,39 @@
export type FacilityType = "Gym" | "Pool" | "Court" | "Class"
/** Mirrors FacilityDto. */
export interface Facility {
facilityId: string
name: string
facilityType: FacilityType
description?: string | null
defaultCapacity: number
isActive: boolean
createdAt: string
}
export interface FacilityInput {
name: string
facilityType: FacilityType
description?: string
defaultCapacity: number
}
/** Mirrors FacilitySessionDto. */
export interface FacilitySession {
facilitySessionId: string
facilityId: string
facilityName?: string | null
startTime: string
endTime: string
capacity: number
bookedCount: number
isCancelled: boolean
}
export interface FacilitySessionInput {
facilityId: string
startTime: string
endTime: string
/** Overrides the facility's default capacity for this specific session. */
capacity?: number
}
+38
View File
@@ -0,0 +1,38 @@
/** Item shape returned by GET client/feed (ShowFeedByClient). */
export interface FeedPost {
feedPostId: string
caption?: string | null
description?: string | null
userId: string
docId?: number | null
isActive: boolean
createdAt: string
likeCount: number
document?: {
fileName?: string | null
fileType?: string | null
documentUrl?: string | null
documentType?: string | null
/** Base64 file content. */
data?: string | null
} | null
}
/** Inner wrapper FeedService puts around the list. */
export interface FeedListResult {
success: boolean
message: string
data: FeedPost[]
}
export interface FeedDocumentInput {
FileName: string
/** Base64 file content (DocumentService reads the `Base64` key). FeedService injects FileType. */
Base64: string
}
export interface CreateFeedInput {
caption?: string
description?: string
document: FeedDocumentInput[]
}
+49
View File
@@ -0,0 +1,49 @@
export type PlanScope = "Individual" | "Group"
export type DurationMode = "Rolling" | "FixedRange"
/** Mirrors CustomerPlanDto (gym membership plans). */
export interface GymPlan {
customerPlanId: string
fkClientId: string
clientName?: string | null
planName: string
planDescription?: string | null
planPrice: number
hasOnboardingFee: boolean
onboardingFeeAmount: number
durationInDays: number
planScope: PlanScope
memberLimit: number
durationMode: DurationMode
validFrom?: string | null
validTo?: string | null
allowSplitPayments: boolean
installmentCount: number
isActive: boolean
createdAt: string
/** Places this plan grants access to. Empty = unrestricted (every active device). */
placeIds: string[]
placeNames: string[]
}
export interface GymPlanInput {
planName: string
planDescription?: string
planPrice: number
hasOnboardingFee?: boolean
onboardingFeeAmount?: number
/** Required for Rolling plans; omit for FixedRange. */
durationInDays?: number
planScope?: PlanScope
/** Required (>= 2) for Group plans. */
memberLimit?: number
durationMode?: DurationMode
/** ISO dates; required for FixedRange plans. */
validFrom?: string
validTo?: string
allowSplitPayments?: boolean
/** Number of mostly-equal installments when split payments are enabled. */
installmentCount?: number
/** Replaces the plan's place set when provided; omit to leave unchanged, [] to clear. */
placeIds?: string[]
}
+20
View File
@@ -0,0 +1,20 @@
/** Mirrors GymRoleDto. */
export interface GymRole {
gymRoleId: string
name: string
isActive: boolean
navKeys: string[]
}
export interface CreateGymRoleInput {
name: string
}
export interface UpdateGymRoleInput {
name?: string
isActive?: boolean
}
export interface SetRolePermissionsInput {
navKeys: string[]
}
+36
View File
@@ -0,0 +1,36 @@
import type { MemberProfile } from "@/lib/models/customer"
import type { GymPlan } from "@/lib/models/gym-plan"
/** CustomerInvoice entity from GET client/member-invoices (member billing). */
export interface MemberInvoice {
customerInvoiceId: string
fkCustomerSubscriptionId: string
fkClientId: string
amount: number
/** The portion of `amount` carried forward from a prior cycle's unpaid balance (renewal
* approval flow), rather than this cycle's own charge. 0/undefined when there's none --
* only populated by the renewal-approvals list and the single-invoice detail endpoint. */
carriedForwardAmount?: number
status: string
/** Approval gate for the renewal-approval workflow: false means this invoice was
* auto-generated by the daily renewal sweep and is awaiting approve/change-plan/reject.
* Independent of `status`. */
isActive: boolean
paidAmount?: number
balance?: number
customerInvoiceDate: string
/** Flattened from CustomerSubscription.TenantCustomerLink -- use this for display, not
* customerSubscription.customer, which the backend never actually populates. */
customerName?: string | null
customerPhone?: string | null
/** TenantCustomerLink id -- used for "view previous invoices" and latest-per-customer grouping. */
fkCustomerId?: string | null
customerSubscription?: {
subscriptionId: string
onboardDate: string
expiryDate?: string | null
renewalDate?: string | null
customer?: MemberProfile | null
customerPlan?: GymPlan | null
} | null
}
+37
View File
@@ -0,0 +1,37 @@
export type LedgerEntryType = "Income" | "Expense"
/** Mirrors LedgerEntryDto — one row in the P&L breakdown (derived or manual). */
export interface LedgerEntry {
id?: string | null
entryType: LedgerEntryType
category: string
description?: string | null
amount: number
entryDate: string
isManual: boolean
}
export interface CategoryTotal {
category: string
amount: number
}
/** GET client/reports/profit-loss. */
export interface ProfitLoss {
from: string
to: string
totalIncome: number
totalExpense: number
netProfit: number
incomeByCategory: CategoryTotal[]
expenseByCategory: CategoryTotal[]
entries: LedgerEntry[]
}
export interface AddLedgerEntryInput {
entryType: LedgerEntryType
category: string
description?: string
amount: number
entryDate?: string
}
+3
View File
@@ -0,0 +1,3 @@
export interface LockPolicy {
lockAfterDaysOverdue: number | null
}
+12
View File
@@ -0,0 +1,12 @@
export interface NavItemApi {
key: string
label: string
shortLabel?: string
href: string
icon: string
}
export interface NavResponse {
isOwner: boolean
items: NavItemApi[]
}
+17
View File
@@ -0,0 +1,17 @@
/** GET client/pay/{token} — public pay-by-link invoice summary. */
export interface PayByLinkInvoice {
memberName?: string | null
gymName?: string | null
planName?: string | null
amount: number
balance: number
currency: string
}
export interface PayByLinkResult {
success: boolean
invoiceId: string
paidAmount: number
balance: number
status: string
}
+16
View File
@@ -0,0 +1,16 @@
/** 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
}
+69
View File
@@ -0,0 +1,69 @@
/** Shared shapes for workout & meal plans (mirrors WorkoutPlanDto / MealPlanDto). */
export interface AssignedCustomer {
/** customerWorkoutId or customerMealId — the id UnassignWorkout/UnassignMeal expects. */
assignmentId: string
customerId: string
customerName?: string | null
isActive: boolean
assignedDate: string
}
export interface TrainingPlan {
/** workoutPlanId or mealPlanId, normalized to `planId` at the API layer. */
planId: string
name: string
description?: string | null
isTemplate: boolean
categoryId: string
categoryName?: string | null
clientId?: string | null
clientName?: string | null
isActive: boolean
createDate: string
assignedCustomers: AssignedCustomer[]
}
export interface PlanCategory {
categoryId: string
categoryName: string
description?: string | null
}
export interface PlanItemRow {
/** Day label for workouts (e.g. "Monday"); free text for meals ("Breakfast"). */
label: string
detail: string
}
/** One structured exercise within a workout day (mirrors WorkoutExerciseDto). */
export interface WorkoutExerciseView {
id: string
name: string
muscleGroup?: string | null
sets?: number | null
reps?: string | null
description?: string | null
breathIn?: string | null
breathOut?: string | null
videoUrl?: string | null
}
/** A saved plan item, normalized for the detail panel (one card per item). */
export interface PlanItemView {
id: string
/** exerciseDay (workout) or meal name (meal). */
title: string
/** exercises list (workout) or description (meal). */
detail: string
/** Structured exercises (workout only) — empty for meal items. */
exercises: WorkoutExerciseView[]
}
export interface CreatePlanInput {
name: string
description?: string
categoryId: string
isTemplate?: boolean
customerIds?: string[]
}
+27
View File
@@ -0,0 +1,27 @@
/** One equipment type + how many the gym has (e.g. Treadmill × 10). */
export interface GymEquipment {
name: string
count: number
}
/** Gym business record attached to a client (from GET client/me). */
export interface ClientBusiness {
bid: string
businessName?: string | null
address?: string | null
city?: string | null
description?: string | null
businessLogoId?: string | null
/** JSON blob: { hasAc, equipment: [{ name, count }] } — parse with parseFacilities(). */
facilities?: string | null
isActive: boolean
}
/** Mirrors the Client entity returned by GET client/me. */
export interface ClientProfile {
clientId: string
clientName?: string | null
clientPhone?: string | null
clientEmail?: string | null
clientBusinesses?: ClientBusiness[] | null
}
+67
View File
@@ -0,0 +1,67 @@
/** Mirrors StaffDto. */
export interface StaffMember {
staffId: string
userId: string
userName?: string | null
email?: string | null
phoneNumber?: string | null
clientId: string
clientName?: string | null
gymRoleId?: string | null
gymRoleName?: string | null
staffPosition?: string | null
department?: string | null
hireDate: string
isActive: boolean
createdAt: string
fingerprintId?: number | null
fingerprintName?: string | null
accessProfileId?: number | null
accessNumber?: string | null
accessProvisionStatus?: "NotConfigured" | "NoDevice" | "Pending" | "Synced" | "Failed"
actualAccessState?: "Unknown" | "Enabled" | "Disabled" | "Mixed"
desiredAccessState?: "Enabled" | "Disabled"
fingerprintCount?: number
accessLastError?: string | null
}
export interface StaffInput {
userName: string
email: string
phoneNumber: string
staffPosition?: string
department?: string
/** "yyyy-MM-dd" — create only. */
hireDate?: string
/** Create only. Left unset, the new staff member has no dashboard access until a role
* is assigned later via Roles & Permissions. */
gymRoleId?: string
}
export type StaffRateType = "Monthly" | "Hourly"
/** Mirrors StaffPayRateDto. */
export interface StaffPayRate {
id: string
rateType: StaffRateType
amount: number
effectiveFrom: string
}
export interface UpsertStaffPayRateInput {
rateType: StaffRateType
amount: number
}
/** Mirrors StaffPaymentDto. */
export interface StaffPayment {
id: string
amount: number
note?: string | null
paidAt: string
}
export interface RecordStaffPaymentInput {
amount: number
note?: string
}
+55
View File
@@ -0,0 +1,55 @@
/** Mirrors ClientStatSummaryDto (camelCased by the backend). */
export interface ClientStatSummary {
totalCustomerCount: number
thisMonthCustomerCount: number
lastMonthCustomerCount: number
customerGrowthPercentage: number
/** These four are zeroed server-side for non-owner callers — revenue is owner-only. */
thisMonthRevenue: number
lastMonthRevenue: number
revenueGrowthPercentage: number
totalRevenue: number
overdueCount: number
expiringWithin7DaysCount: number
/** Always "today" (UTC date), independent of any reporting-period filter elsewhere on this
* object -- visible to every staff member, not owner-gated like revenue above. */
todayPaymentsCount: number
todayPaymentsTotal: number
/** Muscle Beast-only pilot -- gated server-side, so the UI never needs to know any gym's
* ClientId to decide whether to show the Today's Payments card/filter. */
showTodayPayments: boolean
}
/** Mirrors MonthlyRevenueStat. */
export interface MonthlyRevenueStat {
year: number
month: number
monthLabel: string
/** Zeroed server-side for non-owner callers — revenue is owner-only. */
revenue: number
newMemberCount: number
}
interface CustomerRef {
cusId: string
cusName: string
}
/** PaymentTransaction entity rows from GET client/stats/recent-payments. */
export interface RecentPayment {
attemptId: string
payDate: string | null
payStatus: string
payAmount: number
payNote?: string | null
customer?: CustomerRef | null
}
/** Attendance entity rows from GET client/stats/recent-attendance. */
export interface RecentAttendance {
attendanceId: string
eventTime: string
eventType: string
deviceUserId: number
customer?: CustomerRef | null
}
+116
View File
@@ -0,0 +1,116 @@
export type PaymentStatus = "Pending" | "Partial" | "Paid"
export interface SubscriptionMemberInfo {
subscriptionMemberId: string
customerId: string
cusName?: string | null
email?: string | null
primaryPH?: string | null
isPrimary: boolean
joinedAt: string
isActive: boolean
/** False when unassigned from the plan but still a member of the gym. */
isPlanAssigned?: boolean
}
export interface SubscriptionInstallment {
subscriptionInstallmentId: string
installmentNo: number
dueDate: string
amount: number
paidAmount: number
status: PaymentStatus
}
export interface AutoChargeCard {
cardId: string
cardLast4?: string | null
cardType?: string | null
}
export interface MemberCard {
jobId: string
cardId?: string | null
cardNumber?: string | null
cardType?: string | null
nickName?: string | null
isPrimary: boolean
consentGiven: boolean
}
/** GET client/subscriptions/{id}. */
export interface SubscriptionSummary {
subscriptionId: string
customerPlanId: string
planName?: string | null
planScope: "Individual" | "Group"
memberLimit: number
durationMode: "Rolling" | "FixedRange"
allowSplitPayments: boolean
planPrice: number
onboardDate: string
/** Frozen at creation, never changes again -- see renewalDate for the current cycle end. */
expiryDate?: string | null
renewalDate?: string | null
isActive: boolean
customerInvoiceId?: string | null
invoiceAmount: number
paidAmount: number
balance: number
paymentStatus: PaymentStatus
members: SubscriptionMemberInfo[]
installments: SubscriptionInstallment[]
autoChargeEnabled: boolean
autoChargeCard?: AutoChargeCard | null
}
export interface SubscriptionPayment {
attemptId: string
payDate?: string | null
payStatus: string
payAmount: number
payNote?: string | null
refType?: string | null
paymentMethod?: "Cash" | "Bank" | null
bankRefNo?: string | null
isApproved?: boolean | null
}
/** GET client/subscriptions/{id}/payments. */
export interface SubscriptionPaymentsResult {
invoiceId?: string | null
invoiceAmount: number
paidAmount: number
balance: number
status: PaymentStatus
installments: SubscriptionInstallment[]
payments: SubscriptionPayment[]
}
export interface RecordPaymentInput {
amount: number
note?: string
paymentMethod: "Cash" | "Bank"
/** Reference/slip number — only meaningful (and sent) when paymentMethod is "Bank". */
bankRefNo?: string
}
export type ExtraFeeFrequency = "OneTime" | "Recurring"
/** Mirrors CustomerSubItemDto — an optional extra fee on a subscription (e.g. coaching fee). */
export interface SubscriptionExtraFee {
customerSubItemId: string
subItemName?: string | null
subItemDescription?: string | null
subItemPrice: number
frequency: ExtraFeeFrequency
isActive: boolean
createdAt: string
}
export interface AddExtraFeeInput {
name: string
description?: string
amount: number
frequency: ExtraFeeFrequency
}
+46
View File
@@ -0,0 +1,46 @@
/** TicketStatusEnum: 1=Pending, 2=Open, 3=Closed (serialized as int). */
export type TicketStatus = 1 | 2 | 3
export const TICKET_STATUS_LABEL: Record<TicketStatus, string> = {
1: "Pending",
2: "Open",
3: "Closed",
}
/** Mirrors TicketDto (list rows). */
export interface Ticket {
ticketId: string
mainReason: string
createDate: string
userId: string
userName?: string | null
userEmail?: string | null
adminId?: string | null
adminName?: string | null
ticketCategoryId?: string | null
ticketCategoryName?: string | null
userRating?: number | null
ticketStatus: TicketStatus
}
/** Mirrors TicketMessageDto. */
export interface TicketMessage {
messageId: string
isSentByAdmin: boolean
message: string
attachmentURL?: string | null
createDate: string
}
/** Mirrors TicketDetailDto. */
export interface TicketDetail extends Ticket {
userPhoneNumber?: string | null
userFeedback?: string | null
feedbackAt?: string | null
messages: TicketMessage[]
}
export interface TicketCategory {
ticketCategoryId: string
ticketCategoryName: string
}
+34
View File
@@ -0,0 +1,34 @@
/** Mirrors TimeBankCardDto. */
export interface TimeBankCard {
timeBankCardId: string
rfidTagId: string
customerId?: string | null
totalMinutesPurchased: number
minutesRemaining: number
amountPaid: number
purchasedAt: string
expiresAt?: string | null
isActive: boolean
/** True right after a tap-in swipe (session open, clock running). */
sessionOpen: boolean
sessionStartedAt?: string | null
sessionFacilityName?: string | null
/** Only populated in the response to a tap-out swipe. */
minutesDeductedThisSwipe?: number | null
}
export interface IssueTimeBankCardInput {
rfidTagId: string
totalMinutesPurchased: number
amountPaid: number
/** Ties the card to a member's own account; omit for a walk-in-only card. */
customerId?: string
expiresAt?: string
}
export interface SwipeTimeBankCardInput {
rfidTagId: string
facilityId: string
}