@zonetrix/shared 2.1.0 → 2.3.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/dist/index.d.mts CHANGED
@@ -3,8 +3,9 @@
3
3
  */
4
4
  type SeatState = "available" | "reserved" | "selected" | "unavailable" | "hidden";
5
5
  type SeatShape = "circle" | "square" | "rounded-square";
6
- type Tool = "select" | "seat" | "row" | "section" | "stage";
6
+ type Tool = "select" | "seat" | "row" | "section" | "object";
7
7
  type EditorMode = "edit" | "read";
8
+ type ObjectType = "stage" | "table" | "wall" | "barrier" | "dj-booth" | "bar" | "entry-exit" | "custom";
8
9
  /**
9
10
  * Seat data structure used in the editor and viewer
10
11
  */
@@ -50,6 +51,12 @@ interface StageConfig {
50
51
  color?: string;
51
52
  floorId?: string;
52
53
  }
54
+ /**
55
+ * Object configuration (extends StageConfig with object type support)
56
+ */
57
+ interface ObjectConfig extends StageConfig {
58
+ objectType?: ObjectType;
59
+ }
53
60
  /**
54
61
  * Floor configuration for multi-floor venues
55
62
  */
@@ -247,4 +254,195 @@ declare function importConfigFromJSON(jsonString: string): SeatMapConfig;
247
254
  */
248
255
  declare function downloadConfigAsFile(config: SeatMapConfig, filename?: string): void;
249
256
 
