@zonetrix/viewer 2.9.0 → 2.10.1
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 +146 -2
- package/dist/components/SeatMapViewer.d.ts +1 -0
- package/dist/hooks/useFirebaseSeatStates.d.ts +13 -4
- package/dist/hooks/useRealtimeSeatMap.d.ts +15 -7
- package/dist/index.js +1 -1
- package/dist/index.mjs +546 -518
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,9 +106,10 @@ function BookingApp() {
|
|
|
106
106
|
| `configUrl` | `string` | No* | URL to fetch configuration from |
|
|
107
107
|
| `floorId` | `string` | No | Filter seats/stages by floor ID (controlled mode) |
|
|
108
108
|
| `onFloorChange` | `(floorId: string) => void` | No | Callback when floor changes |
|
|
109
|
-
| `reservedSeats` | `string[]` | No | Array of seat IDs/numbers to mark as reserved |
|
|
109
|
+
| `reservedSeats` | `string[]` | No | Array of seat IDs/numbers to mark as reserved (others' reservations) |
|
|
110
110
|
| `unavailableSeats` | `string[]` | No | Array of seat IDs/numbers to mark as unavailable |
|
|
111
111
|
| `selectedSeats` | `string[]` | No | Array of seat IDs for controlled selection mode |
|
|
112
|
+
| `myReservedSeats` | `string[]` | No | Array of seat IDs reserved by current user (shown as selected/blue) |
|
|
112
113
|
| `onSeatSelect` | `(seat: SeatData) => void` | No | Callback when a seat is selected |
|
|
113
114
|
| `onSeatDeselect` | `(seat: SeatData) => void` | No | Callback when a seat is deselected |
|
|
114
115
|
| `onSelectionChange` | `(seats: SeatData[]) => void` | No | Callback when selection changes |
|
|
@@ -424,7 +425,150 @@ function CartIntegration() {
|
|
|
424
425
|
}
|
|
425
426
|
```
|
|
426
427
|
|
|
427
|
-
### 8.
|
|
428
|
+
### 8. Firebase Real-time Integration
|
|
429
|
+
|
|
430
|
+
The viewer includes built-in Firebase Realtime Database integration for instant seat state updates across all users.
|
|
431
|
+
|
|
432
|
+
#### Setup
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
npm install firebase @zonetrix/shared
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Initialize Firebase in your app:
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
import { initializeFirebaseForViewer } from '@zonetrix/viewer';
|
|
442
|
+
|
|
443
|
+
// Initialize once at app startup
|
|
444
|
+
initializeFirebaseForViewer({
|
|
445
|
+
apiKey: "your-api-key",
|
|
446
|
+
authDomain: "your-project.firebaseapp.com",
|
|
447
|
+
databaseURL: "https://your-project.firebaseio.com",
|
|
448
|
+
projectId: "your-project",
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### Basic Real-time Usage
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
import { useRealtimeSeatMap, SeatMapViewer } from '@zonetrix/viewer';
|
|
456
|
+
|
|
457
|
+
function BookingPage({ seatMapId }) {
|
|
458
|
+
const {
|
|
459
|
+
config,
|
|
460
|
+
otherReservedSeats, // Reserved by others → yellow
|
|
461
|
+
unavailableSeats,
|
|
462
|
+
loading,
|
|
463
|
+
error
|
|
464
|
+
} = useRealtimeSeatMap({ seatMapId });
|
|
465
|
+
|
|
466
|
+
if (loading) return <LoadingSpinner />;
|
|
467
|
+
if (error) return <ErrorMessage error={error} />;
|
|
468
|
+
|
|
469
|
+
return (
|
|
470
|
+
<SeatMapViewer
|
|
471
|
+
config={config}
|
|
472
|
+
reservedSeats={otherReservedSeats}
|
|
473
|
+
unavailableSeats={unavailableSeats}
|
|
474
|
+
onSeatSelect={handleSeatSelect}
|
|
475
|
+
/>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
#### User-Aware Reservations (Multi-User Booking)
|
|
481
|
+
|
|
482
|
+
When multiple users are booking simultaneously, pass `userId` to show each user's own reservations as "selected" while showing others' reservations as "reserved":
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
import { useRealtimeSeatMap, SeatMapViewer } from '@zonetrix/viewer';
|
|
486
|
+
|
|
487
|
+
function BookingPage({ seatMapId, userId }) {
|
|
488
|
+
const {
|
|
489
|
+
config,
|
|
490
|
+
myReservedSeats, // Seats I reserved → blue (selected)
|
|
491
|
+
otherReservedSeats, // Seats others reserved → yellow (reserved)
|
|
492
|
+
unavailableSeats,
|
|
493
|
+
loading
|
|
494
|
+
} = useRealtimeSeatMap({ seatMapId, userId });
|
|
495
|
+
|
|
496
|
+
if (loading) return <LoadingSpinner />;
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<SeatMapViewer
|
|
500
|
+
config={config}
|
|
501
|
+
myReservedSeats={myReservedSeats} // Show as selected (blue)
|
|
502
|
+
reservedSeats={otherReservedSeats} // Show as reserved (yellow)
|
|
503
|
+
unavailableSeats={unavailableSeats}
|
|
504
|
+
onSeatSelect={handleSeatSelect}
|
|
505
|
+
/>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
#### Firebase Data Structure
|
|
511
|
+
|
|
512
|
+
Seat states are stored at `seat_states/{seatMapId}/{seatId}`:
|
|
513
|
+
|
|
514
|
+
```javascript
|
|
515
|
+
// Reserved seat (with user tracking)
|
|
516
|
+
{
|
|
517
|
+
state: "reserved",
|
|
518
|
+
userId: "user-123",
|
|
519
|
+
timestamp: 1704931200000
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Unavailable seat
|
|
523
|
+
{
|
|
524
|
+
state: "unavailable",
|
|
525
|
+
timestamp: 1704931200000
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Available seats: key doesn't exist (deleted)
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### Booking App Integration
|
|
532
|
+
|
|
533
|
+
Your booking backend should update Firebase when users reserve seats:
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
import { getDatabase, ref, set, remove } from 'firebase/database';
|
|
537
|
+
|
|
538
|
+
const db = getDatabase();
|
|
539
|
+
|
|
540
|
+
// Reserve a seat for a user
|
|
541
|
+
async function reserveSeat(seatMapId, seatId, userId) {
|
|
542
|
+
await set(ref(db, `seat_states/${seatMapId}/${seatId}`), {
|
|
543
|
+
state: 'reserved',
|
|
544
|
+
userId: userId,
|
|
545
|
+
timestamp: Date.now()
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Release a seat (make available)
|
|
550
|
+
async function releaseSeat(seatMapId, seatId) {
|
|
551
|
+
await remove(ref(db, `seat_states/${seatMapId}/${seatId}`));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Mark seat as unavailable (sold, blocked)
|
|
555
|
+
async function markUnavailable(seatMapId, seatId) {
|
|
556
|
+
await set(ref(db, `seat_states/${seatMapId}/${seatId}`), {
|
|
557
|
+
state: 'unavailable',
|
|
558
|
+
timestamp: Date.now()
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
#### Hooks Reference
|
|
564
|
+
|
|
565
|
+
| Hook | Description |
|
|
566
|
+
|------|-------------|
|
|
567
|
+
| `useFirebaseSeatStates` | Subscribe to real-time seat states only |
|
|
568
|
+
| `useFirebaseConfig` | Load seat map config from Firebase |
|
|
569
|
+
| `useRealtimeSeatMap` | Combined hook (config + real-time states) |
|
|
570
|
+
|
|
571
|
+
### 9. Error Handling
|
|
428
572
|
|
|
429
573
|
```tsx
|
|
430
574
|
import { SeatMapViewer } from '@zonetrix/viewer';
|
|
@@ -8,6 +8,7 @@ export interface SeatMapViewerProps {
|
|
|
8
8
|
reservedSeats?: string[];
|
|
9
9
|
unavailableSeats?: string[];
|
|
10
10
|
selectedSeats?: string[];
|
|
11
|
+
myReservedSeats?: string[];
|
|
11
12
|
onSeatSelect?: (seat: SeatData) => void;
|
|
12
13
|
onSeatDeselect?: (seat: SeatData) => void;
|
|
13
14
|
onSelectionChange?: (seats: SeatData[]) => void;
|
|
@@ -2,6 +2,8 @@ import { FirebaseSeatStates } from '@zonetrix/shared';
|
|
|
2
2
|
export interface UseFirebaseSeatStatesOptions {
|
|
3
3
|
/** The seat map ID to subscribe to */
|
|
4
4
|
seatMapId: string | null;
|
|
5
|
+
/** Current user ID for user-aware state derivation */
|
|
6
|
+
currentUserId?: string;
|
|
5
7
|
/** Whether the subscription is enabled (default: true) */
|
|
6
8
|
enabled?: boolean;
|
|
7
9
|
/** Callback when states change */
|
|
@@ -18,23 +20,30 @@ export interface UseFirebaseSeatStatesResult {
|
|
|
18
20
|
error: Error | null;
|
|
19
21
|
/** Timestamp of last update */
|
|
20
22
|
lastUpdated: number | null;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
+
/** Seats reserved by current user (show as selected) */
|
|
24
|
+
myReservedSeats: string[];
|
|
25
|
+
/** Seats reserved by other users (show as reserved) */
|
|
26
|
+
otherReservedSeats: string[];
|
|
27
|
+
/** Seats unavailable for everyone */
|
|
23
28
|
unavailableSeats: string[];
|
|
29
|
+
/** @deprecated Use otherReservedSeats instead */
|
|
30
|
+
reservedSeats: string[];
|
|
24
31
|
}
|
|
25
32
|
/**
|
|
26
33
|
* Subscribe to real-time seat state updates from Firebase
|
|
27
34
|
*
|
|
28
35
|
* @example
|
|
29
36
|
* ```tsx
|
|
30
|
-
* const {
|
|
37
|
+
* const { myReservedSeats, otherReservedSeats, unavailableSeats, loading } = useFirebaseSeatStates({
|
|
31
38
|
* seatMapId: '123',
|
|
39
|
+
* currentUserId: 'user-abc',
|
|
32
40
|
* });
|
|
33
41
|
*
|
|
34
42
|
* return (
|
|
35
43
|
* <SeatMapViewer
|
|
36
44
|
* config={config}
|
|
37
|
-
*
|
|
45
|
+
* myReservedSeats={myReservedSeats}
|
|
46
|
+
* reservedSeats={otherReservedSeats}
|
|
38
47
|
* unavailableSeats={unavailableSeats}
|
|
39
48
|
* />
|
|
40
49
|
* );
|
|
@@ -3,6 +3,8 @@ import { FirebaseSeatStates } from '@zonetrix/shared';
|
|
|
3
3
|
export interface UseRealtimeSeatMapOptions {
|
|
4
4
|
/** The seat map ID to load and subscribe to */
|
|
5
5
|
seatMapId: string | null;
|
|
6
|
+
/** Current user ID for user-aware state derivation */
|
|
7
|
+
userId?: string;
|
|
6
8
|
/** Whether the hook is enabled (default: true) */
|
|
7
9
|
enabled?: boolean;
|
|
8
10
|
/** Subscribe to design changes in real-time (default: false) */
|
|
@@ -21,10 +23,14 @@ export interface UseRealtimeSeatMapResult {
|
|
|
21
23
|
loading: boolean;
|
|
22
24
|
/** Any error that occurred */
|
|
23
25
|
error: Error | null;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
/**
|
|
26
|
+
/** Seats reserved by current user (show as selected) */
|
|
27
|
+
myReservedSeats: string[];
|
|
28
|
+
/** Seats reserved by other users (show as reserved) */
|
|
29
|
+
otherReservedSeats: string[];
|
|
30
|
+
/** Seats unavailable for everyone */
|
|
27
31
|
unavailableSeats: string[];
|
|
32
|
+
/** @deprecated Use otherReservedSeats instead */
|
|
33
|
+
reservedSeats: string[];
|
|
28
34
|
/** Raw seat states map */
|
|
29
35
|
seatStates: FirebaseSeatStates | null;
|
|
30
36
|
/** Timestamp of last state update */
|
|
@@ -39,14 +45,15 @@ export interface UseRealtimeSeatMapResult {
|
|
|
39
45
|
* ```tsx
|
|
40
46
|
* import { useRealtimeSeatMap, SeatMapViewer } from '@zonetrix/viewer';
|
|
41
47
|
*
|
|
42
|
-
* function BookingPage({ seatMapId }) {
|
|
48
|
+
* function BookingPage({ seatMapId, userId }) {
|
|
43
49
|
* const {
|
|
44
50
|
* config,
|
|
45
|
-
*
|
|
51
|
+
* myReservedSeats,
|
|
52
|
+
* otherReservedSeats,
|
|
46
53
|
* unavailableSeats,
|
|
47
54
|
* loading,
|
|
48
55
|
* error
|
|
49
|
-
* } = useRealtimeSeatMap({ seatMapId });
|
|
56
|
+
* } = useRealtimeSeatMap({ seatMapId, userId });
|
|
50
57
|
*
|
|
51
58
|
* if (loading) return <LoadingSpinner />;
|
|
52
59
|
* if (error) return <ErrorMessage error={error} />;
|
|
@@ -55,7 +62,8 @@ export interface UseRealtimeSeatMapResult {
|
|
|
55
62
|
* return (
|
|
56
63
|
* <SeatMapViewer
|
|
57
64
|
* config={config}
|
|
58
|
-
*
|
|
65
|
+
* myReservedSeats={myReservedSeats}
|
|
66
|
+
* reservedSeats={otherReservedSeats}
|
|
59
67
|
* unavailableSeats={unavailableSeats}
|
|
60
68
|
* onSeatSelect={handleSeatSelect}
|
|
61
69
|
* />
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),t=require("react"),T=require("react-konva"),Y=require("firebase/database"),nt=require("@zonetrix/shared");function we(n){const[s,c]=t.useState(null),[o,g]=t.useState(!1),[p,d]=t.useState(null),m=async()=>{if(n){g(!0),d(null);try{const h=await fetch(n);if(!h.ok)throw new Error(`Failed to fetch config: ${h.statusText}`);const a=await h.json();c(a)}catch(h){const a=h instanceof Error?h:new Error("Unknown error occurred");d(a),console.error("Failed to fetch seat map config:",a)}finally{g(!1)}}};return t.useEffect(()=>{m()},[n]),{config:s,loading:o,error:p,refetch:m}}function Ce(n){const[s,c]=t.useState({width:0,height:0});return t.useEffect(()=>{const o=n.current;if(!o)return;const{width:g,height:p}=o.getBoundingClientRect();g>0&&p>0&&c({width:g,height:p});const d=new ResizeObserver(m=>{const h=m[0];if(!h)return;const{width:a,height:i}=h.contentRect;a>0&&i>0&&c(f=>f.width===a&&f.height===i?f:{width:a,height:i})});return d.observe(o),()=>{d.disconnect()}},[n]),s}function me(n,s){return Math.sqrt(Math.pow(s.x-n.x,2)+Math.pow(s.y-n.y,2))}function ve(n,s){return{x:(n.x+s.x)/2,y:(n.y+s.y)/2}}function je(n,s){const c=t.useRef(null),o=t.useRef(null),g=t.useRef(1);t.useEffect(()=>{const p=n.current;if(!p||!s.enabled)return;const d=p.container(),m=i=>{if(i.touches.length===2){i.preventDefault();const f={x:i.touches[0].clientX,y:i.touches[0].clientY},l={x:i.touches[1].clientX,y:i.touches[1].clientY};c.current=me(f,l),o.current=ve(f,l),g.current=s.currentScale}},h=i=>{if(i.touches.length!==2)return;i.preventDefault();const f={x:i.touches[0].clientX,y:i.touches[0].clientY},l={x:i.touches[1].clientX,y:i.touches[1].clientY},C=me(f,l),v=ve(f,l);if(c.current!==null&&o.current!==null){const j=C/c.current,F=Math.min(Math.max(s.currentScale*j,s.minScale),s.maxScale),R=d.getBoundingClientRect(),M=v.x-R.left,L=v.y-R.top,P=s.currentScale,W={x:(M-s.currentPosition.x)/P,y:(L-s.currentPosition.y)/P},A=v.x-o.current.x,N=v.y-o.current.y,ne={x:M-W.x*F+A,y:L-W.y*F+N};s.onScaleChange(F,ne),c.current=C,o.current=v}},a=i=>{i.touches.length<2&&(c.current=null,o.current=null)};return d.addEventListener("touchstart",m,{passive:!1}),d.addEventListener("touchmove",h,{passive:!1}),d.addEventListener("touchend",a),()=>{d.removeEventListener("touchstart",m),d.removeEventListener("touchmove",h),d.removeEventListener("touchend",a)}},[n,s])}const Me={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},Ee=t.memo(({seat:n,state:s,colors:c,onClick:o,onMouseEnter:g,onMouseLeave:p})=>{const h={available:c.seatAvailable,reserved:c.seatReserved,selected:c.seatSelected,unavailable:c.seatUnavailable,hidden:c.seatHidden}[s],a=s==="available"||s==="selected",i=t.useCallback(()=>{a&&o(n)},[n,o,a]),f=t.useCallback(v=>{g(n,v);const j=v.target.getStage();j&&a&&(j.container().style.cursor="pointer")},[n,g,a]),l=t.useCallback(v=>{p();const j=v.target.getStage();j&&(j.container().style.cursor="grab")},[p]),C={x:n.position.x,y:n.position.y,fill:h,stroke:"#ffffff",strokeWidth:1,onClick:i,onTap:i,onMouseEnter:f,onMouseLeave:l};return n.shape==="circle"?r.jsx(T.Circle,{...C,radius:12}):r.jsx(T.Rect,{...C,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:n.shape==="square"?0:4})});Ee.displayName="ViewerSeat";const Fe=t.memo(({stage:n,stageColor:s})=>{const c=p=>({stage:"🎭",table:"⬜",wall:"▬",barrier:"🛡️","dj-booth":"🎵",bar:"🍷","entry-exit":"🚪",custom:"➕"})[p||"stage"]||"🎭",o=n.config.color||s,g=c(n.config.objectType);return r.jsxs(T.Group,{x:n.position.x,y:n.position.y,rotation:n.config.rotation||0,children:[r.jsx(T.Rect,{width:n.config.width,height:n.config.height,fill:o+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),r.jsx(T.Text,{text:g,x:0,y:0,width:n.config.width,height:n.config.height*.4,fontSize:32,fill:"#ffffff",align:"center",verticalAlign:"middle"}),r.jsx(T.Text,{text:n.config.label,x:0,y:n.config.height*.4,width:n.config.width,height:n.config.height*.6,fontSize:20,fontStyle:"bold",fill:"#ffffff",align:"center",verticalAlign:"middle"})]})});Fe.displayName="ViewerStage";const Re=t.memo(({floors:n,currentFloorId:s,onFloorChange:c,showAllOption:o,allLabel:g,position:p,className:d})=>{const m=t.useMemo(()=>[...n].sort((l,C)=>l.order-C.order),[n]),a={position:"absolute",display:"flex",alignItems:"center",gap:"8px",padding:"8px 12px",backgroundColor:"rgba(26, 26, 26, 0.95)",borderRadius:"8px",margin:"12px",zIndex:10,...{"top-left":{top:0,left:0},"top-right":{top:0,right:0},"bottom-left":{bottom:0,left:0},"bottom-right":{bottom:0,right:0}}[p]},i={padding:"10px 16px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease",minHeight:"44px",touchAction:"manipulation"},f={...i,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return r.jsxs("div",{className:d,style:a,children:[o&&r.jsx("button",{type:"button",onClick:()=>c(null),style:s===null?f:i,children:g}),m.map(l=>r.jsx("button",{type:"button",onClick:()=>c(l.id),style:s===l.id?f:i,children:l.name},l.id))]})});Re.displayName="FloorSelectorBar";const ke=t.memo(({scale:n,minScale:s,maxScale:c,onZoomIn:o,onZoomOut:g,position:p,className:d})=>{const h={position:"absolute",display:"flex",flexDirection:"column",gap:"4px",padding:"8px",backgroundColor:"rgba(26, 26, 26, 0.95)",borderRadius:"8px",margin:"12px",zIndex:10,...{"top-left":{top:0,left:0},"top-right":{top:0,right:0},"bottom-left":{bottom:0,left:0},"bottom-right":{bottom:0,right:0}}[p]},a={width:"44px",height:"44px",minWidth:"44px",minHeight:"44px",fontSize:"22px",fontWeight:"bold",border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",transition:"all 0.2s ease",touchAction:"manipulation"},i={...a,opacity:.4,cursor:"not-allowed"},f=n<c,l=n>s;return r.jsxs("div",{className:d,style:h,children:[r.jsx("button",{type:"button",onClick:o,disabled:!f,style:f?a:i,title:"Zoom In",children:"+"}),r.jsx("button",{type:"button",onClick:g,disabled:!l,style:l?a:i,title:"Zoom Out",children:"−"})]})});ke.displayName="ZoomControls";const Ie=t.memo(({visible:n,x:s,y:c,seat:o,currency:g,state:p})=>{if(!n||!o)return null;const d=o.seatNumber||(o.rowLabel&&o.columnLabel?`${o.rowLabel}-${o.columnLabel}`:"N/A"),m={position:"fixed",left:`${s+15}px`,top:`${c+15}px`,zIndex:1e3,pointerEvents:"none"},h={backgroundColor:"rgba(26, 26, 26, 0.95)",color:"#fff",border:"1px solid #444",borderRadius:"8px",padding:"8px 12px",fontSize:"13px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.3)",minWidth:"140px"},a={color:"#9ca3af",marginRight:"4px"},i={fontWeight:600},f={color:"#4ade80",fontWeight:600},l={fontSize:"11px",color:"#6b7280",textTransform:"capitalize",marginTop:"4px"};return r.jsx("div",{style:m,children:r.jsxs("div",{style:h,children:[o.sectionName&&r.jsxs("div",{style:{marginBottom:"4px"},children:[r.jsx("span",{style:a,children:"Section:"}),r.jsx("span",{style:{...i,color:"#3b82f6"},children:o.sectionName})]}),r.jsxs("div",{style:{marginBottom:"4px"},children:[r.jsx("span",{style:a,children:"Seat:"}),r.jsx("span",{style:i,children:d})]}),o.price!==void 0&&o.price>0&&p==="available"&&r.jsxs("div",{style:{marginBottom:"4px"},children:[r.jsx("span",{style:a,children:"Price:"}),r.jsxs("span",{style:f,children:[g," ",o.price.toFixed(2)]})]}),r.jsxs("div",{style:l,children:["Status: ",p]})]})})});Ie.displayName="SeatTooltip";const st=({config:n,configUrl:s,floorId:c,onFloorChange:o,reservedSeats:g=[],unavailableSeats:p=[],selectedSeats:d,onSeatSelect:m,onSeatDeselect:h,onSelectionChange:a,colorOverrides:i,showTooltip:f=!0,zoomEnabled:l=!0,className:C="",onConfigLoad:v,onError:j,showFloorSelector:F,floorSelectorPosition:R="top-left",floorSelectorClassName:M,showAllFloorsOption:L=!0,allFloorsLabel:P="All",fitToView:W=!0,fitPadding:A=40,showZoomControls:N=!0,zoomControlsPosition:ne="bottom-right",zoomControlsClassName:U,minZoom:q,maxZoom:D=3,zoomStep:$=.25,touchEnabled:se=!0})=>{const de=t.useRef(null),be=t.useRef(null),E=Ce(be),[V,Se]=t.useState(new Set),[k,_]=t.useState(1),[I,B]=t.useState({x:0,y:0}),[ze,Te]=t.useState(null),[Ne,Xe]=t.useState(1),oe=t.useRef({width:0,height:0}),[G,ye]=t.useState({visible:!1,x:0,y:0,seat:null,state:"available"}),{config:Ye,loading:Pe,error:K}=we(s),x=n||Ye,he=c!==void 0,z=he?c||null:ze,ie=d!==void 0,We=t.useCallback(e=>{he||Te(e),o?.(e)},[he,o]),fe=x?.floors||[],Ae=F!==void 0?F:fe.length>1,re=t.useMemo(()=>x?{...x.colors,...i}:{...Me,...i},[x,i]),O=t.useMemo(()=>{if(!x)return[];let e=x.seats.filter(u=>u.state!=="hidden");return z&&(e=e.filter(u=>u.floorId===z||!u.floorId&&z==="floor_default")),e},[x,z]),ae=t.useMemo(()=>x?.stages?z?x.stages.filter(e=>e.floorId===z||!e.floorId&&z==="floor_default"):x.stages:[],[x,z]),X=t.useMemo(()=>{if(!x||O.length===0&&ae.length===0)return null;const e=12;let u=1/0,S=1/0,b=-1/0,y=-1/0;return O.forEach(w=>{u=Math.min(u,w.position.x-e),S=Math.min(S,w.position.y-e),b=Math.max(b,w.position.x+e),y=Math.max(y,w.position.y+e)}),ae.forEach(w=>{u=Math.min(u,w.position.x),S=Math.min(S,w.position.y),b=Math.max(b,w.position.x+(w.config?.width||200)),y=Math.max(y,w.position.y+(w.config?.height||100))}),{minX:u,minY:S,maxX:b,maxY:y,width:b-u,height:y-S}},[x,O,ae]);t.useEffect(()=>{if(!W||!x||!X||E.width===0||E.height===0)return;const e=Math.abs(E.width-oe.current.width),u=Math.abs(E.height-oe.current.height);if(!(oe.current.width===0)&&e<10&&u<10)return;oe.current=E;const b=E.width,y=E.height,w=b-A*2,ee=y-A*2,ce=w/X.width,pe=ee/X.height,le=Math.min(ce,pe,D),Ke=X.minX+X.width/2,Ze=X.minY+X.height/2,Je=b/2,Qe=y/2,et=Je-Ke*le,tt=Qe-Ze*le;_(le),B({x:et,y:tt}),Xe(le)},[W,x,X,A,D,E,z]);const Z=t.useMemo(()=>{const e=new Set(g),u=new Set(p);return{reserved:e,unavailable:u}},[g,p]),ge=t.useMemo(()=>d?new Set(d):null,[d]),J=t.useCallback(e=>{const u=e.id,S=e.seatNumber||"";return Z.unavailable.has(u)||Z.unavailable.has(S)?"unavailable":Z.reserved.has(u)||Z.reserved.has(S)?"reserved":V.has(u)?"selected":e.state},[Z,V]);t.useEffect(()=>{x&&v&&v(x)},[x,v]),t.useEffect(()=>{K&&j&&j(K)},[K,j]),t.useEffect(()=>{ie&&ge&&Se(ge)},[ie,ge]);const $e=t.useCallback(e=>{const u=J(e);if(u!=="available"&&u!=="selected")return;const S=V.has(e.id);ie||Se(b=>{const y=new Set(b);return S?y.delete(e.id):y.add(e.id),y}),S?h?.(e):(m?.(e),m||console.log("Seat selected:",e))},[J,V,ie,m,h]),Q=t.useMemo(()=>x?O.filter(e=>V.has(e.id)):[],[O,V]);t.useEffect(()=>{a?.(Q)},[Q,a]);const H=q!==void 0?q:Ne,Be=t.useCallback(()=>{if(!l)return;const e=Math.min(k+$,D);if(e!==k){const u=E.width||x?.canvas.width||800,S=E.height||x?.canvas.height||600,b=u/2,y=S/2,w={x:(b-I.x)/k,y:(y-I.y)/k};_(e),B({x:b-w.x*e,y:y-w.y*e})}},[l,k,$,D,E,x,I]),Ve=t.useCallback(()=>{if(!l)return;const e=Math.max(k-$,H);if(e!==k){const u=E.width||x?.canvas.width||800,S=E.height||x?.canvas.height||600,b=u/2,y=S/2,w={x:(b-I.x)/k,y:(y-I.y)/k};_(e),B({x:b-w.x*e,y:y-w.y*e})}},[l,k,$,H,E,x,I]),Oe=t.useCallback(e=>{B({x:e.target.x(),y:e.target.y()})},[]),He=t.useCallback(e=>{if(!l)return;e.evt.preventDefault();const u=de.current;if(!u)return;const S=u.scaleX(),b=u.getPointerPosition();if(!b)return;const y=1.1,w=e.evt.deltaY>0?S/y:S*y,ee=Math.min(Math.max(w,H),D),ce={x:(b.x-I.x)/S,y:(b.y-I.y)/S},pe={x:b.x-ce.x*ee,y:b.y-ce.y*ee};_(ee),B(pe)},[l,I,H,D]);je(de,{enabled:se&&l,minScale:H,maxScale:D,currentScale:k,currentPosition:I,onScaleChange:(e,u)=>{_(e),B(u)},onPositionChange:e=>{B(e)}});const Ue=t.useCallback((e,u)=>{if(!f)return;const S=u.target.getStage();if(!S)return;const b=S.getPointerPosition();if(!b)return;const y=S.container().getBoundingClientRect();ye({visible:!0,x:y.left+b.x,y:y.top+b.y,seat:e,state:J(e)})},[f,J]),qe=t.useCallback(()=>{ye(e=>({...e,visible:!1}))},[]);if(Pe)return r.jsx("div",{className:`flex items-center justify-center h-full ${C}`,children:r.jsx("p",{children:"Loading seat map..."})});if(K)return r.jsx("div",{className:`flex items-center justify-center h-full ${C}`,children:r.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",K.message]})});if(!x)return r.jsx("div",{className:`flex items-center justify-center h-full ${C}`,children:r.jsx("p",{children:"No configuration provided"})});const _e=E.width||x.canvas.width,Ge=E.height||x.canvas.height;return r.jsxs("div",{ref:be,className:`relative ${C}`,style:{width:"100%",height:"100%"},children:[Ae&&fe.length>0&&r.jsx(Re,{floors:fe,currentFloorId:z,onFloorChange:We,showAllOption:L,allLabel:P,position:R,className:M}),r.jsxs(T.Stage,{ref:de,width:_e,height:Ge,scaleX:k,scaleY:k,x:I.x,y:I.y,draggable:!0,onDragEnd:Oe,onWheel:He,style:{backgroundColor:x.canvas.backgroundColor,cursor:"grab"},children:[r.jsx(T.Layer,{listening:!1,children:ae.map(e=>r.jsx(Fe,{stage:e,stageColor:re.stageColor},e.id))}),r.jsx(T.Layer,{children:O.map(e=>r.jsx(Ee,{seat:e,state:J(e),colors:re,onClick:$e,onMouseEnter:Ue,onMouseLeave:qe},e.id))})]}),f&&r.jsx(Ie,{visible:G.visible,x:G.x,y:G.y,seat:G.seat,currency:re.currency,state:G.state}),N&&l&&r.jsx(ke,{scale:k,minScale:H,maxScale:D,onZoomIn:Be,onZoomOut:Ve,position:ne,className:U}),Q.length>0&&r.jsxs("div",{className:"absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg",children:[r.jsxs("h3",{className:"font-semibold mb-2",children:["Selected Seats (",Q.length,")"]}),r.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:Q.map(e=>r.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${re.currency} ${e.price.toFixed(2)}`]},e.id))})]})]})};let te=null;function ot(n){te=n}function xe(){if(!te)throw new Error("Firebase database not initialized. Call initializeFirebaseForViewer(db) first.");return te}function ue(){return te!==null}function it(){te=null}function Le(n){const{seatMapId:s,enabled:c=!0,onStateChange:o,onError:g}=n,[p,d]=t.useState(null),[m,h]=t.useState(!0),[a,i]=t.useState(null),[f,l]=t.useState(null),[C,v]=t.useState([]),[j,F]=t.useState([]),R=t.useRef(o),M=t.useRef(g);return R.current=o,M.current=g,t.useEffect(()=>{if(!c||!s){h(!1);return}if(!ue()){h(!1),i(new Error("Firebase not initialized. Call initializeFirebaseForViewer first."));return}const L=xe(),P=Y.ref(L,`seat_states/${s}`);h(!0),i(null);const W=N=>{const U=N.val()||{};d(U),h(!1),l(Date.now());const q=[],D=[];Object.entries(U).forEach(([$,se])=>{se==="reserved"?q.push($):se==="unavailable"&&D.push($)}),v(q),F(D),R.current?.(U)},A=N=>{i(N),h(!1),M.current?.(N)};return Y.onValue(P,W,A),()=>{Y.off(P)}},[s,c]),{states:p,loading:m,error:a,lastUpdated:f,reservedSeats:C,unavailableSeats:j}}function De(n){const{seatMapId:s,enabled:c=!0,subscribeToChanges:o=!1,onConfigLoad:g,onError:p}=n,[d,m]=t.useState(null),[h,a]=t.useState(!0),[i,f]=t.useState(null),l=t.useRef(g),C=t.useRef(p);l.current=g,C.current=p;const v=t.useCallback(async()=>{if(!s)return;if(!ue()){f(new Error("Firebase not initialized. Call initializeFirebaseForViewer first.")),a(!1);return}const j=xe(),F=Y.ref(j,`seatmaps/${s}`);try{a(!0),f(null);const M=(await Y.get(F)).val();if(M){const L=nt.fromFirebaseSeatMap(M);m(L),l.current?.(L)}else f(new Error(`Seat map ${s} not found in Firebase`))}catch(R){const M=R instanceof Error?R:new Error("Unknown error");f(M),C.current?.(M)}finally{a(!1)}},[s]);return t.useEffect(()=>{if(!c||!s){a(!1);return}if(v(),o&&ue()){const j=xe(),F=Y.ref(j,`seatmaps/${s}/meta/updated_at`);let R=!0;const M=L=>{if(R){R=!1;return}L.exists()&&v()};return Y.onValue(F,M),()=>{Y.off(F)}}},[s,c,o,v]),{config:d,loading:h,error:i,refetch:v}}function rt(n){const{seatMapId:s,enabled:c=!0,subscribeToDesignChanges:o=!1,onConfigLoad:g,onStateChange:p,onError:d}=n,{config:m,loading:h,error:a,refetch:i}=De({seatMapId:s,enabled:c,subscribeToChanges:o,onConfigLoad:g,onError:d}),{states:f,loading:l,error:C,lastUpdated:v,reservedSeats:j,unavailableSeats:F}=Le({seatMapId:s,enabled:c,onStateChange:p,onError:d});return{config:m,loading:h||l,error:a||C,reservedSeats:j,unavailableSeats:F,seatStates:f,lastUpdated:v,refetch:i}}exports.DEFAULT_COLORS=Me;exports.SeatMapViewer=st;exports.clearFirebaseInstance=it;exports.initializeFirebaseForViewer=ot;exports.isFirebaseInitialized=ue;exports.useConfigFetcher=we;exports.useContainerSize=Ce;exports.useFirebaseConfig=De;exports.useFirebaseSeatStates=Le;exports.useRealtimeSeatMap=rt;exports.useTouchGestures=je;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react/jsx-runtime"),t=require("react"),P=require("react-konva"),B=require("firebase/database"),rt=require("@zonetrix/shared");function Re(n){const[s,o]=t.useState(null),[r,h]=t.useState(!1),[d,l]=t.useState(null),v=async()=>{if(n){h(!0),l(null);try{const p=await fetch(n);if(!p.ok)throw new Error(`Failed to fetch config: ${p.statusText}`);const a=await p.json();o(a)}catch(p){const a=p instanceof Error?p:new Error("Unknown error occurred");l(a),console.error("Failed to fetch seat map config:",a)}finally{h(!1)}}};return t.useEffect(()=>{v()},[n]),{config:s,loading:r,error:d,refetch:v}}function je(n){const[s,o]=t.useState({width:0,height:0});return t.useEffect(()=>{const r=n.current;if(!r)return;const{width:h,height:d}=r.getBoundingClientRect();h>0&&d>0&&o({width:h,height:d});const l=new ResizeObserver(v=>{const p=v[0];if(!p)return;const{width:a,height:c}=p.contentRect;a>0&&c>0&&o(f=>f.width===a&&f.height===c?f:{width:a,height:c})});return l.observe(r),()=>{l.disconnect()}},[n]),s}function we(n,s){return Math.sqrt(Math.pow(s.x-n.x,2)+Math.pow(s.y-n.y,2))}function Ce(n,s){return{x:(n.x+s.x)/2,y:(n.y+s.y)/2}}function Me(n,s){const o=t.useRef(null),r=t.useRef(null),h=t.useRef(1);t.useEffect(()=>{const d=n.current;if(!d||!s.enabled)return;const l=d.container(),v=c=>{if(c.touches.length===2){c.preventDefault();const f={x:c.touches[0].clientX,y:c.touches[0].clientY},g={x:c.touches[1].clientX,y:c.touches[1].clientY};o.current=we(f,g),r.current=Ce(f,g),h.current=s.currentScale}},p=c=>{if(c.touches.length!==2)return;c.preventDefault();const f={x:c.touches[0].clientX,y:c.touches[0].clientY},g={x:c.touches[1].clientX,y:c.touches[1].clientY},m=we(f,g),w=Ce(f,g);if(o.current!==null&&r.current!==null){const R=m/o.current,E=Math.min(Math.max(s.currentScale*R,s.minScale),s.maxScale),F=l.getBoundingClientRect(),j=w.x-F.left,I=w.y-F.top,L=s.currentScale,T={x:(j-s.currentPosition.x)/L,y:(I-s.currentPosition.y)/L},V=w.x-r.current.x,W=w.y-r.current.y,q={x:j-T.x*E+V,y:I-T.y*E+W};s.onScaleChange(E,q),o.current=m,r.current=w}},a=c=>{c.touches.length<2&&(o.current=null,r.current=null)};return l.addEventListener("touchstart",v,{passive:!1}),l.addEventListener("touchmove",p,{passive:!1}),l.addEventListener("touchend",a),()=>{l.removeEventListener("touchstart",v),l.removeEventListener("touchmove",p),l.removeEventListener("touchend",a)}},[n,s])}const Ee={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},Fe=t.memo(({seat:n,state:s,colors:o,onClick:r,onMouseEnter:h,onMouseLeave:d})=>{const p={available:o.seatAvailable,reserved:o.seatReserved,selected:o.seatSelected,unavailable:o.seatUnavailable,hidden:o.seatHidden}[s],a=s==="available"||s==="selected",c=t.useCallback(()=>{a&&r(n)},[n,r,a]),f=t.useCallback(w=>{h(n,w);const R=w.target.getStage();R&&a&&(R.container().style.cursor="pointer")},[n,h,a]),g=t.useCallback(w=>{d();const R=w.target.getStage();R&&(R.container().style.cursor="grab")},[d]),m={x:n.position.x,y:n.position.y,fill:p,stroke:"#ffffff",strokeWidth:1,onClick:c,onTap:c,onMouseEnter:f,onMouseLeave:g};return n.shape==="circle"?i.jsx(P.Circle,{...m,radius:12}):i.jsx(P.Rect,{...m,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:n.shape==="square"?0:4})});Fe.displayName="ViewerSeat";const ke=t.memo(({stage:n,stageColor:s})=>{const o=d=>({stage:"🎭",table:"⬜",wall:"▬",barrier:"🛡️","dj-booth":"🎵",bar:"🍷","entry-exit":"🚪",custom:"➕"})[d||"stage"]||"🎭",r=n.config.color||s,h=o(n.config.objectType);return i.jsxs(P.Group,{x:n.position.x,y:n.position.y,rotation:n.config.rotation||0,children:[i.jsx(P.Rect,{width:n.config.width,height:n.config.height,fill:r+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),i.jsx(P.Text,{text:h,x:0,y:0,width:n.config.width,height:n.config.height*.4,fontSize:32,fill:"#ffffff",align:"center",verticalAlign:"middle"}),i.jsx(P.Text,{text:n.config.label,x:0,y:n.config.height*.4,width:n.config.width,height:n.config.height*.6,fontSize:20,fontStyle:"bold",fill:"#ffffff",align:"center",verticalAlign:"middle"})]})});ke.displayName="ViewerStage";const Ie=t.memo(({floors:n,currentFloorId:s,onFloorChange:o,showAllOption:r,allLabel:h,position:d,className:l})=>{const v=t.useMemo(()=>[...n].sort((g,m)=>g.order-m.order),[n]),a={position:"absolute",display:"flex",alignItems:"center",gap:"8px",padding:"8px 12px",backgroundColor:"rgba(26, 26, 26, 0.95)",borderRadius:"8px",margin:"12px",zIndex:10,...{"top-left":{top:0,left:0},"top-right":{top:0,right:0},"bottom-left":{bottom:0,left:0},"bottom-right":{bottom:0,right:0}}[d]},c={padding:"10px 16px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease",minHeight:"44px",touchAction:"manipulation"},f={...c,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return i.jsxs("div",{className:l,style:a,children:[r&&i.jsx("button",{type:"button",onClick:()=>o(null),style:s===null?f:c,children:h}),v.map(g=>i.jsx("button",{type:"button",onClick:()=>o(g.id),style:s===g.id?f:c,children:g.name},g.id))]})});Ie.displayName="FloorSelectorBar";const Le=t.memo(({scale:n,minScale:s,maxScale:o,onZoomIn:r,onZoomOut:h,position:d,className:l})=>{const p={position:"absolute",display:"flex",flexDirection:"column",gap:"4px",padding:"8px",backgroundColor:"rgba(26, 26, 26, 0.95)",borderRadius:"8px",margin:"12px",zIndex:10,...{"top-left":{top:0,left:0},"top-right":{top:0,right:0},"bottom-left":{bottom:0,left:0},"bottom-right":{bottom:0,right:0}}[d]},a={width:"44px",height:"44px",minWidth:"44px",minHeight:"44px",fontSize:"22px",fontWeight:"bold",border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",transition:"all 0.2s ease",touchAction:"manipulation"},c={...a,opacity:.4,cursor:"not-allowed"},f=n<o,g=n>s;return i.jsxs("div",{className:l,style:p,children:[i.jsx("button",{type:"button",onClick:r,disabled:!f,style:f?a:c,title:"Zoom In",children:"+"}),i.jsx("button",{type:"button",onClick:h,disabled:!g,style:g?a:c,title:"Zoom Out",children:"−"})]})});Le.displayName="ZoomControls";const De=t.memo(({visible:n,x:s,y:o,seat:r,currency:h,state:d})=>{if(!n||!r)return null;const l=r.seatNumber||(r.rowLabel&&r.columnLabel?`${r.rowLabel}-${r.columnLabel}`:"N/A"),v={position:"fixed",left:`${s+15}px`,top:`${o+15}px`,zIndex:1e3,pointerEvents:"none"},p={backgroundColor:"rgba(26, 26, 26, 0.95)",color:"#fff",border:"1px solid #444",borderRadius:"8px",padding:"8px 12px",fontSize:"13px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.3)",minWidth:"140px"},a={color:"#9ca3af",marginRight:"4px"},c={fontWeight:600},f={color:"#4ade80",fontWeight:600},g={fontSize:"11px",color:"#6b7280",textTransform:"capitalize",marginTop:"4px"};return i.jsx("div",{style:v,children:i.jsxs("div",{style:p,children:[r.sectionName&&i.jsxs("div",{style:{marginBottom:"4px"},children:[i.jsx("span",{style:a,children:"Section:"}),i.jsx("span",{style:{...c,color:"#3b82f6"},children:r.sectionName})]}),i.jsxs("div",{style:{marginBottom:"4px"},children:[i.jsx("span",{style:a,children:"Seat:"}),i.jsx("span",{style:c,children:l})]}),r.price!==void 0&&r.price>0&&d==="available"&&i.jsxs("div",{style:{marginBottom:"4px"},children:[i.jsx("span",{style:a,children:"Price:"}),i.jsxs("span",{style:f,children:[h," ",r.price.toFixed(2)]})]}),i.jsxs("div",{style:g,children:["Status: ",d]})]})})});De.displayName="SeatTooltip";const ot=({config:n,configUrl:s,floorId:o,onFloorChange:r,reservedSeats:h=[],unavailableSeats:d=[],selectedSeats:l,myReservedSeats:v=[],onSeatSelect:p,onSeatDeselect:a,onSelectionChange:c,colorOverrides:f,showTooltip:g=!0,zoomEnabled:m=!0,className:w="",onConfigLoad:R,onError:E,showFloorSelector:F,floorSelectorPosition:j="top-left",floorSelectorClassName:I,showAllFloorsOption:L=!0,allFloorsLabel:T="All",fitToView:V=!0,fitPadding:W=40,showZoomControls:q=!0,zoomControlsPosition:fe="bottom-right",zoomControlsClassName:se,minZoom:re,maxZoom:z=3,zoomStep:A=.25,touchEnabled:ve=!0})=>{const X=t.useRef(null),O=t.useRef(null),M=je(O),[Y,he]=t.useState(new Set),[k,J]=t.useState(1),[D,U]=t.useState({x:0,y:0}),[Ne,Ae]=t.useState(null),[Xe,Ye]=t.useState(1),oe=t.useRef({width:0,height:0}),[K,me]=t.useState({visible:!1,x:0,y:0,seat:null,state:"available"}),{config:Pe,loading:We,error:Z}=Re(s),b=n||Pe,ge=o!==void 0,N=ge?o||null:Ne,ie=l!==void 0,Oe=t.useCallback(e=>{ge||Ae(e),r?.(e)},[ge,r]),pe=b?.floors||[],$e=F!==void 0?F:pe.length>1,ae=t.useMemo(()=>b?{...b.colors,...f}:{...Ee,...f},[b,f]),_=t.useMemo(()=>{if(!b)return[];let e=b.seats.filter(u=>u.state!=="hidden");return N&&(e=e.filter(u=>u.floorId===N||!u.floorId&&N==="floor_default")),e},[b,N]),ce=t.useMemo(()=>b?.stages?N?b.stages.filter(e=>e.floorId===N||!e.floorId&&N==="floor_default"):b.stages:[],[b,N]),$=t.useMemo(()=>{if(!b||_.length===0&&ce.length===0)return null;const e=12;let u=1/0,x=1/0,y=-1/0,S=-1/0;return _.forEach(C=>{u=Math.min(u,C.position.x-e),x=Math.min(x,C.position.y-e),y=Math.max(y,C.position.x+e),S=Math.max(S,C.position.y+e)}),ce.forEach(C=>{u=Math.min(u,C.position.x),x=Math.min(x,C.position.y),y=Math.max(y,C.position.x+(C.config?.width||200)),S=Math.max(S,C.position.y+(C.config?.height||100))}),{minX:u,minY:x,maxX:y,maxY:S,width:y-u,height:S-x}},[b,_,ce]);t.useEffect(()=>{if(!V||!b||!$||M.width===0||M.height===0)return;const e=Math.abs(M.width-oe.current.width),u=Math.abs(M.height-oe.current.height);if(!(oe.current.width===0)&&e<10&&u<10)return;oe.current=M;const y=M.width,S=M.height,C=y-W*2,te=S-W*2,le=C/$.width,xe=te/$.height,ue=Math.min(le,xe,z),Ze=$.minX+$.width/2,Qe=$.minY+$.height/2,et=y/2,tt=S/2,nt=et-Ze*ue,st=tt-Qe*ue;J(ue),U({x:nt,y:st}),Ye(ue)},[V,b,$,W,z,M,N]);const H=t.useMemo(()=>{const e=new Set(h),u=new Set(d),x=new Set(v);return{reserved:e,unavailable:u,myReserved:x}},[h,d,v]),be=t.useMemo(()=>l?new Set(l):null,[l]),Q=t.useCallback(e=>{const u=e.id,x=e.seatNumber||"";return H.unavailable.has(u)||H.unavailable.has(x)?"unavailable":H.reserved.has(u)||H.reserved.has(x)?"reserved":H.myReserved.has(u)||H.myReserved.has(x)||Y.has(u)?"selected":e.state},[H,Y]);t.useEffect(()=>{b&&R&&R(b)},[b,R]),t.useEffect(()=>{Z&&E&&E(Z)},[Z,E]),t.useEffect(()=>{ie&&be&&he(be)},[ie,be]);const Be=t.useCallback(e=>{const u=Q(e);if(u!=="available"&&u!=="selected")return;const x=Y.has(e.id);ie||he(y=>{const S=new Set(y);return x?S.delete(e.id):S.add(e.id),S}),x?a?.(e):(p?.(e),p||console.log("Seat selected:",e))},[Q,Y,ie,p,a]),ee=t.useMemo(()=>b?_.filter(e=>Y.has(e.id)):[],[_,Y]);t.useEffect(()=>{c?.(ee)},[ee,c]);const G=re!==void 0?re:Xe,Ve=t.useCallback(()=>{if(!m)return;const e=Math.min(k+A,z);if(e!==k){const u=M.width||b?.canvas.width||800,x=M.height||b?.canvas.height||600,y=u/2,S=x/2,C={x:(y-D.x)/k,y:(S-D.y)/k};J(e),U({x:y-C.x*e,y:S-C.y*e})}},[m,k,A,z,M,b,D]),Ue=t.useCallback(()=>{if(!m)return;const e=Math.max(k-A,G);if(e!==k){const u=M.width||b?.canvas.width||800,x=M.height||b?.canvas.height||600,y=u/2,S=x/2,C={x:(y-D.x)/k,y:(S-D.y)/k};J(e),U({x:y-C.x*e,y:S-C.y*e})}},[m,k,A,G,M,b,D]),He=t.useCallback(e=>{U({x:e.target.x(),y:e.target.y()})},[]),qe=t.useCallback(e=>{if(!m)return;e.evt.preventDefault();const u=X.current;if(!u)return;const x=u.scaleX(),y=u.getPointerPosition();if(!y)return;const S=1.1,C=e.evt.deltaY>0?x/S:x*S,te=Math.min(Math.max(C,G),z),le={x:(y.x-D.x)/x,y:(y.y-D.y)/x},xe={x:y.x-le.x*te,y:y.y-le.y*te};J(te),U(xe)},[m,D,G,z]);Me(X,{enabled:ve&&m,minScale:G,maxScale:z,currentScale:k,currentPosition:D,onScaleChange:(e,u)=>{J(e),U(u)},onPositionChange:e=>{U(e)}});const _e=t.useCallback((e,u)=>{if(!g)return;const x=u.target.getStage();if(!x)return;const y=x.getPointerPosition();if(!y)return;const S=x.container().getBoundingClientRect();me({visible:!0,x:S.left+y.x,y:S.top+y.y,seat:e,state:Q(e)})},[g,Q]),Ge=t.useCallback(()=>{me(e=>({...e,visible:!1}))},[]);if(We)return i.jsx("div",{className:`flex items-center justify-center h-full ${w}`,children:i.jsx("p",{children:"Loading seat map..."})});if(Z)return i.jsx("div",{className:`flex items-center justify-center h-full ${w}`,children:i.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",Z.message]})});if(!b)return i.jsx("div",{className:`flex items-center justify-center h-full ${w}`,children:i.jsx("p",{children:"No configuration provided"})});const Je=M.width||b.canvas.width,Ke=M.height||b.canvas.height;return i.jsxs("div",{ref:O,className:`relative ${w}`,style:{width:"100%",height:"100%"},children:[$e&&pe.length>0&&i.jsx(Ie,{floors:pe,currentFloorId:N,onFloorChange:Oe,showAllOption:L,allLabel:T,position:j,className:I}),i.jsxs(P.Stage,{ref:X,width:Je,height:Ke,scaleX:k,scaleY:k,x:D.x,y:D.y,draggable:!0,onDragEnd:He,onWheel:qe,style:{backgroundColor:b.canvas.backgroundColor,cursor:"grab"},children:[i.jsx(P.Layer,{listening:!1,children:ce.map(e=>i.jsx(ke,{stage:e,stageColor:ae.stageColor},e.id))}),i.jsx(P.Layer,{children:_.map(e=>i.jsx(Fe,{seat:e,state:Q(e),colors:ae,onClick:Be,onMouseEnter:_e,onMouseLeave:Ge},e.id))})]}),g&&i.jsx(De,{visible:K.visible,x:K.x,y:K.y,seat:K.seat,currency:ae.currency,state:K.state}),q&&m&&i.jsx(Le,{scale:k,minScale:G,maxScale:z,onZoomIn:Ve,onZoomOut:Ue,position:fe,className:se}),ee.length>0&&i.jsxs("div",{className:"absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg",children:[i.jsxs("h3",{className:"font-semibold mb-2",children:["Selected Seats (",ee.length,")"]}),i.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:ee.map(e=>i.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${ae.currency} ${e.price.toFixed(2)}`]},e.id))})]})]})};let ne=null;function it(n){ne=n}function Se(){if(!ne)throw new Error("Firebase database not initialized. Call initializeFirebaseForViewer(db) first.");return ne}function de(){return ne!==null}function at(){ne=null}function ye(n,s){if(n.length!==s.length)return!1;for(let o=0;o<n.length;o++)if(n[o]!==s[o])return!1;return!0}function ct(n,s){return ye(n.myReserved,s.myReserved)&&ye(n.otherReserved,s.otherReserved)&&ye(n.unavailable,s.unavailable)}function lt(n,s){const o=[],r=[],h=[];return Object.entries(n).forEach(([d,l])=>{l&&typeof l=="object"&&l.state&&(l.state==="unavailable"?h.push(d):l.state==="reserved"&&(s&&l.userId===s?o.push(d):r.push(d)))}),{myReserved:o,otherReserved:r,unavailable:h}}function Te(n){const{seatMapId:s,currentUserId:o,enabled:r=!0,onStateChange:h,onError:d}=n,[l,v]=t.useState(null),[p,a]=t.useState(!0),[c,f]=t.useState(null),[g,m]=t.useState(null),[w,R]=t.useState([]),[E,F]=t.useState([]),[j,I]=t.useState([]),L=t.useRef(h),T=t.useRef(d),V=t.useRef(o);L.current=h,T.current=d,V.current=o;const W=t.useRef({myReserved:[],otherReserved:[],unavailable:[]}),q=t.useRef(null);return t.useEffect(()=>{if(!r||!s){a(!1);return}if(!de()){a(!1),f(new Error("Firebase not initialized. Call initializeFirebaseForViewer first."));return}const fe=Se(),se=B.ref(fe,`seat_states/${s}`);a(!0),f(null);const re=A=>{const X=A.val()||{},O=lt(X,V.current),M=W.current,Y=JSON.stringify(q.current)!==JSON.stringify(X);Y&&(v(X),q.current=X),a(!1),!ct(M,O)&&(m(Date.now()),R(O.myReserved),F(O.otherReserved),I(O.unavailable),W.current=O),Y&&L.current?.(X)},z=A=>{f(A),a(!1),T.current?.(A)};return B.onValue(se,re,z),()=>{B.off(se)}},[s,r]),{states:l,loading:p,error:c,lastUpdated:g,myReservedSeats:w,otherReservedSeats:E,unavailableSeats:j,reservedSeats:E}}function ze(n){const{seatMapId:s,enabled:o=!0,subscribeToChanges:r=!1,onConfigLoad:h,onError:d}=n,[l,v]=t.useState(null),[p,a]=t.useState(!0),[c,f]=t.useState(null),g=t.useRef(h),m=t.useRef(d);g.current=h,m.current=d;const w=t.useCallback(async()=>{if(!s)return;if(!de()){f(new Error("Firebase not initialized. Call initializeFirebaseForViewer first.")),a(!1);return}const R=Se(),E=B.ref(R,`seatmaps/${s}`);try{a(!0),f(null);const j=(await B.get(E)).val();if(j){const I=rt.fromFirebaseSeatMap(j);v(I),g.current?.(I)}else f(new Error(`Seat map ${s} not found in Firebase`))}catch(F){const j=F instanceof Error?F:new Error("Unknown error");f(j),m.current?.(j)}finally{a(!1)}},[s]);return t.useEffect(()=>{if(!o||!s){a(!1);return}if(w(),r&&de()){const R=Se(),E=B.ref(R,`seatmaps/${s}/meta/updated_at`);let F=!0,j=null;const I=L=>{if(F){F=!1,j=L.val();return}const T=L.val();L.exists()&&T!==j&&(j=T,w())};return B.onValue(E,I),()=>{B.off(E)}}},[s,o,r]),{config:l,loading:p,error:c,refetch:w}}function ut(n){const{seatMapId:s,userId:o,enabled:r=!0,subscribeToDesignChanges:h=!1,onConfigLoad:d,onStateChange:l,onError:v}=n,{config:p,loading:a,error:c,refetch:f}=ze({seatMapId:s,enabled:r,subscribeToChanges:h,onConfigLoad:d,onError:v}),{states:g,loading:m,error:w,lastUpdated:R,myReservedSeats:E,otherReservedSeats:F,unavailableSeats:j,reservedSeats:I}=Te({seatMapId:s,currentUserId:o,enabled:r,onStateChange:l,onError:v});return{config:p,loading:a||m,error:c||w,myReservedSeats:E,otherReservedSeats:F,unavailableSeats:j,reservedSeats:I,seatStates:g,lastUpdated:R,refetch:f}}exports.DEFAULT_COLORS=Ee;exports.SeatMapViewer=ot;exports.clearFirebaseInstance=at;exports.initializeFirebaseForViewer=it;exports.isFirebaseInitialized=de;exports.useConfigFetcher=Re;exports.useContainerSize=je;exports.useFirebaseConfig=ze;exports.useFirebaseSeatStates=Te;exports.useRealtimeSeatMap=ut;exports.useTouchGestures=Me;
|