gymmonk-schema 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/center.d.ts +66 -6
- package/dist/center.d.ts.map +1 -1
- package/dist/center.js +88 -6
- package/dist/center.js.map +1 -1
- package/dist/equipment.d.ts +49 -0
- package/dist/equipment.d.ts.map +1 -1
- package/dist/equipment.js +60 -0
- package/dist/equipment.js.map +1 -1
- package/dist/gym-registration.d.ts +189 -0
- package/dist/gym-registration.d.ts.map +1 -0
- package/dist/gym-registration.js +37 -0
- package/dist/gym-registration.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/member-profile.d.ts +1 -1
- package/dist/member-profile.d.ts.map +1 -1
- package/dist/member-profile.js +10 -1
- package/dist/member-profile.js.map +1 -1
- package/dist/membership-plan.d.ts +2 -5
- package/dist/membership-plan.d.ts.map +1 -1
- package/dist/membership-plan.js +4 -4
- package/dist/membership-plan.js.map +1 -1
- package/dist/messages.d.ts +39 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +37 -2
- package/dist/messages.js.map +1 -1
- package/dist/onboarding-form.d.ts +23 -151
- package/dist/onboarding-form.d.ts.map +1 -1
- package/dist/onboarding-form.js +28 -99
- package/dist/onboarding-form.js.map +1 -1
- package/dist/onboarding.d.ts +26 -319
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/onboarding.js +30 -63
- package/dist/onboarding.js.map +1 -1
- package/dist/organisation.d.ts +138 -0
- package/dist/organisation.d.ts.map +1 -1
- package/dist/organisation.js +66 -1
- package/dist/organisation.js.map +1 -1
- package/dist/user.d.ts +40 -0
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +43 -0
- package/dist/user.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# gymmonk-schema
|
|
2
|
+
|
|
3
|
+
Shared **Zod** schemas, enums and domain types for **GymMonk** (fitness SaaS).
|
|
4
|
+
Single source of truth (SSOT) consumed by both `gymmonk-backend` and
|
|
5
|
+
`gymmonk-web-client`, so validation, wire types and enums never drift between
|
|
6
|
+
the API and the app.
|
|
7
|
+
|
|
8
|
+
## What's inside
|
|
9
|
+
|
|
10
|
+
- **Common** (`common.ts`) — reusable primitives: `objectIdSchema`,
|
|
11
|
+
`paginationSchema`, `optionalTrimmedString`, `emptyStringToUndefined`.
|
|
12
|
+
- Domain modules (workouts, training library, membership, check-in, …) are
|
|
13
|
+
added here as they land, each re-exported through `src/index.ts`.
|
|
14
|
+
|
|
15
|
+
## Build & publish (npm-first)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm run build # tsc -> dist/
|
|
20
|
+
npm publish --access public
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`prepublishOnly` runs `build` + `check` (type-check + biome) as the publish gate.
|
|
24
|
+
`--access public` is a CLI flag, not a manifest field.
|
|
25
|
+
|
|
26
|
+
## Consume
|
|
27
|
+
|
|
28
|
+
In `gymmonk-backend` and `gymmonk-web-client`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install gymmonk-schema@<version>
|
|
32
|
+
# then delete any nested Zod dup so a single Zod instance is used:
|
|
33
|
+
rm -rf node_modules/gymmonk-schema/node_modules
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Bump the caret pin in each consumer's `package.json` on every publish (a `0.x`
|
|
37
|
+
caret locks to the same minor, so `npm install` won't auto-pick a new minor).
|
|
38
|
+
|
|
39
|
+
## Convention
|
|
40
|
+
|
|
41
|
+
Mirrors the jansathi schema family (`dating-schema`, `near-me-schema`) exactly:
|
|
42
|
+
pure ESM, `tsc`-only build, Zod is a **peer** dependency (`^4.4.0`), NodeNext
|
|
43
|
+
`.js` import specifiers, const-object enums fed to `z.enum(...)`.
|
package/dist/center.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ export declare const createCenterBodySchema: z.ZodObject<{
|
|
|
91
91
|
}, z.core.$strip>>;
|
|
92
92
|
}, z.core.$strip>;
|
|
93
93
|
floorAreaSqft: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
capacity: z.ZodOptional<z.ZodNumber>;
|
|
94
95
|
primaryPhone: z.ZodString;
|
|
95
96
|
secondaryPhone: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
96
97
|
email: z.ZodPreprocess<z.ZodOptional<z.ZodEmail>>;
|
|
@@ -190,6 +191,7 @@ export declare const updateCenterBodySchema: z.ZodObject<{
|
|
|
190
191
|
}, z.core.$strip>>;
|
|
191
192
|
}, z.core.$strip>>;
|
|
192
193
|
floorAreaSqft: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
194
|
+
capacity: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
193
195
|
primaryPhone: z.ZodOptional<z.ZodString>;
|
|
194
196
|
secondaryPhone: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
|
|
195
197
|
email: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodEmail>>>;
|
|
@@ -292,6 +294,7 @@ export declare const centerSchema: z.ZodObject<{
|
|
|
292
294
|
}, z.core.$strip>>;
|
|
293
295
|
}, z.core.$strip>;
|
|
294
296
|
floorAreaSqft: z.ZodNullable<z.ZodNumber>;
|
|
297
|
+
capacity: z.ZodNullable<z.ZodNumber>;
|
|
295
298
|
primaryPhone: z.ZodString;
|
|
296
299
|
secondaryPhone: z.ZodNullable<z.ZodString>;
|
|
297
300
|
email: z.ZodNullable<z.ZodString>;
|
|
@@ -343,21 +346,78 @@ export declare const centerSchema: z.ZodObject<{
|
|
|
343
346
|
updatedAt: z.ZodISODateTime;
|
|
344
347
|
}, z.core.$strip>;
|
|
345
348
|
export type Center = z.infer<typeof centerSchema>;
|
|
346
|
-
/**
|
|
349
|
+
/**
|
|
350
|
+
* The three-up header on a centre's detail screen.
|
|
351
|
+
*
|
|
352
|
+
* Computed, never stored — `activeMembers` counts live memberships and
|
|
353
|
+
* `monthlyRevenueInr` sums this month's successful payments, so the card can
|
|
354
|
+
* never drift from the ledger the way a denormalised total would. `capacity`
|
|
355
|
+
* is the one stored value, and is null until the owner fills it in.
|
|
356
|
+
*/
|
|
357
|
+
export declare const centerStatsSchema: z.ZodObject<{
|
|
358
|
+
capacity: z.ZodNullable<z.ZodNumber>;
|
|
359
|
+
activeMembers: z.ZodNumber;
|
|
360
|
+
monthlyRevenueInr: z.ZodNumber;
|
|
361
|
+
}, z.core.$strip>;
|
|
362
|
+
export type CenterStats = z.infer<typeof centerStatsSchema>;
|
|
363
|
+
/** A lightweight public directory result for the "find a gym" screen. */
|
|
347
364
|
export declare const nearbyGymSchema: z.ZodObject<{
|
|
348
365
|
id: z.ZodString;
|
|
349
366
|
code: z.ZodString;
|
|
350
367
|
name: z.ZodString;
|
|
351
368
|
area: z.ZodString;
|
|
352
|
-
|
|
369
|
+
district: z.ZodNullable<z.ZodString>;
|
|
370
|
+
state: z.ZodNullable<z.ZodString>;
|
|
371
|
+
distanceKm: z.ZodNullable<z.ZodNumber>;
|
|
353
372
|
memberCount: z.ZodOptional<z.ZodNumber>;
|
|
354
373
|
}, z.core.$strip>;
|
|
355
374
|
export type NearbyGym = z.infer<typeof nearbyGymSchema>;
|
|
356
|
-
/**
|
|
357
|
-
|
|
375
|
+
/**
|
|
376
|
+
* The gym directory has THREE ways in, and they differ in how far they reach.
|
|
377
|
+
*
|
|
378
|
+
* `code` exact public gym id — matches anywhere in the country, because
|
|
379
|
+
* a member holding the id already knows which gym they mean and
|
|
380
|
+
* may well be joining one in another state.
|
|
381
|
+
* `q` name search — deliberately narrowed to ONE STATE. Gym names
|
|
382
|
+
* repeat heavily ("Fitness First"), so a nationwide name search
|
|
383
|
+
* returns a list nobody can pick from.
|
|
384
|
+
* `districtId` browse — the gyms around them, nearest first when they share
|
|
385
|
+
* their position.
|
|
386
|
+
*
|
|
387
|
+
* `stateId` / `districtId` are master-area ids (`area.ts`), matched against the
|
|
388
|
+
* address `ancestry` array every center stores.
|
|
389
|
+
*/
|
|
390
|
+
export declare const GYM_SEARCH_MAX_RESULTS = 20;
|
|
391
|
+
export declare const gymSearchQuerySchema: z.ZodObject<{
|
|
392
|
+
code: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
393
|
+
q: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
394
|
+
stateId: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
395
|
+
districtId: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
358
396
|
lat: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
359
397
|
lng: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
360
|
-
|
|
398
|
+
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
399
|
+
}, z.core.$strip>;
|
|
400
|
+
export type GymSearchQuery = z.infer<typeof gymSearchQuerySchema>;
|
|
401
|
+
/**
|
|
402
|
+
* The directory response.
|
|
403
|
+
*
|
|
404
|
+
* `truncated` says the district holds more gyms than were returned, which is
|
|
405
|
+
* what the screen turns into "showing the 20 nearest — enter a gym id if yours
|
|
406
|
+
* is not here" rather than letting the member assume their gym is absent.
|
|
407
|
+
*/
|
|
408
|
+
export declare const gymSearchResultSchema: z.ZodObject<{
|
|
409
|
+
items: z.ZodArray<z.ZodObject<{
|
|
410
|
+
id: z.ZodString;
|
|
411
|
+
code: z.ZodString;
|
|
412
|
+
name: z.ZodString;
|
|
413
|
+
area: z.ZodString;
|
|
414
|
+
district: z.ZodNullable<z.ZodString>;
|
|
415
|
+
state: z.ZodNullable<z.ZodString>;
|
|
416
|
+
distanceKm: z.ZodNullable<z.ZodNumber>;
|
|
417
|
+
memberCount: z.ZodOptional<z.ZodNumber>;
|
|
418
|
+
}, z.core.$strip>>;
|
|
419
|
+
total: z.ZodNumber;
|
|
420
|
+
truncated: z.ZodBoolean;
|
|
361
421
|
}, z.core.$strip>;
|
|
362
|
-
export type
|
|
422
|
+
export type GymSearchResult = z.infer<typeof gymSearchResultSchema>;
|
|
363
423
|
//# sourceMappingURL=center.d.ts.map
|
package/dist/center.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,yDAAyD;AACzD,eAAO,MAAM,YAAY,8IAaf,CAAC;AACX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;EAAuB,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,kBAAkB;;;EAAiC,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,aAAa,aAGwB,CAAC;AACnD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,yDAAyD;AACzD,eAAO,MAAM,YAAY,8IAaf,CAAC;AACX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;EAAuB,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,kBAAkB;;;EAAiC,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,aAAa,aAGwB,CAAC;AACnD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuBjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsBvB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAIlD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,yEAAyE;AACzE,eAAO,MAAM,eAAe;;;;;;;;;iBAkB1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,eAAO,MAAM,oBAAoB;;;;;;;;iBAwB7B,CAAC;AACL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/center.js
CHANGED
|
@@ -45,6 +45,15 @@ export const createCenterBodySchema = z.object({
|
|
|
45
45
|
address: addressSchema,
|
|
46
46
|
/** Usable floor area in square feet. */
|
|
47
47
|
floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),
|
|
48
|
+
/**
|
|
49
|
+
* How many people the branch holds at once.
|
|
50
|
+
*
|
|
51
|
+
* The denominator behind "how full are we" — the centre detail screen shows
|
|
52
|
+
* it beside the active-member count, and crowd-load has no meaning without
|
|
53
|
+
* it. Optional: a gym that has never measured it should not be blocked from
|
|
54
|
+
* saving the rest of its details.
|
|
55
|
+
*/
|
|
56
|
+
capacity: z.number().int().min(1).max(100_000).optional(),
|
|
48
57
|
primaryPhone: phoneSchema,
|
|
49
58
|
secondaryPhone: optionalPhoneSchema,
|
|
50
59
|
email: optionalEmailSchema,
|
|
@@ -65,6 +74,7 @@ export const centerSchema = z.object({
|
|
|
65
74
|
types: z.array(centerTypeSchema),
|
|
66
75
|
address: addressSchema,
|
|
67
76
|
floorAreaSqft: z.number().int().nullable(),
|
|
77
|
+
capacity: z.number().int().nullable(),
|
|
68
78
|
primaryPhone: z.string(),
|
|
69
79
|
secondaryPhone: z.string().nullable(),
|
|
70
80
|
email: z.string().nullable(),
|
|
@@ -77,20 +87,92 @@ export const centerSchema = z.object({
|
|
|
77
87
|
createdAt: isoDateTimeSchema,
|
|
78
88
|
updatedAt: isoDateTimeSchema,
|
|
79
89
|
});
|
|
80
|
-
// ───
|
|
81
|
-
/**
|
|
90
|
+
// ─── Center stats (owner detail header) ──────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* The three-up header on a centre's detail screen.
|
|
93
|
+
*
|
|
94
|
+
* Computed, never stored — `activeMembers` counts live memberships and
|
|
95
|
+
* `monthlyRevenueInr` sums this month's successful payments, so the card can
|
|
96
|
+
* never drift from the ledger the way a denormalised total would. `capacity`
|
|
97
|
+
* is the one stored value, and is null until the owner fills it in.
|
|
98
|
+
*/
|
|
99
|
+
export const centerStatsSchema = z.object({
|
|
100
|
+
capacity: z.number().int().nullable(),
|
|
101
|
+
activeMembers: z.number().int().nonnegative(),
|
|
102
|
+
monthlyRevenueInr: z.number().nonnegative(),
|
|
103
|
+
});
|
|
104
|
+
// ─── Gym directory (member "find a gym") ─────────────────────────────────────
|
|
105
|
+
/** A lightweight public directory result for the "find a gym" screen. */
|
|
82
106
|
export const nearbyGymSchema = z.object({
|
|
83
107
|
id: objectIdSchema,
|
|
84
108
|
code: gymCodeSchema,
|
|
85
109
|
name: z.string(),
|
|
110
|
+
/** The narrowest place name we hold (locality → district → state). */
|
|
86
111
|
area: z.string(),
|
|
87
|
-
|
|
112
|
+
district: z.string().nullable(),
|
|
113
|
+
state: z.string().nullable(),
|
|
114
|
+
/**
|
|
115
|
+
* Kilometres from the coordinates the caller supplied.
|
|
116
|
+
*
|
|
117
|
+
* NULL, not 0, when it cannot be computed — the caller sent no position, or
|
|
118
|
+
* the gym's address carries no coordinates. Zero is a real distance ("you are
|
|
119
|
+
* standing in it") and using it as the unknown value put every un-geocoded
|
|
120
|
+
* gym at the top of a nearest-first list.
|
|
121
|
+
*/
|
|
122
|
+
distanceKm: z.number().nonnegative().nullable(),
|
|
88
123
|
memberCount: z.number().int().nonnegative().optional(),
|
|
89
124
|
});
|
|
90
|
-
/**
|
|
91
|
-
|
|
125
|
+
/**
|
|
126
|
+
* The gym directory has THREE ways in, and they differ in how far they reach.
|
|
127
|
+
*
|
|
128
|
+
* `code` exact public gym id — matches anywhere in the country, because
|
|
129
|
+
* a member holding the id already knows which gym they mean and
|
|
130
|
+
* may well be joining one in another state.
|
|
131
|
+
* `q` name search — deliberately narrowed to ONE STATE. Gym names
|
|
132
|
+
* repeat heavily ("Fitness First"), so a nationwide name search
|
|
133
|
+
* returns a list nobody can pick from.
|
|
134
|
+
* `districtId` browse — the gyms around them, nearest first when they share
|
|
135
|
+
* their position.
|
|
136
|
+
*
|
|
137
|
+
* `stateId` / `districtId` are master-area ids (`area.ts`), matched against the
|
|
138
|
+
* address `ancestry` array every center stores.
|
|
139
|
+
*/
|
|
140
|
+
export const GYM_SEARCH_MAX_RESULTS = 20;
|
|
141
|
+
export const gymSearchQuerySchema = z
|
|
142
|
+
.object({
|
|
143
|
+
/** Exact public gym id, e.g. `GYM-2025-001`. Unscoped by geography. */
|
|
144
|
+
code: optionalTrimmedString(40),
|
|
145
|
+
/** Name fragment. Requires `stateId`. */
|
|
146
|
+
q: optionalTrimmedString(120),
|
|
147
|
+
stateId: optionalTrimmedString(40),
|
|
148
|
+
districtId: optionalTrimmedString(40),
|
|
92
149
|
lat: z.coerce.number().min(-90).max(90).optional(),
|
|
93
150
|
lng: z.coerce.number().min(-180).max(180).optional(),
|
|
94
|
-
|
|
151
|
+
limit: z.coerce
|
|
152
|
+
.number()
|
|
153
|
+
.int()
|
|
154
|
+
.min(1)
|
|
155
|
+
.max(GYM_SEARCH_MAX_RESULTS)
|
|
156
|
+
.default(GYM_SEARCH_MAX_RESULTS),
|
|
157
|
+
})
|
|
158
|
+
.refine((v) => !v.q || Boolean(v.stateId), {
|
|
159
|
+
message: 'Pick a state to search gyms by name',
|
|
160
|
+
path: ['stateId'],
|
|
161
|
+
})
|
|
162
|
+
.refine((v) => Boolean(v.code || v.q || v.stateId || v.districtId), {
|
|
163
|
+
message: 'Enter a gym id, or pick where to look',
|
|
164
|
+
path: ['stateId'],
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* The directory response.
|
|
168
|
+
*
|
|
169
|
+
* `truncated` says the district holds more gyms than were returned, which is
|
|
170
|
+
* what the screen turns into "showing the 20 nearest — enter a gym id if yours
|
|
171
|
+
* is not here" rather than letting the member assume their gym is absent.
|
|
172
|
+
*/
|
|
173
|
+
export const gymSearchResultSchema = z.object({
|
|
174
|
+
items: z.array(nearbyGymSchema),
|
|
175
|
+
total: z.number().int().nonnegative(),
|
|
176
|
+
truncated: z.boolean(),
|
|
95
177
|
});
|
|
96
178
|
//# sourceMappingURL=center.js.map
|
package/dist/center.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"center.js","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Center (branch)\n * ================================\n * A physical gym location belonging to an organisation. Carries the public gym\n * code members use to join, opening hours, equipment scope, and contact info.\n * v1 memberships are single-center, so a Center is the unit a member joins.\n *\n * @module gymmonk-schema/center\n */\n\nimport { z } from 'zod';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalPhoneSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { addressSchema, weeklyHoursSchema } from './shared.js';\n\n// ─── Center type ─────────────────────────────────────────────────────────────\n\n/** The activity types a center offers (multi-select). */\nexport const CENTER_TYPES = [\n 'gym',\n 'crossfit',\n 'yoga',\n 'pilates',\n 'zumba',\n 'cardio',\n 'strength',\n 'mma',\n 'boxing',\n 'swimming',\n 'aerobics',\n 'personal-training',\n] as const;\nexport const centerTypeSchema = z.enum(CENTER_TYPES);\nexport type CenterType = z.infer<typeof centerTypeSchema>;\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const centerStatusSchema = z.enum(['active', 'inactive']);\nexport type CenterStatus = z.infer<typeof centerStatusSchema>;\n\n/**\n * The public gym code a member enters at onboarding to join, `GYM-YYYY-NNN`.\n * Server-generated and unique; never accepted in a create body.\n */\nexport const gymCodeSchema = z\n .string()\n .trim()\n .regex(/^GYM-\\d{4}-\\d{3,}$/, 'Invalid gym code');\nexport type GymCode = z.infer<typeof gymCodeSchema>;\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createCenterBodySchema = z.object({\n name: z.string().trim().min(1, 'Center name is required').max(160),\n description: optionalTrimmedString(2000),\n types: z.array(centerTypeSchema).min(1, 'Pick at least one type'),\n address: addressSchema,\n /** Usable floor area in square feet. */\n floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),\n primaryPhone: phoneSchema,\n secondaryPhone: optionalPhoneSchema,\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n hours: weeklyHoursSchema,\n /** Uploaded gallery image URLs. */\n photos: z.array(z.string().trim().max(2000)).max(20).default([]),\n});\nexport type CreateCenterBody = z.infer<typeof createCenterBodySchema>;\n\nexport const updateCenterBodySchema = createCenterBodySchema.partial();\nexport type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const centerSchema = z.object({\n id: objectIdSchema,\n organisationId: objectIdSchema,\n /** Public join code, `GYM-YYYY-NNN`. */\n code: gymCodeSchema,\n name: z.string(),\n description: z.string().nullable(),\n types: z.array(centerTypeSchema),\n address: addressSchema,\n floorAreaSqft: z.number().int().nullable(),\n primaryPhone: z.string(),\n secondaryPhone: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n hours: weeklyHoursSchema,\n photos: z.array(z.string()),\n status: centerStatusSchema,\n /** Denormalised active-member count for roster headers. */\n memberCount: z.number().int().nonnegative(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Center = z.infer<typeof centerSchema>;\n\n// ─── Nearby / directory card ─────────────────────────────────────────────────\n\n/** A lightweight public directory result for the \"find your gym\" step. */\nexport const nearbyGymSchema = z.object({\n id: objectIdSchema,\n code: gymCodeSchema,\n name: z.string(),\n area: z.string(),\n distanceKm: z.number().nonnegative(),\n memberCount: z.number().int().nonnegative().optional(),\n});\nexport type NearbyGym = z.infer<typeof nearbyGymSchema>;\n\n/** Query params for the nearby-gyms search. */\nexport const nearbyGymsQuerySchema = z.object({\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n q: optionalTrimmedString(120),\n});\nexport type NearbyGymsQuery = z.infer<typeof nearbyGymsQuerySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"center.js","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Center (branch)\n * ================================\n * A physical gym location belonging to an organisation. Carries the public gym\n * code members use to join, opening hours, equipment scope, and contact info.\n * v1 memberships are single-center, so a Center is the unit a member joins.\n *\n * @module gymmonk-schema/center\n */\n\nimport { z } from 'zod';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalPhoneSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { addressSchema, weeklyHoursSchema } from './shared.js';\n\n// ─── Center type ─────────────────────────────────────────────────────────────\n\n/** The activity types a center offers (multi-select). */\nexport const CENTER_TYPES = [\n 'gym',\n 'crossfit',\n 'yoga',\n 'pilates',\n 'zumba',\n 'cardio',\n 'strength',\n 'mma',\n 'boxing',\n 'swimming',\n 'aerobics',\n 'personal-training',\n] as const;\nexport const centerTypeSchema = z.enum(CENTER_TYPES);\nexport type CenterType = z.infer<typeof centerTypeSchema>;\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const centerStatusSchema = z.enum(['active', 'inactive']);\nexport type CenterStatus = z.infer<typeof centerStatusSchema>;\n\n/**\n * The public gym code a member enters at onboarding to join, `GYM-YYYY-NNN`.\n * Server-generated and unique; never accepted in a create body.\n */\nexport const gymCodeSchema = z\n .string()\n .trim()\n .regex(/^GYM-\\d{4}-\\d{3,}$/, 'Invalid gym code');\nexport type GymCode = z.infer<typeof gymCodeSchema>;\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createCenterBodySchema = z.object({\n name: z.string().trim().min(1, 'Center name is required').max(160),\n description: optionalTrimmedString(2000),\n types: z.array(centerTypeSchema).min(1, 'Pick at least one type'),\n address: addressSchema,\n /** Usable floor area in square feet. */\n floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),\n /**\n * How many people the branch holds at once.\n *\n * The denominator behind \"how full are we\" — the centre detail screen shows\n * it beside the active-member count, and crowd-load has no meaning without\n * it. Optional: a gym that has never measured it should not be blocked from\n * saving the rest of its details.\n */\n capacity: z.number().int().min(1).max(100_000).optional(),\n primaryPhone: phoneSchema,\n secondaryPhone: optionalPhoneSchema,\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n hours: weeklyHoursSchema,\n /** Uploaded gallery image URLs. */\n photos: z.array(z.string().trim().max(2000)).max(20).default([]),\n});\nexport type CreateCenterBody = z.infer<typeof createCenterBodySchema>;\n\nexport const updateCenterBodySchema = createCenterBodySchema.partial();\nexport type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const centerSchema = z.object({\n id: objectIdSchema,\n organisationId: objectIdSchema,\n /** Public join code, `GYM-YYYY-NNN`. */\n code: gymCodeSchema,\n name: z.string(),\n description: z.string().nullable(),\n types: z.array(centerTypeSchema),\n address: addressSchema,\n floorAreaSqft: z.number().int().nullable(),\n capacity: z.number().int().nullable(),\n primaryPhone: z.string(),\n secondaryPhone: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n hours: weeklyHoursSchema,\n photos: z.array(z.string()),\n status: centerStatusSchema,\n /** Denormalised active-member count for roster headers. */\n memberCount: z.number().int().nonnegative(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Center = z.infer<typeof centerSchema>;\n\n// ─── Center stats (owner detail header) ──────────────────────────────────────\n\n/**\n * The three-up header on a centre's detail screen.\n *\n * Computed, never stored — `activeMembers` counts live memberships and\n * `monthlyRevenueInr` sums this month's successful payments, so the card can\n * never drift from the ledger the way a denormalised total would. `capacity`\n * is the one stored value, and is null until the owner fills it in.\n */\nexport const centerStatsSchema = z.object({\n capacity: z.number().int().nullable(),\n activeMembers: z.number().int().nonnegative(),\n monthlyRevenueInr: z.number().nonnegative(),\n});\nexport type CenterStats = z.infer<typeof centerStatsSchema>;\n\n// ─── Gym directory (member \"find a gym\") ─────────────────────────────────────\n\n/** A lightweight public directory result for the \"find a gym\" screen. */\nexport const nearbyGymSchema = z.object({\n id: objectIdSchema,\n code: gymCodeSchema,\n name: z.string(),\n /** The narrowest place name we hold (locality → district → state). */\n area: z.string(),\n district: z.string().nullable(),\n state: z.string().nullable(),\n /**\n * Kilometres from the coordinates the caller supplied.\n *\n * NULL, not 0, when it cannot be computed — the caller sent no position, or\n * the gym's address carries no coordinates. Zero is a real distance (\"you are\n * standing in it\") and using it as the unknown value put every un-geocoded\n * gym at the top of a nearest-first list.\n */\n distanceKm: z.number().nonnegative().nullable(),\n memberCount: z.number().int().nonnegative().optional(),\n});\nexport type NearbyGym = z.infer<typeof nearbyGymSchema>;\n\n/**\n * The gym directory has THREE ways in, and they differ in how far they reach.\n *\n * `code` exact public gym id — matches anywhere in the country, because\n * a member holding the id already knows which gym they mean and\n * may well be joining one in another state.\n * `q` name search — deliberately narrowed to ONE STATE. Gym names\n * repeat heavily (\"Fitness First\"), so a nationwide name search\n * returns a list nobody can pick from.\n * `districtId` browse — the gyms around them, nearest first when they share\n * their position.\n *\n * `stateId` / `districtId` are master-area ids (`area.ts`), matched against the\n * address `ancestry` array every center stores.\n */\nexport const GYM_SEARCH_MAX_RESULTS = 20;\n\nexport const gymSearchQuerySchema = z\n .object({\n /** Exact public gym id, e.g. `GYM-2025-001`. Unscoped by geography. */\n code: optionalTrimmedString(40),\n /** Name fragment. Requires `stateId`. */\n q: optionalTrimmedString(120),\n stateId: optionalTrimmedString(40),\n districtId: optionalTrimmedString(40),\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .max(GYM_SEARCH_MAX_RESULTS)\n .default(GYM_SEARCH_MAX_RESULTS),\n })\n .refine((v) => !v.q || Boolean(v.stateId), {\n message: 'Pick a state to search gyms by name',\n path: ['stateId'],\n })\n .refine((v) => Boolean(v.code || v.q || v.stateId || v.districtId), {\n message: 'Enter a gym id, or pick where to look',\n path: ['stateId'],\n });\nexport type GymSearchQuery = z.infer<typeof gymSearchQuerySchema>;\n\n/**\n * The directory response.\n *\n * `truncated` says the district holds more gyms than were returned, which is\n * what the screen turns into \"showing the 20 nearest — enter a gym id if yours\n * is not here\" rather than letting the member assume their gym is absent.\n */\nexport const gymSearchResultSchema = z.object({\n items: z.array(nearbyGymSchema),\n total: z.number().int().nonnegative(),\n truncated: z.boolean(),\n});\nexport type GymSearchResult = z.infer<typeof gymSearchResultSchema>;\n"]}
|
package/dist/equipment.d.ts
CHANGED
|
@@ -78,6 +78,55 @@ export declare const updateEquipmentBodySchema: z.ZodObject<{
|
|
|
78
78
|
notes: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
|
|
79
79
|
}, z.core.$strip>;
|
|
80
80
|
export type UpdateEquipmentBody = z.infer<typeof updateEquipmentBodySchema>;
|
|
81
|
+
/**
|
|
82
|
+
* The equipment an owner can add by tapping, per category.
|
|
83
|
+
*
|
|
84
|
+
* A SHARED vocabulary, not a UI list: an owner who types "Treadmill" at one
|
|
85
|
+
* gym and "treadmill" at another produces two rows that never roll up, and the
|
|
86
|
+
* usage analytics that key off equipment name would split in half. The picker
|
|
87
|
+
* offers these; anything genuinely unusual is still addable as a free-text
|
|
88
|
+
* `custom` item, which is what that category is for.
|
|
89
|
+
*/
|
|
90
|
+
export declare const EQUIPMENT_CATALOG: Record<EquipmentCategory, readonly string[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Set a center's inventory for a set of items in one call.
|
|
93
|
+
*
|
|
94
|
+
* The add screen is a grid of steppers the owner nudges up and down, and it
|
|
95
|
+
* saves once at the end. Sending each nudge as its own create/update/delete
|
|
96
|
+
* would make a half-saved screen the normal outcome of a flaky connection, so
|
|
97
|
+
* the whole intent goes over as one idempotent statement: after this call,
|
|
98
|
+
* these items are at these quantities. A quantity of ZERO removes the item,
|
|
99
|
+
* which is how the stepper's "back to none" is expressed.
|
|
100
|
+
*/
|
|
101
|
+
export declare const equipmentQuantityEntrySchema: z.ZodObject<{
|
|
102
|
+
category: z.ZodEnum<{
|
|
103
|
+
custom: "custom";
|
|
104
|
+
cardio: "cardio";
|
|
105
|
+
strength: "strength";
|
|
106
|
+
weight: "weight";
|
|
107
|
+
training: "training";
|
|
108
|
+
recovery: "recovery";
|
|
109
|
+
}>;
|
|
110
|
+
name: z.ZodString;
|
|
111
|
+
quantity: z.ZodNumber;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
export type EquipmentQuantityEntry = z.infer<typeof equipmentQuantityEntrySchema>;
|
|
114
|
+
export declare const setEquipmentQuantitiesBodySchema: z.ZodObject<{
|
|
115
|
+
centerId: z.ZodString;
|
|
116
|
+
items: z.ZodArray<z.ZodObject<{
|
|
117
|
+
category: z.ZodEnum<{
|
|
118
|
+
custom: "custom";
|
|
119
|
+
cardio: "cardio";
|
|
120
|
+
strength: "strength";
|
|
121
|
+
weight: "weight";
|
|
122
|
+
training: "training";
|
|
123
|
+
recovery: "recovery";
|
|
124
|
+
}>;
|
|
125
|
+
name: z.ZodString;
|
|
126
|
+
quantity: z.ZodNumber;
|
|
127
|
+
}, z.core.$strip>>;
|
|
128
|
+
}, z.core.$strip>;
|
|
129
|
+
export type SetEquipmentQuantitiesBody = z.infer<typeof setEquipmentQuantitiesBodySchema>;
|
|
81
130
|
export declare const equipmentQuerySchema: z.ZodObject<{
|
|
82
131
|
centerId: z.ZodString;
|
|
83
132
|
category: z.ZodOptional<z.ZodEnum<{
|
package/dist/equipment.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"equipment.d.ts","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,oBAAoB,6EAOvB,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;EAA+B,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;iBAS1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,4DAA4D;AAC5D,eAAO,MAAM,8BAA8B;;;;;;;;;;;iBAIzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;iBAMpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAE1B,CAAC;AACb,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,eAAO,MAAM,oBAAoB;;;;;;;;;;iBAG/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"equipment.d.ts","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,oBAAoB,6EAOvB,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;EAA+B,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;iBAS1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,4DAA4D;AAC5D,eAAO,MAAM,8BAA8B;;;;;;;;;;;iBAIzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;iBAMpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAE1B,CAAC;AACb,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CA6B1E,CAAC;AAIF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;iBAIvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;iBAG3C,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAI1F,eAAO,MAAM,oBAAoB;;;;;;;;;;iBAG/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
package/dist/equipment.js
CHANGED
|
@@ -46,6 +46,66 @@ export const createEquipmentBodySchema = z.object({
|
|
|
46
46
|
export const updateEquipmentBodySchema = createEquipmentBodySchema
|
|
47
47
|
.omit({ centerId: true })
|
|
48
48
|
.partial();
|
|
49
|
+
// ─── Catalog ─────────────────────────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* The equipment an owner can add by tapping, per category.
|
|
52
|
+
*
|
|
53
|
+
* A SHARED vocabulary, not a UI list: an owner who types "Treadmill" at one
|
|
54
|
+
* gym and "treadmill" at another produces two rows that never roll up, and the
|
|
55
|
+
* usage analytics that key off equipment name would split in half. The picker
|
|
56
|
+
* offers these; anything genuinely unusual is still addable as a free-text
|
|
57
|
+
* `custom` item, which is what that category is for.
|
|
58
|
+
*/
|
|
59
|
+
export const EQUIPMENT_CATALOG = {
|
|
60
|
+
cardio: [
|
|
61
|
+
'Treadmill',
|
|
62
|
+
'Elliptical',
|
|
63
|
+
'Stationary Bike',
|
|
64
|
+
'Rowing Machine',
|
|
65
|
+
'Stair Climber',
|
|
66
|
+
'Spin Bike',
|
|
67
|
+
],
|
|
68
|
+
strength: [
|
|
69
|
+
'Chest Press',
|
|
70
|
+
'Lat Pulldown',
|
|
71
|
+
'Leg Press',
|
|
72
|
+
'Shoulder Press',
|
|
73
|
+
'Cable Crossover',
|
|
74
|
+
'Smith Machine',
|
|
75
|
+
'Seated Row',
|
|
76
|
+
],
|
|
77
|
+
weight: ['Dumbbells', 'Barbells', 'Weight Plates', 'Kettlebells', 'Bench Press', 'Squat Rack'],
|
|
78
|
+
training: [
|
|
79
|
+
'Battle Ropes',
|
|
80
|
+
'Plyo Box',
|
|
81
|
+
'Medicine Ball',
|
|
82
|
+
'TRX Straps',
|
|
83
|
+
'Agility Ladder',
|
|
84
|
+
'Punching Bag',
|
|
85
|
+
],
|
|
86
|
+
recovery: ['Foam Roller', 'Massage Gun', 'Stretching Mat', 'Ice Bath', 'Sauna', 'Steam Room'],
|
|
87
|
+
custom: [],
|
|
88
|
+
};
|
|
89
|
+
// ─── Bulk quantity set (the "Add New Equipment" screen) ──────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Set a center's inventory for a set of items in one call.
|
|
92
|
+
*
|
|
93
|
+
* The add screen is a grid of steppers the owner nudges up and down, and it
|
|
94
|
+
* saves once at the end. Sending each nudge as its own create/update/delete
|
|
95
|
+
* would make a half-saved screen the normal outcome of a flaky connection, so
|
|
96
|
+
* the whole intent goes over as one idempotent statement: after this call,
|
|
97
|
+
* these items are at these quantities. A quantity of ZERO removes the item,
|
|
98
|
+
* which is how the stepper's "back to none" is expressed.
|
|
99
|
+
*/
|
|
100
|
+
export const equipmentQuantityEntrySchema = z.object({
|
|
101
|
+
category: equipmentCategorySchema,
|
|
102
|
+
name: z.string().trim().min(1).max(120),
|
|
103
|
+
quantity: z.number().int().min(0).max(100000),
|
|
104
|
+
});
|
|
105
|
+
export const setEquipmentQuantitiesBodySchema = z.object({
|
|
106
|
+
centerId: objectIdSchema,
|
|
107
|
+
items: z.array(equipmentQuantityEntrySchema).min(1).max(200),
|
|
108
|
+
});
|
|
49
109
|
// ─── List query ──────────────────────────────────────────────────────────────
|
|
50
110
|
export const equipmentQuerySchema = z.object({
|
|
51
111
|
centerId: objectIdSchema,
|
package/dist/equipment.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"equipment.js","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEvF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,UAAU;IACV,UAAU;IACV,QAAQ;CACA,CAAC;AACX,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAGpE,gFAAgF;AAEhF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,cAAc;IAClB,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,uBAAuB;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;CAC7B,CAAC,CAAC;AAGH,4DAA4D;AAC5D,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,MAAM,CAAC;IACrD,QAAQ,EAAE,uBAAuB;IACjC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;CACtC,CAAC,CAAC;AAGH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,uBAAuB;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACxD,KAAK,EAAE,qBAAqB,CAAC,GAAG,CAAC;CAClC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yBAAyB;KAC/D,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KACxB,OAAO,EAAE,CAAC;AAGb,gFAAgF;AAEhF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,uBAAuB,CAAC,QAAQ,EAAE;CAC7C,CAAC,CAAC","sourcesContent":["/**\n * gymmonk-schema — Equipment\n * ==========================\n * A center's equipment inventory. The owner UI groups items by category and\n * shows per-category counts; the entity is a per-item record scoped to a center.\n *\n * @module gymmonk-schema/equipment\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\n\n// ─── Category ────────────────────────────────────────────────────────────────\n\nexport const EQUIPMENT_CATEGORIES = [\n 'cardio',\n 'strength',\n 'weight',\n 'training',\n 'recovery',\n 'custom',\n] as const;\nexport const equipmentCategorySchema = z.enum(EQUIPMENT_CATEGORIES);\nexport type EquipmentCategory = z.infer<typeof equipmentCategorySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const equipmentSchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string(),\n quantity: z.number().int().nonnegative(),\n notes: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Equipment = z.infer<typeof equipmentSchema>;\n\n/** Per-category rollup the owner equipment list renders. */\nexport const equipmentCategorySummarySchema = z.object({\n category: equipmentCategorySchema,\n label: z.string(),\n count: z.number().int().nonnegative(),\n});\nexport type EquipmentCategorySummary = z.infer<typeof equipmentCategorySummarySchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createEquipmentBodySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string().trim().min(1, 'Name is required').max(120),\n quantity: z.number().int().min(1).max(100000).default(1),\n notes: optionalTrimmedString(500),\n});\nexport type CreateEquipmentBody = z.infer<typeof createEquipmentBodySchema>;\n\nexport const updateEquipmentBodySchema = createEquipmentBodySchema\n .omit({ centerId: true })\n .partial();\nexport type UpdateEquipmentBody = z.infer<typeof updateEquipmentBodySchema>;\n\n// ─── List query ──────────────────────────────────────────────────────────────\n\nexport const equipmentQuerySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema.optional(),\n});\nexport type EquipmentQuery = z.infer<typeof equipmentQuerySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"equipment.js","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Equipment\n * ==========================\n * A center's equipment inventory. The owner UI groups items by category and\n * shows per-category counts; the entity is a per-item record scoped to a center.\n *\n * @module gymmonk-schema/equipment\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\n\n// ─── Category ────────────────────────────────────────────────────────────────\n\nexport const EQUIPMENT_CATEGORIES = [\n 'cardio',\n 'strength',\n 'weight',\n 'training',\n 'recovery',\n 'custom',\n] as const;\nexport const equipmentCategorySchema = z.enum(EQUIPMENT_CATEGORIES);\nexport type EquipmentCategory = z.infer<typeof equipmentCategorySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const equipmentSchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string(),\n quantity: z.number().int().nonnegative(),\n notes: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Equipment = z.infer<typeof equipmentSchema>;\n\n/** Per-category rollup the owner equipment list renders. */\nexport const equipmentCategorySummarySchema = z.object({\n category: equipmentCategorySchema,\n label: z.string(),\n count: z.number().int().nonnegative(),\n});\nexport type EquipmentCategorySummary = z.infer<typeof equipmentCategorySummarySchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createEquipmentBodySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string().trim().min(1, 'Name is required').max(120),\n quantity: z.number().int().min(1).max(100000).default(1),\n notes: optionalTrimmedString(500),\n});\nexport type CreateEquipmentBody = z.infer<typeof createEquipmentBodySchema>;\n\nexport const updateEquipmentBodySchema = createEquipmentBodySchema\n .omit({ centerId: true })\n .partial();\nexport type UpdateEquipmentBody = z.infer<typeof updateEquipmentBodySchema>;\n\n// ─── Catalog ─────────────────────────────────────────────────────────────────\n\n/**\n * The equipment an owner can add by tapping, per category.\n *\n * A SHARED vocabulary, not a UI list: an owner who types \"Treadmill\" at one\n * gym and \"treadmill\" at another produces two rows that never roll up, and the\n * usage analytics that key off equipment name would split in half. The picker\n * offers these; anything genuinely unusual is still addable as a free-text\n * `custom` item, which is what that category is for.\n */\nexport const EQUIPMENT_CATALOG: Record<EquipmentCategory, readonly string[]> = {\n cardio: [\n 'Treadmill',\n 'Elliptical',\n 'Stationary Bike',\n 'Rowing Machine',\n 'Stair Climber',\n 'Spin Bike',\n ],\n strength: [\n 'Chest Press',\n 'Lat Pulldown',\n 'Leg Press',\n 'Shoulder Press',\n 'Cable Crossover',\n 'Smith Machine',\n 'Seated Row',\n ],\n weight: ['Dumbbells', 'Barbells', 'Weight Plates', 'Kettlebells', 'Bench Press', 'Squat Rack'],\n training: [\n 'Battle Ropes',\n 'Plyo Box',\n 'Medicine Ball',\n 'TRX Straps',\n 'Agility Ladder',\n 'Punching Bag',\n ],\n recovery: ['Foam Roller', 'Massage Gun', 'Stretching Mat', 'Ice Bath', 'Sauna', 'Steam Room'],\n custom: [],\n};\n\n// ─── Bulk quantity set (the \"Add New Equipment\" screen) ──────────────────────\n\n/**\n * Set a center's inventory for a set of items in one call.\n *\n * The add screen is a grid of steppers the owner nudges up and down, and it\n * saves once at the end. Sending each nudge as its own create/update/delete\n * would make a half-saved screen the normal outcome of a flaky connection, so\n * the whole intent goes over as one idempotent statement: after this call,\n * these items are at these quantities. A quantity of ZERO removes the item,\n * which is how the stepper's \"back to none\" is expressed.\n */\nexport const equipmentQuantityEntrySchema = z.object({\n category: equipmentCategorySchema,\n name: z.string().trim().min(1).max(120),\n quantity: z.number().int().min(0).max(100000),\n});\nexport type EquipmentQuantityEntry = z.infer<typeof equipmentQuantityEntrySchema>;\n\nexport const setEquipmentQuantitiesBodySchema = z.object({\n centerId: objectIdSchema,\n items: z.array(equipmentQuantityEntrySchema).min(1).max(200),\n});\nexport type SetEquipmentQuantitiesBody = z.infer<typeof setEquipmentQuantitiesBodySchema>;\n\n// ─── List query ──────────────────────────────────────────────────────────────\n\nexport const equipmentQuerySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema.optional(),\n});\nexport type EquipmentQuery = z.infer<typeof equipmentQuerySchema>;\n"]}
|