250
- export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, cloneConfig, createDefaultConfig, downloadConfigAsFile, exportConfigAsJSON, formatDate, generateId, getSelectedSeats, importConfigFromJSON, updateConfigTimestamp, validateSeatMapConfig };
257
+ /**
258
+ * Firebase Realtime Database type definitions for Seat Map Studio
259
+ */
260
+
261
+ /**
262
+ * Firebase seat state (excludes 'selected' which is client-only, 'hidden' which is filtered)
263
+ */
264
+ type FirebaseSeatState = 'available' | 'reserved' | 'unavailable';
265
+ /**
266
+ * Lightweight seat states map for high-frequency real-time updates
267
+ * Stored at: seat_states/{seatMapId}
268
+ */
269
+ interface FirebaseSeatStates {
270
+ [seatId: string]: FirebaseSeatState;
271
+ }
272
+ /**
273
+ * Position in Firebase (same structure as app)
274
+ */
275
+ interface FirebasePosition {
276
+ x: number;
277
+ y: number;
278
+ }
279
+ /**
280
+ * Seat data stored in Firebase
281
+ * Stored at: seatmaps/{seatMapId}/seats/{seatId}
282
+ */
283
+ interface FirebaseSeat {
284
+ position: FirebasePosition;
285
+ shape: SeatShape;
286
+ state: FirebaseSeatState;
287
+ sectionName?: string;
288
+ rowLabel?: string;
289
+ columnLabel?: string;
290
+ seatNumber?: string;
291
+ price?: number;
292
+ floorId?: string;
293
+ }
294
+ /**
295
+ * Stage configuration in Firebase
296
+ * Stored at: seatmaps/{seatMapId}/config/stages/{stageId}
297
+ */
298
+ interface FirebaseStage {
299
+ position: FirebasePosition;
300
+ config: {
301
+ label: string;
302
+ width: number;
303
+ height: number;
304
+ rotation?: number;
305
+ color?: string;
306
+ objectType?: ObjectType;
307
+ };
308
+ floorId?: string;
309
+ }
310
+ /**
311
+ * Canvas configuration in Firebase
312
+ * Stored at: seatmaps/{seatMapId}/config/canvas
313
+ */
314
+ interface FirebaseCanvasConfig {
315
+ width: number;
316
+ height: number;
317
+ backgroundColor: string;
318
+ }
319
+ /**
320
+ * Seat map metadata in Firebase
321
+ * Stored at: seatmaps/{seatMapId}/meta
322
+ */
323
+ interface FirebaseSeatMapMeta {
324
+ event_id: number;
325
+ sub_event_id: number;
326
+ name: string;
327
+ venue?: string;
328
+ capacity?: number;
329
+ updated_at: number;
330
+ version: string;
331
+ }
332
+ /**
333
+ * Design configuration in Firebase (excludes seats which are separate)
334
+ * Stored at: seatmaps/{seatMapId}/config
335
+ */
336
+ interface FirebaseSeatMapConfig {
337
+ canvas: FirebaseCanvasConfig;
338
+ colors: ColorSettings;
339
+ floors?: Record<string, FloorConfig>;
340
+ stages?: Record<string, FirebaseStage>;
341
+ }
342
+ /**
343
+ * Complete seat map data structure in Firebase
344
+ * Stored at: seatmaps/{seatMapId}
345
+ */
346
+ interface FirebaseSeatMap {
347
+ meta: FirebaseSeatMapMeta;
348
+ config: FirebaseSeatMapConfig;
349
+ seats: Record<string, FirebaseSeat>;
350
+ }
351
+ /**
352
+ * Index entry for seat map lookup by event
353
+ * Stored at: indexes/by_event/{eventId}/{seatMapId}
354
+ */
355
+ type FirebaseIndexEntry = true;
356
+ /**
357
+ * Options for Firebase real-time hooks
358
+ */
359
+ interface FirebaseHookOptions {
360
+ enabled?: boolean;
361
+ onError?: (error: Error) => void;
362
+ }
363
+ /**
364
+ * Result from Firebase seat states hook
365
+ */
366
+ interface FirebaseSeatStatesResult {
367
+ states: FirebaseSeatStates | null;
368
+ loading: boolean;
369
+ error: Error | null;
370
+ lastUpdated: number | null;
371
+ }
372
+ /**
373
+ * Result from Firebase config hook
374
+ */
375
+ interface FirebaseConfigResult {
376
+ config: FirebaseSeatMap | null;
377
+ loading: boolean;
378
+ error: Error | null;
379
+ refetch: () => Promise<void>;
380
+ }
381
+
382
+ /**
383
+ * Firebase Realtime Database path utilities
384
+ * Provides type-safe path builders for all Firebase nodes
385
+ */
386
+ /**
387
+ * All Firebase database paths used by Seat Map Studio
388
+ */
389
+ declare const FirebasePaths: {
390
+ readonly seatmap: (seatMapId: string) => `seatmaps/${string}`;
391
+ readonly seatmapMeta: (seatMapId: string) => `seatmaps/${string}/meta`;
392
+ readonly seatmapConfig: (seatMapId: string) => `seatmaps/${string}/config`;
393
+ readonly seatmapSeats: (seatMapId: string) => `seatmaps/${string}/seats`;
394
+ readonly seatmapSeat: (seatMapId: string, seatId: string) => `seatmaps/${string}/seats/${string}`;
395
+ readonly seatStates: (seatMapId: string) => `seat_states/${string}`;
396
+ readonly seatState: (seatMapId: string, seatId: string) => `seat_states/${string}/${string}`;
397
+ readonly indexByEvent: (eventId: number) => `indexes/by_event/${number}`;
398
+ readonly indexBySubEvent: (subEventId: number) => `indexes/by_sub_event/${number}`;
399
+ readonly indexEntry: (eventId: number, seatMapId: string) => `indexes/by_event/${number}/${string}`;
400
+ readonly indexSubEventEntry: (subEventId: number, seatMapId: string) => `indexes/by_sub_event/${number}/${string}`;
401
+ };
402
+ /**
403
+ * Encode seat ID for Firebase path (Firebase doesn't allow certain characters)
404
+ * Replaces: . # $ [ ] /
405
+ */
406
+ declare function encodeSeatId(seatId: string): string;
407
+ /**
408
+ * Decode seat ID from Firebase path
409
+ */
410
+ declare function decodeSeatId(encodedId: string): string;
411
+
412
+ /**
413
+ * Converters between SeatMapConfig and Firebase data structures
414
+ */
415
+
416
+ /**
417
+ * Convert app SeatState to Firebase state (filters out 'selected' and 'hidden')
418
+ */
419
+ declare function toFirebaseState(state: SeatState): FirebaseSeatState;
420
+ /**
421
+ * Convert Firebase state to app SeatState
422
+ */
423
+ declare function fromFirebaseState(state: FirebaseSeatState): SeatState;
424
+ /**
425
+ * Convert SeatMapConfig to Firebase format
426
+ */
427
+ declare function toFirebaseSeatMap(config: SeatMapConfig, eventId: number, subEventId: number): FirebaseSeatMap;
428
+ /**
429
+ * Convert Firebase data to SeatMapConfig
430
+ */
431
+ declare function fromFirebaseSeatMap(firebase: FirebaseSeatMap, seatStates?: FirebaseSeatStates): SeatMapConfig;
432
+ /**
433
+ * Extract seat states from SeatMapConfig for the lightweight states node
434
+ */
435
+ declare function extractSeatStates(config: SeatMapConfig): FirebaseSeatStates;
436
+ /**
437
+ * Merge Firebase seat states with locally-derived reserved/unavailable arrays
438
+ */
439
+ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates): {
440
+ reservedSeats: string[];
441
+ unavailableSeats: string[];
442
+ };
443
+ /**
444
+ * Create index updates for a seat map
445
+ */
446
+ declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
447
+
448
+ export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type FirebaseCanvasConfig, type FirebaseConfigResult, type FirebaseHookOptions, type FirebaseIndexEntry, FirebasePaths, type FirebasePosition, type FirebaseSeat, type FirebaseSeatMap, type FirebaseSeatMapConfig, type FirebaseSeatMapMeta, type FirebaseSeatState, type FirebaseSeatStates, type FirebaseSeatStatesResult, type FirebaseStage, type ObjectConfig, type ObjectType, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, cloneConfig, createDefaultConfig, createIndexUpdates, decodeSeatId, deriveSeatArraysFromStates, downloadConfigAsFile, encodeSeatId, exportConfigAsJSON, extractSeatStates, formatDate, fromFirebaseSeatMap, fromFirebaseState, generateId, getSelectedSeats, importConfigFromJSON, toFirebaseSeatMap, toFirebaseState, updateConfigTimestamp, validateSeatMapConfig };
package/dist/index.d.ts CHANGED
@@ -3,8 +3,9 @@
3
3
  */
