@zonetrix/shared 2.2.0 → 2.4.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
@@ -254,4 +254,225 @@ declare function importConfigFromJSON(jsonString: string): SeatMapConfig;
254
254
  */
255
255
  declare function downloadConfigAsFile(config: SeatMapConfig, filename?: string): void;
256
256
 
257
- export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, 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, 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 type (excludes 'selected' which is client-only, 'hidden' which is filtered)
263
+ */
264
+ type FirebaseSeatState = 'available' | 'reserved' | 'unavailable';
265
+ /**
266
+ * Seat state value stored in Firebase (for reserved/unavailable seats)
267
+ * Available seats: key is deleted (doesn't exist)
268
+ * Reserved/Unavailable seats: object with state, userId, and timestamp
269
+ */
270
+ interface FirebaseSeatStateValue {
271
+ state: 'reserved' | 'unavailable';
272
+ userId?: string;
273
+ timestamp?: number;
274
+ }
275
+ /**
276
+ * Entry in the seat states map - null/undefined means available
277
+ */
278
+ type FirebaseSeatStateEntry = FirebaseSeatStateValue | null;
279
+ /**
280
+ * Lightweight seat states map for high-frequency real-time updates
281
+ * Stored at: seat_states/{seatMapId}
282
+ *
283
+ * Keys: encoded seat IDs
284
+ * Values: null = available, object = reserved/unavailable with user info
285
+ */
286
+ interface FirebaseSeatStates {
287
+ [seatId: string]: FirebaseSeatStateEntry;
288
+ }
289
+ /**
290
+ * Position in Firebase (same structure as app)
291
+ */
292
+ interface FirebasePosition {
293
+ x: number;
294
+ y: number;
295
+ }
296
+ /**
297
+ * Seat data stored in Firebase
298
+ * Stored at: seatmaps/{seatMapId}/seats/{seatId}
299
+ */
300
+ interface FirebaseSeat {
301
+ position: FirebasePosition;
302
+ shape: SeatShape;
303
+ state: FirebaseSeatState;
304
+ sectionName?: string;
305
+ rowLabel?: string;
306
+ columnLabel?: string;
307
+ seatNumber?: string;
308
+ price?: number;
309
+ floorId?: string;
310
+ }
311
+ /**
312
+ * Stage configuration in Firebase
313
+ * Stored at: seatmaps/{seatMapId}/config/stages/{stageId}
314
+ */
315
+ interface FirebaseStage {
316
+ position: FirebasePosition;
317
+ config: {
318
+ label: string;
319
+ width: number;
320
+ height: number;
321
+ rotation?: number;
322
+ color?: string;
323
+ objectType?: ObjectType;
324
+ };
325
+ floorId?: string;
326
+ }
327
+ /**
328
+ * Canvas configuration in Firebase
329
+ * Stored at: seatmaps/{seatMapId}/config/canvas
330
+ */
331
+ interface FirebaseCanvasConfig {
332
+ width: number;
333
+ height: number;
334
+ backgroundColor: string;
335
+ }
336
+ /**
337
+ * Seat map metadata in Firebase
338
+ * Stored at: seatmaps/{seatMapId}/meta
339
+ */
340
+ interface FirebaseSeatMapMeta {
341
+ event_id: number;
342
+ sub_event_id: number;
343
+ name: string;
344
+ venue?: string;
345
+ capacity?: number;
346
+ updated_at: number;
347
+ version: string;
348
+ }
349
+ /**
350
+ * Design configuration in Firebase (excludes seats which are separate)
351
+ * Stored at: seatmaps/{seatMapId}/config
352
+ */
353
+ interface FirebaseSeatMapConfig {
354
+ canvas: FirebaseCanvasConfig;
355
+ colors: ColorSettings;
356
+ floors?: Record<string, FloorConfig>;
357
+ stages?: Record<string, FirebaseStage>;
358
+ }
359
+ /**
360
+ * Complete seat map data structure in Firebase
361
+ * Stored at: seatmaps/{seatMapId}
362
+ */
363
+ interface FirebaseSeatMap {
364
+ meta: FirebaseSeatMapMeta;
365
+ config: FirebaseSeatMapConfig;
366
+ seats: Record<string, FirebaseSeat>;
367
+ }
368
+ /**
369
+ * Index entry for seat map lookup by event
370
+ * Stored at: indexes/by_event/{eventId}/{seatMapId}
371
+ */
372
+ type FirebaseIndexEntry = true;
373
+ /**
374
+ * Options for Firebase real-time hooks
375
+ */
376
+ interface FirebaseHookOptions {
377
+ enabled?: boolean;
378
+ onError?: (error: Error) => void;
379
+ }
380
+ /**
381
+ * Result from Firebase seat states hook
382
+ */
383
+ interface FirebaseSeatStatesResult {
384
+ states: FirebaseSeatStates | null;
385
+ loading: boolean;
386
+ error: Error | null;
387
+ lastUpdated: number | null;
388
+ myReservedSeats: string[];
389
+ otherReservedSeats: string[];
390
+ unavailableSeats: string[];
391
+ }
392
+ /**
393
+ * Result from Firebase config hook
394
+ */
395
+ interface FirebaseConfigResult {
396
+ config: FirebaseSeatMap | null;
397
+ loading: boolean;
398
+ error: Error | null;
399
+ refetch: () => Promise<void>;
400
+ }
401
+
402
+ /**
403
+ * Firebase Realtime Database path utilities
404
+ * Provides type-safe path builders for all Firebase nodes
405
+ */
406
+ /**
407
+ * All Firebase database paths used by Seat Map Studio
408
+ */
409
+ declare const FirebasePaths: {
410
+ readonly seatmap: (seatMapId: string) => `seatmaps/${string}`;
411
+ readonly seatmapMeta: (seatMapId: string) => `seatmaps/${string}/meta`;
412
+ readonly seatmapConfig: (seatMapId: string) => `seatmaps/${string}/config`;
413
+ readonly seatmapSeats: (seatMapId: string) => `seatmaps/${string}/seats`;
414
+ readonly seatmapSeat: (seatMapId: string, seatId: string) => `seatmaps/${string}/seats/${string}`;
415
+ readonly seatStates: (seatMapId: string) => `seat_states/${string}`;
416
+ readonly seatState: (seatMapId: string, seatId: string) => `seat_states/${string}/${string}`;
417
+ readonly indexByEvent: (eventId: number) => `indexes/by_event/${number}`;
418
+ readonly indexBySubEvent: (subEventId: number) => `indexes/by_sub_event/${number}`;
419
+ readonly indexEntry: (eventId: number, seatMapId: string) => `indexes/by_event/${number}/${string}`;
420
+ readonly indexSubEventEntry: (subEventId: number, seatMapId: string) => `indexes/by_sub_event/${number}/${string}`;
421
+ };
422
+ /**
423
+ * Encode seat ID for Firebase path (Firebase doesn't allow certain characters)
424
+ * Replaces: . # $ [ ] /
425
+ */
426
+ declare function encodeSeatId(seatId: string): string;
427
+ /**
428
+ * Decode seat ID from Firebase path
429
+ */
430
+ declare function decodeSeatId(encodedId: string): string;
431
+
432
+ /**
433
+ * Converters between SeatMapConfig and Firebase data structures
434
+ */
435
+
436
+ /**
437
+ * Convert app SeatState to Firebase state (filters out 'selected' and 'hidden')
438
+ */
439
+ declare function toFirebaseState(state: SeatState): FirebaseSeatState;
440
+ /**
441
+ * Convert Firebase state to app SeatState
442
+ */
443
+ declare function fromFirebaseState(state: FirebaseSeatState): SeatState;
444
+ /**
445
+ * Convert SeatMapConfig to Firebase format
446
+ */
447
+ declare function toFirebaseSeatMap(config: SeatMapConfig, eventId: number, subEventId: number): FirebaseSeatMap;
448
+ /**
449
+ * Convert Firebase data to SeatMapConfig
450
+ */
451
+ declare function fromFirebaseSeatMap(firebase: FirebaseSeatMap, seatStates?: FirebaseSeatStates): SeatMapConfig;
452
+ /**
453
+ * Extract seat states from SeatMapConfig for the lightweight states node
454
+ *
455
+ * Only stores non-available states (reserved/unavailable).
456
+ * Available seats: key is not stored (viewer treats missing keys as available)
457
+ * Hidden seats: not stored (filtered out by viewer)
458
+ */
459
+ declare function extractSeatStates(config: SeatMapConfig): FirebaseSeatStates;
460
+ /**
461
+ * Derive user-aware seat arrays from Firebase seat states
462
+ *
463
+ * @param states - Firebase seat states map
464
+ * @param currentUserId - Optional current user ID for user-aware derivation
465
+ * @returns Object with myReservedSeats, otherReservedSeats, and unavailableSeats arrays
466
+ */
467
+ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates, currentUserId?: string): {
468
+ myReservedSeats: string[];
469
+ otherReservedSeats: string[];
470
+ unavailableSeats: string[];
471
+ reservedSeats: string[];
472
+ };
473
+ /**
474
+ * Create index updates for a seat map
475
+ */
476
+ declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
477
+
478
+ 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 FirebaseSeatStateEntry, type FirebaseSeatStateValue, 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
@@ -254,4 +254,225 @@ declare function importConfigFromJSON(jsonString: string): SeatMapConfig;
254
254
  */
