@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 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. Error Handling
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
- /** Derived arrays for SeatMapViewer props */
22
- reservedSeats: string[];
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 { reservedSeats, unavailableSeats, loading } = useFirebaseSeatStates({
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
- * reservedSeats={reservedSeats}
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
- /** Array of reserved seat IDs (for SeatMapViewer props) */
25
- reservedSeats: string[];
26
- /** Array of unavailable seat IDs (for SeatMapViewer props) */
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
- * reservedSeats,
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
- * reservedSeats={reservedSeats}
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;