4
4
  type SeatState = "available" | "reserved" | "selected" | "unavailable" | "hidden";
5
5
  type SeatShape = "circle" | "square" | "rounded-square";
6
- type Tool = "select" | "seat" | "row" | "section" | "stage";
6
+ type Tool = "select" | "seat" | "row" | "section" | "object";
7
7
  type EditorMode = "edit" | "read";
8
+ type ObjectType = "stage" | "table" | "wall" | "barrier" | "dj-booth" | "bar" | "entry-exit" | "custom";
8
9
  /**
9
10
  * Seat data structure used in the editor and viewer
10
11
  */
@@ -50,6 +51,12 @@ interface StageConfig {
50
51
  color?: string;
51
52
  floorId?: string;
52
53
  }
54
+ /**
55
+ * Object configuration (extends StageConfig with object type support)
56
+ */
57
+ interface ObjectConfig extends StageConfig {
58
+ objectType?: ObjectType;
59
+ }
53
60
  /**
54
61
  * Floor configuration for multi-floor venues
55
62
  */
@@ -247,4 +254,195 @@ declare function importConfigFromJSON(jsonString: string): SeatMapConfig;
247
254
  */
248
255
  declare function downloadConfigAsFile(config: SeatMapConfig, filename?: string): void;
249
256
 