255
255
  declare function downloadConfigAsFile(config: SeatMapConfig, filename?: string): void;
256
256
 
257
- export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, 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, 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 type (excludes 'selected' which is client-only, 'hidden' which is filtered)
263
+ */
264
+ type FirebaseSeatState = 'available' | 'reserved' | 'unavailable';
265
+ /**
266
+ * Seat state value stored in Firebase (for reserved/unavailable seats)
267
+ * Available seats: key is deleted (doesn't exist)
268
+ * Reserved/Unavailable seats: object with state, userId, and timestamp
269
+ */
270
+ interface FirebaseSeatStateValue {
271
+ state: 'reserved' | 'unavailable';
272
+ userId?: string;
273
+ timestamp?: number;
274
+ }
275
+ /**
276
+ * Entry in the seat states map - null/undefined means available
277
+ */
278
+ type FirebaseSeatStateEntry = FirebaseSeatStateValue | null;
279
+ /**
280
+ * Lightweight seat states map for high-frequency real-time updates
281
+ * Stored at: seat_states/{seatMapId}
282
+ *
283
+ * Keys: encoded seat IDs
284
+ * Values: null = available, object = reserved/unavailable with user info
285
+ */
286
+ interface FirebaseSeatStates {
287
+ [seatId: string]: FirebaseSeatStateEntry;
288
+ }
289
+ /**
290
+ * Position in Firebase (same structure as app)
291
+ */
292
+ interface FirebasePosition {
293
+ x: number;
294
+ y: number;
295
+ }
296
+ /**
297
+ * Seat data stored in Firebase
298
+ * Stored at: seatmaps/{seatMapId}/seats/{seatId}
299
+ */
300
+ interface FirebaseSeat {
301
+ position: FirebasePosition;
302
+ shape: SeatShape;
303
+ state: FirebaseSeatState;
304
+ sectionName?: string;
305
+ rowLabel?: string;
306
+ columnLabel?: string;
307
+ seatNumber?: string;
308
+ price?: number;
309
+ floorId?: string;
310
+ }
311
+ /**
312
+ * Stage configuration in Firebase
313
+ * Stored at: seatmaps/{seatMapId}/config/stages/{stageId}
314
+ */
315
+ interface FirebaseStage {
316
+ position: FirebasePosition;
317
+ config: {
318
+ label: string;
319
+ width: number;
320
+ height: number;
321
+ rotation?: number;
322
+ color?: string;
323
+ objectType?: ObjectType;
324
+ };
325
+ floorId?: string;
326
+ }
327
+ /**
328
+ * Canvas configuration in Firebase
329
+ * Stored at: seatmaps/{seatMapId}/config/canvas
330
+ */
331
+ interface FirebaseCanvasConfig {
332
+ width: number;
333
+ height: number;
334
+ backgroundColor: string;
335
+ }
336
+ /**
337
+ * Seat map metadata in Firebase
338
+ * Stored at: seatmaps/{seatMapId}/meta
339
+ */
340
+ interface FirebaseSeatMapMeta {
341
+ event_id: number;
342
+ sub_event_id: number;
343
+ name: string;
344
+ venue?: string;
345
+ capacity?: number;
346
+ updated_at: number;
347
+ version: string;
348
+ }
349
+ /**
350
+ * Design configuration in Firebase (excludes seats which are separate)
351
+ * Stored at: seatmaps/{seatMapId}/config
352
+ */
353
+ interface FirebaseSeatMapConfig {
354
+ canvas: FirebaseCanvasConfig;
355
+ colors: ColorSettings;
356
+ floors?: Record<string, FloorConfig>;
357
+ stages?: Record<string, FirebaseStage>;
358
+ }
359
+ /**
360
+ * Complete seat map data structure in Firebase
361
+ * Stored at: seatmaps/{seatMapId}
362
+ */
363
+ interface FirebaseSeatMap {
364
+ meta: FirebaseSeatMapMeta;
365
+ config: FirebaseSeatMapConfig;
366
+ seats: Record<string, FirebaseSeat>;
367
+ }
368
+ /**
369
+ * Index entry for seat map lookup by event
370
+ * Stored at: indexes/by_event/{eventId}/{seatMapId}
371
+ */
372
+ type FirebaseIndexEntry = true;
373
+ /**
374
+ * Options for Firebase real-time hooks
375
+ */
376
+ interface FirebaseHookOptions {
377
+ enabled?: boolean;
378
+ onError?: (error: Error) => void;
379
+ }
380
+ /**
381
+ * Result from Firebase seat states hook
382
+ */
383
+ interface FirebaseSeatStatesResult {
384
+ states: FirebaseSeatStates | null;
385
+ loading: boolean;
386
+ error: Error | null;
387
+ lastUpdated: number | null;
388
+ myReservedSeats: string[];
389
+ otherReservedSeats: string[];
390
+ unavailableSeats: string[];
391
+ }
392
+ /**
393
+ * Result from Firebase config hook
394
+ */
395
+ interface FirebaseConfigResult {
396
+ config: FirebaseSeatMap | null;
397
+ loading: boolean;
398
+ error: Error | null;
399
+ refetch: () => Promise<void>;
400
+ }
401
+
402
+ /**
403
+ * Firebase Realtime Database path utilities
404
+ * Provides type-safe path builders for all Firebase nodes
405
+ */
406
+ /**
407
+ * All Firebase database paths used by Seat Map Studio
408
+ */
409
+ declare const FirebasePaths: {
410
+ readonly seatmap: (seatMapId: string) => `seatmaps/${string}`;
411
+ readonly seatmapMeta: (seatMapId: string) => `seatmaps/${string}/meta`;
412
+ readonly seatmapConfig: (seatMapId: string) => `seatmaps/${string}/config`;
413
+ readonly seatmapSeats: (seatMapId: string) => `seatmaps/${string}/seats`;
414
+ readonly seatmapSeat: (seatMapId: string, seatId: string) => `seatmaps/${string}/seats/${string}`;
415
+ readonly seatStates: (seatMapId: string) => `seat_states/${string}`;
416
+ readonly seatState: (seatMapId: string, seatId: string) => `seat_states/${string}/${string}`;
417
+ readonly indexByEvent: (eventId: number) => `indexes/by_event/${number}`;
418
+ readonly indexBySubEvent: (subEventId: number) => `indexes/by_sub_event/${number}`;
419
+ readonly indexEntry: (eventId: number, seatMapId: string) => `indexes/by_event/${number}/${string}`;
420
+ readonly indexSubEventEntry: (subEventId: number, seatMapId: string) => `indexes/by_sub_event/${number}/${string}`;
421
+ };
422
+ /**
423
+ * Encode seat ID for Firebase path (Firebase doesn't allow certain characters)
424
+ * Replaces: . # $ [ ] /
425
+ */
426
+ declare function encodeSeatId(seatId: string): string;
427
+ /**
428
+ * Decode seat ID from Firebase path
429
+ */
430
+ declare function decodeSeatId(encodedId: string): string;
431
+
432
+ /**
433
+ * Converters between SeatMapConfig and Firebase data structures
434
+ */
435
+
436
+ /**
437
+ * Convert app SeatState to Firebase state (filters out 'selected' and 'hidden')
438
+ */
439
+ declare function toFirebaseState(state: SeatState): FirebaseSeatState;
440
+ /**
441
+ * Convert Firebase state to app SeatState
442
+ */
443
+ declare function fromFirebaseState(state: FirebaseSeatState): SeatState;
444
+ /**
445
+ * Convert SeatMapConfig to Firebase format
446
+ */
447
+ declare function toFirebaseSeatMap(config: SeatMapConfig, eventId: number, subEventId: number): FirebaseSeatMap;
448
+ /**
449
+ * Convert Firebase data to SeatMapConfig
450
+ */
451
+ declare function fromFirebaseSeatMap(firebase: FirebaseSeatMap, seatStates?: FirebaseSeatStates): SeatMapConfig;
452
+ /**
453
+ * Extract seat states from SeatMapConfig for the lightweight states node
454
+ *
455
+ * Only stores non-available states (reserved/unavailable).
456
+ * Available seats: key is not stored (viewer treats missing keys as available)
457
+ * Hidden seats: not stored (filtered out by viewer)
458
+ */
459
+ declare function extractSeatStates(config: SeatMapConfig): FirebaseSeatStates;
460
+ /**
461
+ * Derive user-aware seat arrays from Firebase seat states
462
+ *
463
+ * @param states - Firebase seat states map
464
+ * @param currentUserId - Optional current user ID for user-aware derivation
465
+ * @returns Object with myReservedSeats, otherReservedSeats, and unavailableSeats arrays
466
+ */
467
+ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates, currentUserId?: string): {
468
+ myReservedSeats: string[];
469
+ otherReservedSeats: string[];
470
+ unavailableSeats: string[];
471
+ reservedSeats: string[];
472
+ };
473
+ /**
474
+ * Create index updates for a seat map
475
+ */
476
+ declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
477
+
478
+ 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 FirebaseSeatStateEntry, type FirebaseSeatStateValue, 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,242 @@ 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
+ let effectiveState = seat.state;
394
+ const stateEntry = seatStates?.[encodedId] ?? seatStates?.[id];
395
+ if (stateEntry) {
396
+ effectiveState = stateEntry.state;
397
+ }
398
+ return {
399
+ id,
400
+ position: seat.position,
401
+ shape: seat.shape,
402
+ state: fromFirebaseState(effectiveState),
403
+ sectionName: seat.sectionName,
404
+ rowLabel: seat.rowLabel,
405
+ columnLabel: seat.columnLabel,
406
+ seatNumber: seat.seatNumber,
407
+ price: seat.price,
408
+ floorId: seat.floorId
409
+ };
410
+ }
411
+ );
412
+ const stages = firebase.config.stages ? Object.entries(firebase.config.stages).map(([id, stage]) => ({
413
+ id,
414
+ position: stage.position,
415
+ config: {
416
+ label: stage.config.label,
417
+ width: stage.config.width,
418
+ height: stage.config.height,
419
+ rotation: stage.config.rotation,
420
+ color: stage.config.color
421
+ },
422
+ floorId: stage.floorId
423
+ })) : void 0;
424
+ const floors = firebase.config.floors ? Object.values(firebase.config.floors).sort((a, b) => a.order - b.order) : void 0;
425
+ return {
426
+ version: firebase.meta.version,
427
+ metadata: {
428
+ name: firebase.meta.name,
429
+ venue: firebase.meta.venue,
430
+ capacity: firebase.meta.capacity,
431
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
432
+ // Not stored in Firebase
433
+ updatedAt: new Date(firebase.meta.updated_at).toISOString()
434
+ },
435
+ canvas: firebase.config.canvas,
436
+ colors: firebase.config.colors,
437
+ seats,
438
+ stages,
439
+ floors
440
+ };
441
+ }
442
+ function extractSeatStates(config) {
443
+ const states = {};
444
+ for (const seat of config.seats) {
445
+ if (seat.state === "hidden") continue;
446
+ if (seat.state === "available" || seat.state === "selected") continue;
447
+ const encodedId = encodeSeatId(seat.id);
448
+ states[encodedId] = {
449
+ state: seat.state,
450
+ timestamp: Date.now()
451
+ };
452
+ }
453
+ return states;
454
+ }
455
+ function deriveSeatArraysFromStates(states, currentUserId) {
456
+ const myReservedSeats = [];
457
+ const otherReservedSeats = [];
458
+ const unavailableSeats = [];
459
+ for (const [encodedId, entry] of Object.entries(states)) {
460
+ if (!entry) continue;
461
+ const id = decodeSeatId(encodedId);
462
+ if (entry.state === "unavailable") {
463
+ unavailableSeats.push(id);
464
+ } else if (entry.state === "reserved") {
465
+ if (currentUserId && entry.userId === currentUserId) {
466
+ myReservedSeats.push(id);
467
+ } else {
468
+ otherReservedSeats.push(id);
469
+ }
470
+ }
471
+ }
472
+ return {
473
+ myReservedSeats,
474
+ otherReservedSeats,
475
+ unavailableSeats,
476
+ // Legacy: all reserved seats (for non-user-aware usage)
477
+ reservedSeats: [...myReservedSeats, ...otherReservedSeats]
478
+ };
479
+ }
480
+ function createIndexUpdates(seatMapId, eventId, subEventId) {
481
+ return {
482
+ [`indexes/by_event/${eventId}/${seatMapId}`]: true,
483
+ [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
484
+ };
485
+ }
265
486
  // Annotate the CommonJS export names for ESM import in node:
266
487
  0 && (module.exports = {
267
488
  DEFAULT_COLORS,
489
+ FirebasePaths,
268
490
  applySeatStateOverrides,
269
491
  calculateAvailableSeats,
270
492
  calculateCapacity,
271
493
  calculateSeatPrice,
272
494
  cloneConfig,
273
495
  createDefaultConfig,
496
+ createIndexUpdates,
497
+ decodeSeatId,
498
+ deriveSeatArraysFromStates,
274
499
  downloadConfigAsFile,
500
+ encodeSeatId,
275
501
  exportConfigAsJSON,
502
+ extractSeatStates,
276
503
  formatDate,
504
+ fromFirebaseSeatMap,
505
+ fromFirebaseState,
277
506
  generateId,
278
507
  getSelectedSeats,
279
508
  importConfigFromJSON,
509
+ toFirebaseSeatMap,
510
+ toFirebaseState,
280
511
  updateConfigTimestamp,
281
512
  validateSeatMapConfig
282
513
  });
package/dist/index.mjs CHANGED
@@ -222,20 +222,241 @@ 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
+ let effectiveState = seat.state;
344
+ const stateEntry = seatStates?.[encodedId] ?? seatStates?.[id];
345
+ if (stateEntry) {
346
+ effectiveState = stateEntry.state;
347
+ }
348
+ return {
349
+ id,
350
+ position: seat.position,
351
+ shape: seat.shape,
352
+ state: fromFirebaseState(effectiveState),
353
+ sectionName: seat.sectionName,
354
+ rowLabel: seat.rowLabel,
355
+ columnLabel: seat.columnLabel,
356
+ seatNumber: seat.seatNumber,
357
+ price: seat.price,
358
+ floorId: seat.floorId
359
+ };
360
+ }
361
+ );
362
+ const stages = firebase.config.stages ? Object.entries(firebase.config.stages).map(([id, stage]) => ({
363
+ id,
364
+ position: stage.position,
365
+ config: {
366
+ label: stage.config.label,
367
+ width: stage.config.width,
368
+ height: stage.config.height,
369
+ rotation: stage.config.rotation,
370
+ color: stage.config.color
371
+ },
372
+ floorId: stage.floorId
373
+ })) : void 0;
374
+ const floors = firebase.config.floors ? Object.values(firebase.config.floors).sort((a, b) => a.order - b.order) : void 0;
375
+ return {
376
+ version: firebase.meta.version,
377
+ metadata: {
378
+ name: firebase.meta.name,
379
+ venue: firebase.meta.venue,
380
+ capacity: firebase.meta.capacity,
381
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
382
+ // Not stored in Firebase
383
+ updatedAt: new Date(firebase.meta.updated_at).toISOString()
384
+ },
385
+ canvas: firebase.config.canvas,
386
+ colors: firebase.config.colors,
387
+ seats,
388
+ stages,
389
+ floors
390
+ };
391
+ }
392
+ function extractSeatStates(config) {
393
+ const states = {};
394
+ for (const seat of config.seats) {
395
+ if (seat.state === "hidden") continue;
396
+ if (seat.state === "available" || seat.state === "selected") continue;
397
+ const encodedId = encodeSeatId(seat.id);
398
+ states[encodedId] = {
399
+ state: seat.state,
400
+ timestamp: Date.now()
401
+ };
402
+ }
403
+ return states;
404
+ }
405
+ function deriveSeatArraysFromStates(states, currentUserId) {
406
+ const myReservedSeats = [];
407
+ const otherReservedSeats = [];
408
+ const unavailableSeats = [];
409
+ for (const [encodedId, entry] of Object.entries(states)) {
410
+ if (!entry) continue;
411
+ const id = decodeSeatId(encodedId);
412
+ if (entry.state === "unavailable") {
413
+ unavailableSeats.push(id);
414
+ } else if (entry.state === "reserved") {
415
+ if (currentUserId && entry.userId === currentUserId) {
416
+ myReservedSeats.push(id);
417
+ } else {
418
+ otherReservedSeats.push(id);
419
+ }
420
+ }
421
+ }
422
+ return {
423
+ myReservedSeats,
424
+ otherReservedSeats,
425
+ unavailableSeats,
426
+ // Legacy: all reserved seats (for non-user-aware usage)
427
+ reservedSeats: [...myReservedSeats, ...otherReservedSeats]
428
+ };
429
+ }
430
+ function createIndexUpdates(seatMapId, eventId, subEventId) {
431
+ return {
432
+ [`indexes/by_event/${eventId}/${seatMapId}`]: true,
433
+ [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
434
+ };
435
+ }
225
436
  export {
226
437
  DEFAULT_COLORS,
438
+ FirebasePaths,
227
439
  applySeatStateOverrides,
228
440
  calculateAvailableSeats,
229
441
  calculateCapacity,
230
442
  calculateSeatPrice,
231
443
  cloneConfig,
232
444
  createDefaultConfig,
445
+ createIndexUpdates,
446
+ decodeSeatId,
447
+ deriveSeatArraysFromStates,
233
448
  downloadConfigAsFile,
449
+ encodeSeatId,
234
450
  exportConfigAsJSON,
451
+ extractSeatStates,
235
452
  formatDate,
453
+ fromFirebaseSeatMap,
454
+ fromFirebaseState,
236
455
  generateId,
237
456
  getSelectedSeats,
238
457
  importConfigFromJSON,
458
+ toFirebaseSeatMap,
459
+ toFirebaseState,
239
460
  updateConfigTimestamp,
240
461
  validateSeatMapConfig
241
462
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/shared",
3
- "version": "2.2.0",
3
+ "version": "2.4.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",