250
- export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, cloneConfig, createDefaultConfig, downloadConfigAsFile, exportConfigAsJSON, formatDate, generateId, getSelectedSeats, importConfigFromJSON, updateConfigTimestamp, validateSeatMapConfig };
257
+ /**
258
+ * Firebase Realtime Database type definitions for Seat Map Studio
259
+ */
260
+
261
+ /**
262
+ * Firebase seat state (excludes 'selected' which is client-only, 'hidden' which is filtered)
263
+ */
264
+ type FirebaseSeatState = 'available' | 'reserved' | 'unavailable';
265
+ /**
266
+ * Lightweight seat states map for high-frequency real-time updates
267
+ * Stored at: seat_states/{seatMapId}
268
+ */
269
+ interface FirebaseSeatStates {
270
+ [seatId: string]: FirebaseSeatState;
271
+ }
272
+ /**
273
+ * Position in Firebase (same structure as app)
274
+ */
275
+ interface FirebasePosition {
276
+ x: number;
277
+ y: number;
278
+ }
279
+ /**
280
+ * Seat data stored in Firebase
281
+ * Stored at: seatmaps/{seatMapId}/seats/{seatId}
282
+ */
283
+ interface FirebaseSeat {
284
+ position: FirebasePosition;
285
+ shape: SeatShape;
286
+ state: FirebaseSeatState;
287
+ sectionName?: string;
288
+ rowLabel?: string;
289
+ columnLabel?: string;
290
+ seatNumber?: string;
291
+ price?: number;
292
+ floorId?: string;
293
+ }
294
+ /**
295
+ * Stage configuration in Firebase
296
+ * Stored at: seatmaps/{seatMapId}/config/stages/{stageId}
297
+ */
298
+ interface FirebaseStage {
299
+ position: FirebasePosition;
300
+ config: {
301
+ label: string;
302
+ width: number;
303
+ height: number;
304
+ rotation?: number;
305
+ color?: string;
306
+ objectType?: ObjectType;
307
+ };
308
+ floorId?: string;
309
+ }
310
+ /**
311
+ * Canvas configuration in Firebase
312
+ * Stored at: seatmaps/{seatMapId}/config/canvas
313
+ */
314
+ interface FirebaseCanvasConfig {
315
+ width: number;
316
+ height: number;
317
+ backgroundColor: string;
318
+ }
319
+ /**
320
+ * Seat map metadata in Firebase
321
+ * Stored at: seatmaps/{seatMapId}/meta
322
+ */
323
+ interface FirebaseSeatMapMeta {
324
+ event_id: number;
325
+ sub_event_id: number;
326
+ name: string;
327
+ venue?: string;
328
+ capacity?: number;
329
+ updated_at: number;
330
+ version: string;
331
+ }
332
+ /**
333
+ * Design configuration in Firebase (excludes seats which are separate)
334
+ * Stored at: seatmaps/{seatMapId}/config
335
+ */
336
+ interface FirebaseSeatMapConfig {
337
+ canvas: FirebaseCanvasConfig;
338
+ colors: ColorSettings;
339
+ floors?: Record<string, FloorConfig>;
340
+ stages?: Record<string, FirebaseStage>;
341
+ }
342
+ /**
343
+ * Complete seat map data structure in Firebase
344
+ * Stored at: seatmaps/{seatMapId}
345
+ */
346
+ interface FirebaseSeatMap {
347
+ meta: FirebaseSeatMapMeta;
348
+ config: FirebaseSeatMapConfig;
349
+ seats: Record<string, FirebaseSeat>;
350
+ }
351
+ /**
352
+ * Index entry for seat map lookup by event
353
+ * Stored at: indexes/by_event/{eventId}/{seatMapId}
354
+ */
355
+ type FirebaseIndexEntry = true;
356
+ /**
357
+ * Options for Firebase real-time hooks
358
+ */
359
+ interface FirebaseHookOptions {
360
+ enabled?: boolean;
361
+ onError?: (error: Error) => void;
362
+ }
363
+ /**
364
+ * Result from Firebase seat states hook
365
+ */
366
+ interface FirebaseSeatStatesResult {
367
+ states: FirebaseSeatStates | null;
368
+ loading: boolean;
369
+ error: Error | null;
370
+ lastUpdated: number | null;
371
+ }
372
+ /**
373
+ * Result from Firebase config hook
374
+ */
375
+ interface FirebaseConfigResult {
376
+ config: FirebaseSeatMap | null;
377
+ loading: boolean;
378
+ error: Error | null;
379
+ refetch: () => Promise<void>;
380
+ }
381
+
382
+ /**
383
+ * Firebase Realtime Database path utilities
384
+ * Provides type-safe path builders for all Firebase nodes
385
+ */
386
+ /**
387
+ * All Firebase database paths used by Seat Map Studio
388
+ */
389
+ declare const FirebasePaths: {
390
+ readonly seatmap: (seatMapId: string) => `seatmaps/${string}`;
391
+ readonly seatmapMeta: (seatMapId: string) => `seatmaps/${string}/meta`;
392
+ readonly seatmapConfig: (seatMapId: string) => `seatmaps/${string}/config`;
393
+ readonly seatmapSeats: (seatMapId: string) => `seatmaps/${string}/seats`;
394
+ readonly seatmapSeat: (seatMapId: string, seatId: string) => `seatmaps/${string}/seats/${string}`;
395
+ readonly seatStates: (seatMapId: string) => `seat_states/${string}`;
396
+ readonly seatState: (seatMapId: string, seatId: string) => `seat_states/${string}/${string}`;
397
+ readonly indexByEvent: (eventId: number) => `indexes/by_event/${number}`;
398
+ readonly indexBySubEvent: (subEventId: number) => `indexes/by_sub_event/${number}`;
399
+ readonly indexEntry: (eventId: number, seatMapId: string) => `indexes/by_event/${number}/${string}`;
400
+ readonly indexSubEventEntry: (subEventId: number, seatMapId: string) => `indexes/by_sub_event/${number}/${string}`;
401
+ };
402
+ /**
403
+ * Encode seat ID for Firebase path (Firebase doesn't allow certain characters)
404
+ * Replaces: . # $ [ ] /
405
+ */
406
+ declare function encodeSeatId(seatId: string): string;
407
+ /**
408
+ * Decode seat ID from Firebase path
409
+ */
410
+ declare function decodeSeatId(encodedId: string): string;
411
+
412
+ /**
413
+ * Converters between SeatMapConfig and Firebase data structures
414
+ */
415
+
416
+ /**
417
+ * Convert app SeatState to Firebase state (filters out 'selected' and 'hidden')
418
+ */
419
+ declare function toFirebaseState(state: SeatState): FirebaseSeatState;
420
+ /**
421
+ * Convert Firebase state to app SeatState
422
+ */
423
+ declare function fromFirebaseState(state: FirebaseSeatState): SeatState;
424
+ /**
425
+ * Convert SeatMapConfig to Firebase format
426
+ */
427
+ declare function toFirebaseSeatMap(config: SeatMapConfig, eventId: number, subEventId: number): FirebaseSeatMap;
428
+ /**
429
+ * Convert Firebase data to SeatMapConfig
430
+ */
431
+ declare function fromFirebaseSeatMap(firebase: FirebaseSeatMap, seatStates?: FirebaseSeatStates): SeatMapConfig;
432
+ /**
433
+ * Extract seat states from SeatMapConfig for the lightweight states node
434
+ */
435
+ declare function extractSeatStates(config: SeatMapConfig): FirebaseSeatStates;
436
+ /**
437
+ * Merge Firebase seat states with locally-derived reserved/unavailable arrays
438
+ */
439
+ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates): {
440
+ reservedSeats: string[];
441
+ unavailableSeats: string[];
442
+ };
443
+ /**
444
+ * Create index updates for a seat map
445
+ */
446
+ declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
447
+
448
+ export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type FirebaseCanvasConfig, type FirebaseConfigResult, type FirebaseHookOptions, type FirebaseIndexEntry, FirebasePaths, type FirebasePosition, type FirebaseSeat, type FirebaseSeatMap, type FirebaseSeatMapConfig, type FirebaseSeatMapMeta, type FirebaseSeatState, type FirebaseSeatStates, type FirebaseSeatStatesResult, type FirebaseStage, type ObjectConfig, type ObjectType, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, cloneConfig, createDefaultConfig, createIndexUpdates, decodeSeatId, deriveSeatArraysFromStates, downloadConfigAsFile, encodeSeatId, exportConfigAsJSON, extractSeatStates, formatDate, fromFirebaseSeatMap, fromFirebaseState, generateId, getSelectedSeats, importConfigFromJSON, toFirebaseSeatMap, toFirebaseState, updateConfigTimestamp, validateSeatMapConfig };
package/dist/index.js CHANGED
@@ -21,18 +21,28 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_COLORS: () => DEFAULT_COLORS,
24
+ FirebasePaths: () => FirebasePaths,
24
25
  applySeatStateOverrides: () => applySeatStateOverrides,
25
26
  calculateAvailableSeats: () => calculateAvailableSeats,
26
27
  calculateCapacity: () => calculateCapacity,
27
28
  calculateSeatPrice: () => calculateSeatPrice,
28
29
  cloneConfig: () => cloneConfig,
29
30
  createDefaultConfig: () => createDefaultConfig,
31
+ createIndexUpdates: () => createIndexUpdates,
32
+ decodeSeatId: () => decodeSeatId,
33
+ deriveSeatArraysFromStates: () => deriveSeatArraysFromStates,
30
34
  downloadConfigAsFile: () => downloadConfigAsFile,
35
+ encodeSeatId: () => encodeSeatId,
31
36
  exportConfigAsJSON: () => exportConfigAsJSON,
37
+ extractSeatStates: () => extractSeatStates,
32
38
  formatDate: () => formatDate,
39
+ fromFirebaseSeatMap: () => fromFirebaseSeatMap,
40
+ fromFirebaseState: () => fromFirebaseState,
33
41
  generateId: () => generateId,
34
42
  getSelectedSeats: () => getSelectedSeats,
35
43
  importConfigFromJSON: () => importConfigFromJSON,
44
+ toFirebaseSeatMap: () => toFirebaseSeatMap,
45
+ toFirebaseState: () => toFirebaseState,
36
46
  updateConfigTimestamp: () => updateConfigTimestamp,
37
47
  validateSeatMapConfig: () => validateSeatMapConfig
38
48
  });
@@ -262,21 +272,222 @@ function downloadConfigAsFile(config, filename = "seat-map-config.json") {
262
272
  document.body.removeChild(link);
263
273
  URL.revokeObjectURL(url);
264
274
  }
275
+
276
+ // src/firebase/schema.ts
277
+ var FirebasePaths = {
278
+ // Seat map nodes
279
+ seatmap: (seatMapId) => `seatmaps/${seatMapId}`,
280
+ seatmapMeta: (seatMapId) => `seatmaps/${seatMapId}/meta`,
281
+ seatmapConfig: (seatMapId) => `seatmaps/${seatMapId}/config`,
282
+ seatmapSeats: (seatMapId) => `seatmaps/${seatMapId}/seats`,
283
+ seatmapSeat: (seatMapId, seatId) => `seatmaps/${seatMapId}/seats/${seatId}`,
284
+ // Lightweight seat states node (for high-frequency updates)
285
+ seatStates: (seatMapId) => `seat_states/${seatMapId}`,
286
+ seatState: (seatMapId, seatId) => `seat_states/${seatMapId}/${seatId}`,
287
+ // Index nodes for efficient lookups
288
+ indexByEvent: (eventId) => `indexes/by_event/${eventId}`,
289
+ indexBySubEvent: (subEventId) => `indexes/by_sub_event/${subEventId}`,
290
+ indexEntry: (eventId, seatMapId) => `indexes/by_event/${eventId}/${seatMapId}`,
291
+ indexSubEventEntry: (subEventId, seatMapId) => `indexes/by_sub_event/${subEventId}/${seatMapId}`
292
+ };
293
+ function encodeSeatId(seatId) {
294
+ return seatId.replace(/\./g, "%2E").replace(/#/g, "%23").replace(/\$/g, "%24").replace(/\[/g, "%5B").replace(/\]/g, "%5D").replace(/\//g, "%2F");
295
+ }
296
+ function decodeSeatId(encodedId) {
297
+ return encodedId.replace(/%2E/g, ".").replace(/%23/g, "#").replace(/%24/g, "$").replace(/%5B/g, "[").replace(/%5D/g, "]").replace(/%2F/g, "/");
298
+ }
299
+
300
+ // src/firebase/converters.ts
301
+ function removeUndefined(obj) {
302
+ if (typeof obj !== "object" || obj === null) return obj;
303
+ const result = {};
304
+ for (const [key, value] of Object.entries(obj)) {
305
+ if (value !== void 0) {
306
+ result[key] = value;
307
+ }
308
+ }
309
+ return result;
310
+ }
311
+ function toFirebaseState(state) {
312
+ if (state === "selected") return "available";
313
+ if (state === "hidden") return "unavailable";
314
+ return state;
315
+ }
316
+ function fromFirebaseState(state) {
317
+ return state;
318
+ }
319
+ function toFirebaseSeatMap(config, eventId, subEventId) {
320
+ const meta = removeUndefined({
321
+ event_id: eventId,
322
+ sub_event_id: subEventId,
323
+ name: config.metadata.name,
324
+ venue: config.metadata.venue,
325
+ capacity: config.metadata.capacity,
326
+ updated_at: Date.now(),
327
+ version: config.version
328
+ });
329
+ const seats = {};
330
+ for (const seat of config.seats) {
331
+ const encodedId = encodeSeatId(seat.id);
332
+ seats[encodedId] = removeUndefined({
333
+ position: seat.position,
334
+ shape: seat.shape,
335
+ state: toFirebaseState(seat.state),
336
+ sectionName: seat.sectionName,
337
+ rowLabel: seat.rowLabel,
338
+ columnLabel: seat.columnLabel,
339
+ seatNumber: seat.seatNumber,
340
+ price: seat.price,
341
+ floorId: seat.floorId
342
+ });
343
+ }
344
+ let stages = null;
345
+ if (config.stages && config.stages.length > 0) {
346
+ stages = {};
347
+ for (const stage of config.stages) {
348
+ stages[stage.id] = removeUndefined({
349
+ position: stage.position,
350
+ config: removeUndefined({
351
+ label: stage.config.label,
352
+ width: stage.config.width,
353
+ height: stage.config.height,
354
+ rotation: stage.config.rotation,
355
+ color: stage.config.color
356
+ }),
357
+ floorId: stage.floorId
358
+ });
359
+ }
360
+ }
361
+ let floors = null;
362
+ if (config.floors && config.floors.length > 0) {
363
+ floors = {};
364
+ for (const floor of config.floors) {
365
+ floors[floor.id] = {
366
+ id: floor.id,
367
+ name: floor.name,
368
+ order: floor.order,
369
+ ...floor.color !== void 0 ? { color: floor.color } : {}
370
+ };
371
+ }
372
+ }
373
+ const firebaseConfig = {
374
+ canvas: config.canvas,
375
+ colors: config.colors
376
+ };
377
+ if (floors) {
378
+ firebaseConfig.floors = floors;
379
+ }
380
+ if (stages) {
381
+ firebaseConfig.stages = stages;
382
+ }
383
+ return {
384
+ meta,
385
+ config: firebaseConfig,
386
+ seats
387
+ };
388
+ }
389
+ function fromFirebaseSeatMap(firebase, seatStates) {
390
+ const seats = Object.entries(firebase.seats || {}).map(
391
+ ([encodedId, seat]) => {
392
+ const id = decodeSeatId(encodedId);
393
+ const state = seatStates?.[encodedId] ?? seatStates?.[id] ?? seat.state;
394
+ return {
395
+ id,
396
+ position: seat.position,
397
+ shape: seat.shape,
398
+ state: fromFirebaseState(state),
399
+ sectionName: seat.sectionName,
400
+ rowLabel: seat.rowLabel,
401
+ columnLabel: seat.columnLabel,
402
+ seatNumber: seat.seatNumber,
403
+ price: seat.price,
404
+ floorId: seat.floorId
405
+ };
406
+ }
407
+ );
408
+ const stages = firebase.config.stages ? Object.entries(firebase.config.stages).map(([id, stage]) => ({
409
+ id,
410
+ position: stage.position,
411
+ config: {
412
+ label: stage.config.label,
413
+ width: stage.config.width,
414
+ height: stage.config.height,
415
+ rotation: stage.config.rotation,
416
+ color: stage.config.color
417
+ },
418
+ floorId: stage.floorId
419
+ })) : void 0;
420
+ const floors = firebase.config.floors ? Object.values(firebase.config.floors).sort((a, b) => a.order - b.order) : void 0;
421
+ return {
422
+ version: firebase.meta.version,
423
+ metadata: {
424
+ name: firebase.meta.name,
425
+ venue: firebase.meta.venue,
426
+ capacity: firebase.meta.capacity,
427
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
428
+ // Not stored in Firebase
429
+ updatedAt: new Date(firebase.meta.updated_at).toISOString()
430
+ },
431
+ canvas: firebase.config.canvas,
432
+ colors: firebase.config.colors,
433
+ seats,
434
+ stages,
435
+ floors
436
+ };
437
+ }
438
+ function extractSeatStates(config) {
439
+ const states = {};
440
+ for (const seat of config.seats) {
441
+ if (seat.state === "hidden") continue;
442
+ const encodedId = encodeSeatId(seat.id);
443
+ states[encodedId] = toFirebaseState(seat.state);
444
+ }
445
+ return states;
446
+ }
447
+ function deriveSeatArraysFromStates(states) {
448
+ const reservedSeats = [];
449
+ const unavailableSeats = [];
450
+ for (const [encodedId, state] of Object.entries(states)) {
451
+ const id = decodeSeatId(encodedId);
452
+ if (state === "reserved") {
453
+ reservedSeats.push(id);
454
+ } else if (state === "unavailable") {
455
+ unavailableSeats.push(id);
456
+ }
457
+ }
458
+ return { reservedSeats, unavailableSeats };
459
+ }
460
+ function createIndexUpdates(seatMapId, eventId, subEventId) {
461
+ return {
462
+ [`indexes/by_event/${eventId}/${seatMapId}`]: true,
463
+ [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
464
+ };
465
+ }
265
466
  // Annotate the CommonJS export names for ESM import in node:
266
467
  0 && (module.exports = {
267
468
  DEFAULT_COLORS,
469
+ FirebasePaths,
268
470
  applySeatStateOverrides,
269
471
  calculateAvailableSeats,
270
472
  calculateCapacity,
271
473
  calculateSeatPrice,
272
474
  cloneConfig,
273
475
  createDefaultConfig,
476
+ createIndexUpdates,
477
+ decodeSeatId,
478
+ deriveSeatArraysFromStates,
274
479
  downloadConfigAsFile,
480
+ encodeSeatId,
275
481
  exportConfigAsJSON,
482
+ extractSeatStates,
276
483
  formatDate,
484
+ fromFirebaseSeatMap,
485
+ fromFirebaseState,
277
486
  generateId,
278
487
  getSelectedSeats,
279
488
  importConfigFromJSON,
489
+ toFirebaseSeatMap,
490
+ toFirebaseState,
280
491
  updateConfigTimestamp,
281
492
  validateSeatMapConfig
282
493
  });
package/dist/index.mjs CHANGED
@@ -222,20 +222,221 @@ function downloadConfigAsFile(config, filename = "seat-map-config.json") {
222
222
  document.body.removeChild(link);
223
223
  URL.revokeObjectURL(url);
224
224
  }
225
+
226
+ // src/firebase/schema.ts
227
+ var FirebasePaths = {
228
+ // Seat map nodes
229
+ seatmap: (seatMapId) => `seatmaps/${seatMapId}`,
230
+ seatmapMeta: (seatMapId) => `seatmaps/${seatMapId}/meta`,
231
+ seatmapConfig: (seatMapId) => `seatmaps/${seatMapId}/config`,
232
+ seatmapSeats: (seatMapId) => `seatmaps/${seatMapId}/seats`,
233
+ seatmapSeat: (seatMapId, seatId) => `seatmaps/${seatMapId}/seats/${seatId}`,
234
+ // Lightweight seat states node (for high-frequency updates)
235
+ seatStates: (seatMapId) => `seat_states/${seatMapId}`,
236
+ seatState: (seatMapId, seatId) => `seat_states/${seatMapId}/${seatId}`,
237
+ // Index nodes for efficient lookups
238
+ indexByEvent: (eventId) => `indexes/by_event/${eventId}`,
239
+ indexBySubEvent: (subEventId) => `indexes/by_sub_event/${subEventId}`,
240
+ indexEntry: (eventId, seatMapId) => `indexes/by_event/${eventId}/${seatMapId}`,
241
+ indexSubEventEntry: (subEventId, seatMapId) => `indexes/by_sub_event/${subEventId}/${seatMapId}`
242
+ };
243
+ function encodeSeatId(seatId) {
244
+ return seatId.replace(/\./g, "%2E").replace(/#/g, "%23").replace(/\$/g, "%24").replace(/\[/g, "%5B").replace(/\]/g, "%5D").replace(/\//g, "%2F");
245
+ }
246
+ function decodeSeatId(encodedId) {
247
+ return encodedId.replace(/%2E/g, ".").replace(/%23/g, "#").replace(/%24/g, "$").replace(/%5B/g, "[").replace(/%5D/g, "]").replace(/%2F/g, "/");
248
+ }
249
+
250
+ // src/firebase/converters.ts
251
+ function removeUndefined(obj) {
252
+ if (typeof obj !== "object" || obj === null) return obj;
253
+ const result = {};
254
+ for (const [key, value] of Object.entries(obj)) {
255
+ if (value !== void 0) {
256
+ result[key] = value;
257
+ }
258
+ }
259
+ return result;
260
+ }
261
+ function toFirebaseState(state) {
262
+ if (state === "selected") return "available";
263
+ if (state === "hidden") return "unavailable";
264
+ return state;
265
+ }
266
+ function fromFirebaseState(state) {
267
+ return state;
268
+ }
269
+ function toFirebaseSeatMap(config, eventId, subEventId) {
270
+ const meta = removeUndefined({
271
+ event_id: eventId,
272
+ sub_event_id: subEventId,
273
+ name: config.metadata.name,
274
+ venue: config.metadata.venue,
275
+ capacity: config.metadata.capacity,
276
+ updated_at: Date.now(),
277
+ version: config.version
278
+ });
279
+ const seats = {};
280
+ for (const seat of config.seats) {
281
+ const encodedId = encodeSeatId(seat.id);
282
+ seats[encodedId] = removeUndefined({
283
+ position: seat.position,
284
+ shape: seat.shape,
285
+ state: toFirebaseState(seat.state),
286
+ sectionName: seat.sectionName,
287
+ rowLabel: seat.rowLabel,
288
+ columnLabel: seat.columnLabel,
289
+ seatNumber: seat.seatNumber,
290
+ price: seat.price,
291
+ floorId: seat.floorId
292
+ });
293
+ }
294
+ let stages = null;
295
+ if (config.stages && config.stages.length > 0) {
296
+ stages = {};
297
+ for (const stage of config.stages) {
298
+ stages[stage.id] = removeUndefined({
299
+ position: stage.position,
300
+ config: removeUndefined({
301
+ label: stage.config.label,
302
+ width: stage.config.width,
303
+ height: stage.config.height,
304
+ rotation: stage.config.rotation,
305
+ color: stage.config.color
306
+ }),
307
+ floorId: stage.floorId
308
+ });
309
+ }
310
+ }
311
+ let floors = null;
312
+ if (config.floors && config.floors.length > 0) {
313
+ floors = {};
314
+ for (const floor of config.floors) {
315
+ floors[floor.id] = {
316
+ id: floor.id,
317
+ name: floor.name,
318
+ order: floor.order,
319
+ ...floor.color !== void 0 ? { color: floor.color } : {}
320
+ };
321
+ }
322
+ }
323
+ const firebaseConfig = {
324
+ canvas: config.canvas,
325
+ colors: config.colors
326
+ };
327
+ if (floors) {
328
+ firebaseConfig.floors = floors;
329
+ }
330
+ if (stages) {
331
+ firebaseConfig.stages = stages;
332
+ }
333
+ return {
334
+ meta,
335
+ config: firebaseConfig,
336
+ seats
337
+ };
338
+ }
339
+ function fromFirebaseSeatMap(firebase, seatStates) {
340
+ const seats = Object.entries(firebase.seats || {}).map(
341
+ ([encodedId, seat]) => {
342
+ const id = decodeSeatId(encodedId);
343
+ const state = seatStates?.[encodedId] ?? seatStates?.[id] ?? seat.state;
344
+ return {
345
+ id,
346
+ position: seat.position,
347
+ shape: seat.shape,
348
+ state: fromFirebaseState(state),
349
+ sectionName: seat.sectionName,
350
+ rowLabel: seat.rowLabel,
351
+ columnLabel: seat.columnLabel,
352
+ seatNumber: seat.seatNumber,
353
+ price: seat.price,
354
+ floorId: seat.floorId
355
+ };
356
+ }
357
+ );
358
+ const stages = firebase.config.stages ? Object.entries(firebase.config.stages).map(([id, stage]) => ({
359
+ id,
360
+ position: stage.position,
361
+ config: {
362
+ label: stage.config.label,
363
+ width: stage.config.width,
364
+ height: stage.config.height,
365
+ rotation: stage.config.rotation,
366
+ color: stage.config.color
367
+ },
368
+ floorId: stage.floorId
369
+ })) : void 0;
370
+ const floors = firebase.config.floors ? Object.values(firebase.config.floors).sort((a, b) => a.order - b.order) : void 0;
371
+ return {
372
+ version: firebase.meta.version,
373
+ metadata: {
374
+ name: firebase.meta.name,
375
+ venue: firebase.meta.venue,
376
+ capacity: firebase.meta.capacity,
377
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
378
+ // Not stored in Firebase
379
+ updatedAt: new Date(firebase.meta.updated_at).toISOString()
380
+ },
381
+ canvas: firebase.config.canvas,
382
+ colors: firebase.config.colors,
383
+ seats,
384
+ stages,
385
+ floors
386
+ };
387
+ }
388
+ function extractSeatStates(config) {
389
+ const states = {};
390
+ for (const seat of config.seats) {
391
+ if (seat.state === "hidden") continue;
392
+ const encodedId = encodeSeatId(seat.id);
393
+ states[encodedId] = toFirebaseState(seat.state);
394
+ }
395
+ return states;
396
+ }
397
+ function deriveSeatArraysFromStates(states) {
398
+ const reservedSeats = [];
399
+ const unavailableSeats = [];
400
+ for (const [encodedId, state] of Object.entries(states)) {
401
+ const id = decodeSeatId(encodedId);
402
+ if (state === "reserved") {
403
+ reservedSeats.push(id);
404
+ } else if (state === "unavailable") {
405
+ unavailableSeats.push(id);
406
+ }
407
+ }
408
+ return { reservedSeats, unavailableSeats };
409
+ }
410
+ function createIndexUpdates(seatMapId, eventId, subEventId) {
411
+ return {
412
+ [`indexes/by_event/${eventId}/${seatMapId}`]: true,
413
+ [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
414
+ };
415
+ }
225
416
  export {
226
417
  DEFAULT_COLORS,
418
+ FirebasePaths,
227
419
  applySeatStateOverrides,
228
420
  calculateAvailableSeats,
229
421
  calculateCapacity,
230
422
  calculateSeatPrice,
231
423
  cloneConfig,
232
424
  createDefaultConfig,
425
+ createIndexUpdates,
426
+ decodeSeatId,
427
+ deriveSeatArraysFromStates,
233
428
  downloadConfigAsFile,
429
+ encodeSeatId,
234
430
  exportConfigAsJSON,
431
+ extractSeatStates,
235
432
  formatDate,
433
+ fromFirebaseSeatMap,
434
+ fromFirebaseState,
236
435
  generateId,
237
436
  getSelectedSeats,
238
437
  importConfigFromJSON,
438
+ toFirebaseSeatMap,
439
+ toFirebaseState,
239
440
  updateConfigTimestamp,
240
441
  validateSeatMapConfig
241
442
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/shared",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Shared types and utilities for seat-map-studio packages",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",