@necrolab/dashboard 0.4.3
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/.claude/settings.local.json +45 -0
- package/.eslintrc.js +24 -0
- package/.prettierignore +1 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +3 -0
- package/ICONS.md +21 -0
- package/README.md +65 -0
- package/backend/api.js +430 -0
- package/backend/auth.js +62 -0
- package/backend/batching.js +43 -0
- package/backend/endpoints.js +343 -0
- package/backend/index.js +23 -0
- package/backend/mock-data.js +66 -0
- package/backend/mock-src/classes/logger.js +112 -0
- package/backend/mock-src/classes/utils.js +42 -0
- package/backend/mock-src/ticketmaster.js +92 -0
- package/backend/validator.js +62 -0
- package/config/configs.json +20 -0
- package/config/filter.json +3 -0
- package/config/presale.csv +3 -0
- package/config/proxies.txt +6 -0
- package/config/used-codes.json +4 -0
- package/index.html +114 -0
- package/index.js +2 -0
- package/jsconfig.json +16 -0
- package/package.json +48 -0
- package/postcss.config.js +6 -0
- package/postinstall.js +9 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/flags/ae.svg +1 -0
- package/public/flags/at.svg +1 -0
- package/public/flags/au.svg +1 -0
- package/public/flags/be.svg +1 -0
- package/public/flags/ch.svg +1 -0
- package/public/flags/cz.svg +1 -0
- package/public/flags/de.svg +1 -0
- package/public/flags/dk.svg +1 -0
- package/public/flags/es.svg +1 -0
- package/public/flags/nl.svg +1 -0
- package/public/flags/no.svg +1 -0
- package/public/flags/nz.svg +1 -0
- package/public/flags/pl.svg +1 -0
- package/public/flags/se.svg +1 -0
- package/public/flags/uk.svg +1 -0
- package/public/flags/us.svg +1 -0
- package/public/img/award.svg +3 -0
- package/public/img/background.svg +14 -0
- package/public/img/bag_w.svg +12 -0
- package/public/img/banks/amex.svg +4 -0
- package/public/img/banks/mastercard.svg +4 -0
- package/public/img/banks/visa.svg +4 -0
- package/public/img/camera.svg +3 -0
- package/public/img/close.svg +3 -0
- package/public/img/controls/disable.svg +5 -0
- package/public/img/controls/enable.svg +5 -0
- package/public/img/groups.svg +3 -0
- package/public/img/hand.svg +3 -0
- package/public/img/key.svg +3 -0
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/loyalty.svg +3 -0
- package/public/img/mail.svg +3 -0
- package/public/img/pencil.svg +3 -0
- package/public/img/profile.svg +4 -0
- package/public/img/reload.svg +3 -0
- package/public/img/sandclock.svg +25 -0
- package/public/img/save.svg +5 -0
- package/public/img/savings.svg +3 -0
- package/public/img/scanner.svg +3 -0
- package/public/img/sell.svg +3 -0
- package/public/img/shield.svg +3 -0
- package/public/img/ski.svg +3 -0
- package/public/img/stadium.svg +8 -0
- package/public/img/stadium_w.svg +8 -0
- package/public/img/timer.svg +3 -0
- package/public/manifest.json +27 -0
- package/public/robots.txt +2 -0
- package/run +10 -0
- package/src/App.vue +307 -0
- package/src/assets/css/_input.scss +197 -0
- package/src/assets/css/main.scss +269 -0
- package/src/assets/css/tailwind.css +3 -0
- package/src/assets/img/award.svg +3 -0
- package/src/assets/img/background.svg +11 -0
- package/src/assets/img/camera.svg +3 -0
- package/src/assets/img/close.svg +3 -0
- package/src/assets/img/eyes/closed.svg +13 -0
- package/src/assets/img/eyes/open.svg +12 -0
- package/src/assets/img/groups.svg +3 -0
- package/src/assets/img/hand.svg +3 -0
- package/src/assets/img/key.svg +3 -0
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/assets/img/loyalty.svg +3 -0
- package/src/assets/img/mail.svg +3 -0
- package/src/assets/img/pencil.svg +3 -0
- package/src/assets/img/reload.svg +3 -0
- package/src/assets/img/savings.svg +3 -0
- package/src/assets/img/scanner.svg +3 -0
- package/src/assets/img/sell.svg +3 -0
- package/src/assets/img/shield.svg +3 -0
- package/src/assets/img/ski.svg +3 -0
- package/src/assets/img/square_check.svg +5 -0
- package/src/assets/img/square_uncheck.svg +5 -0
- package/src/assets/img/stadium.svg +8 -0
- package/src/assets/img/timer.svg +3 -0
- package/src/assets/img/wildcard.svg +7 -0
- package/src/components/Auth/LoginForm.vue +48 -0
- package/src/components/Editors/Account/Account.vue +119 -0
- package/src/components/Editors/Account/AccountCreator.vue +147 -0
- package/src/components/Editors/Account/AccountView.vue +87 -0
- package/src/components/Editors/Account/CreateAccount.vue +106 -0
- package/src/components/Editors/Profile/CreateProfile.vue +321 -0
- package/src/components/Editors/Profile/Profile.vue +142 -0
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
- package/src/components/Editors/Profile/ProfileView.vue +96 -0
- package/src/components/Editors/TagLabel.vue +16 -0
- package/src/components/Editors/TagToggle.vue +41 -0
- package/src/components/Filter/Filter.vue +409 -0
- package/src/components/Filter/FilterPreview.vue +236 -0
- package/src/components/Filter/PriceSortToggle.vue +105 -0
- package/src/components/Table/Header.vue +5 -0
- package/src/components/Table/Row.vue +5 -0
- package/src/components/Table/Table.vue +14 -0
- package/src/components/Table/index.js +4 -0
- package/src/components/Tasks/CheckStock.vue +62 -0
- package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
- package/src/components/Tasks/Controls/MobileControls.vue +32 -0
- package/src/components/Tasks/Controls/index.js +3 -0
- package/src/components/Tasks/CreateTaskAXS.vue +339 -0
- package/src/components/Tasks/CreateTaskTM.vue +459 -0
- package/src/components/Tasks/MassEdit.vue +50 -0
- package/src/components/Tasks/QuickSettings.vue +167 -0
- package/src/components/Tasks/ScrapeVenue.vue +42 -0
- package/src/components/Tasks/Stats.vue +66 -0
- package/src/components/Tasks/Task.vue +296 -0
- package/src/components/Tasks/TaskLabel.vue +20 -0
- package/src/components/Tasks/TaskView.vue +126 -0
- package/src/components/Tasks/Utilities.vue +33 -0
- package/src/components/icons/Award.vue +8 -0
- package/src/components/icons/Bag.vue +8 -0
- package/src/components/icons/BagWhite.vue +8 -0
- package/src/components/icons/Box.vue +8 -0
- package/src/components/icons/Camera.vue +8 -0
- package/src/components/icons/Cart.vue +8 -0
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Checkmark.vue +11 -0
- package/src/components/icons/Click.vue +8 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Console.vue +13 -0
- package/src/components/icons/Down.vue +8 -0
- package/src/components/icons/Edit.vue +13 -0
- package/src/components/icons/Event.vue +8 -0
- package/src/components/icons/Expand.vue +8 -0
- package/src/components/icons/Filter.vue +13 -0
- package/src/components/icons/Gear.vue +8 -0
- package/src/components/icons/Group.vue +8 -0
- package/src/components/icons/Hand.vue +8 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Logout.vue +13 -0
- package/src/components/icons/Loyalty.vue +8 -0
- package/src/components/icons/Mail.vue +8 -0
- package/src/components/icons/Menu.vue +8 -0
- package/src/components/icons/Pause.vue +5 -0
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +8 -0
- package/src/components/icons/Plus.vue +8 -0
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +7 -0
- package/src/components/icons/Sandclock.vue +33 -0
- package/src/components/icons/Savings.vue +8 -0
- package/src/components/icons/Scanner.vue +8 -0
- package/src/components/icons/Scrape.vue +8 -0
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Shield.vue +8 -0
- package/src/components/icons/Shrink.vue +8 -0
- package/src/components/icons/Ski.vue +8 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +13 -0
- package/src/components/icons/StadiumWhite.vue +13 -0
- package/src/components/icons/Status.vue +8 -0
- package/src/components/icons/Tag.vue +8 -0
- package/src/components/icons/Tasks.vue +13 -0
- package/src/components/icons/Ticket.vue +8 -0
- package/src/components/icons/Timer.vue +8 -0
- package/src/components/icons/Trash.vue +8 -0
- package/src/components/icons/Up.vue +10 -0
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +111 -0
- package/src/components/ui/Modal.vue +61 -0
- package/src/components/ui/Navbar.vue +207 -0
- package/src/components/ui/ReconnectIndicator.vue +90 -0
- package/src/components/ui/Splash.vue +24 -0
- package/src/components/ui/controls/CountryChooser.vue +87 -0
- package/src/components/ui/controls/EyeToggle.vue +11 -0
- package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
- package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
- package/src/components/ui/controls/atomic/Switch.vue +84 -0
- package/src/libs/Filter.js +593 -0
- package/src/libs/ansii.js +565 -0
- package/src/libs/panzoom.js +1413 -0
- package/src/main.js +23 -0
- package/src/registerServiceWorker.js +32 -0
- package/src/router/index.js +65 -0
- package/src/stores/cities.json +1 -0
- package/src/stores/connection.js +399 -0
- package/src/stores/countries.js +88 -0
- package/src/stores/logger.js +103 -0
- package/src/stores/requests.js +88 -0
- package/src/stores/sampleData.js +1034 -0
- package/src/stores/ui.js +584 -0
- package/src/stores/utils.js +554 -0
- package/src/types/index.js +42 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +191 -0
- package/src/views/Console.vue +224 -0
- package/src/views/Editor.vue +785 -0
- package/src/views/FilterBuilder.vue +785 -0
- package/src/views/Login.vue +27 -0
- package/src/views/Profiles.vue +209 -0
- package/src/views/Tasks.vue +157 -0
- package/static/offline.html +184 -0
- package/tailwind.config.js +57 -0
- package/vite.config.js +63 -0
- package/vue.config.js +32 -0
- package/workbox-config.js +66 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="filter-builder-container w-full min-h-0 flex flex-col overflow-hidden">
|
|
3
|
+
<!-- Heading -->
|
|
4
|
+
<div class="flex-between pt-5 pb-2 mx-4 mt-2">
|
|
5
|
+
<div class="flex-center gap-4">
|
|
6
|
+
<FilterIcon class="cursor-pointer smooth-hover text-white" />
|
|
7
|
+
<h4 class="text-heading">Filter creator</h4>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="flex items-center">
|
|
10
|
+
<input
|
|
11
|
+
class="h-10 text-white text-sm p-2 bg-dark-500 w-48 flex items-center rounded-l relative border-2 border-dark-550"
|
|
12
|
+
placeholder="Event ID"
|
|
13
|
+
v-model="eventId"
|
|
14
|
+
/>
|
|
15
|
+
<button
|
|
16
|
+
class="h-10 text-white text-sm px-3 bg-dark-400 flex items-center rounded-r relative font-medium border-2 border-dark-550 smooth-hover"
|
|
17
|
+
@click="updateShownVenue"
|
|
18
|
+
>
|
|
19
|
+
Load
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="card-dark mx-4 mb-3 p-3 overflow-hidden">
|
|
25
|
+
<div class="w-full h-full">
|
|
26
|
+
<!-- Main -->
|
|
27
|
+
<div class="grid grid-cols-1 lg:grid-cols-5 gap-3 lg:gap-4 w-full h-full">
|
|
28
|
+
<!-- Map -->
|
|
29
|
+
<div
|
|
30
|
+
class="col-span-1 lg:col-span-3 w-full h-full rounded-lg relative flex flex-col lg:max-w-none lg:overflow-hidden"
|
|
31
|
+
>
|
|
32
|
+
<div v-if="svg" class="flex items-center mb-1">
|
|
33
|
+
<div class="flex items-center justify-between w-20 px-2 text-white font-black text-sm">
|
|
34
|
+
<span class="cursor-pointer" @click="handleZoom(true)">+</span>
|
|
35
|
+
<span class="cursor-pointer" @click="handleZoom(false)">-</span>
|
|
36
|
+
<ReloadIcon class="cursor-pointer w-4 h-4" @click="handleZoom('r')" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="overflow-hidden selecto-wrapper flex-1">
|
|
40
|
+
<div
|
|
41
|
+
v-if="svg"
|
|
42
|
+
class="h-full overflow-auto hidden-scrollbars p-2 rounded shadow border-2 border-dark-550 svg-container"
|
|
43
|
+
>
|
|
44
|
+
<div class="svg-wrapper" id="svg-wrapper" v-html="svg"></div>
|
|
45
|
+
</div>
|
|
46
|
+
<div
|
|
47
|
+
v-else
|
|
48
|
+
class="h-full flex items-center justify-center p-2 rounded shadow border-2 border-dark-550 svg-container"
|
|
49
|
+
>
|
|
50
|
+
<div class="text-center text-dark-400">
|
|
51
|
+
<StadiumIcon class="mx-auto mb-2 w-8 h-8 opacity-50" />
|
|
52
|
+
<p class="text-sm">Enter an event ID and click "Load" to display the venue map</p>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="col-span-1 lg:col-span-2 w-full flex flex-col h-full">
|
|
58
|
+
<div class="flex justify-between mb-2 items-center text-white">
|
|
59
|
+
<div class="rounded flex justify-between gap-2 items-center" v-if="hasLoaded">
|
|
60
|
+
<PriceSortToggle
|
|
61
|
+
:current="filterBuilder.globalFilter.priceSort"
|
|
62
|
+
class="smooth-hover"
|
|
63
|
+
@change="(e) => filterBuilder.updateGlobalFilter({ priceSort: e })"
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<input
|
|
67
|
+
type="number"
|
|
68
|
+
:value="filterBuilder.globalFilter.maxPrice"
|
|
69
|
+
@input="
|
|
70
|
+
(e) =>
|
|
71
|
+
filterBuilder.updateGlobalFilter({
|
|
72
|
+
maxPrice: parseInt(e.target.value || 0)
|
|
73
|
+
})
|
|
74
|
+
"
|
|
75
|
+
class="w-14 bg-dark-500 border-2 border-dark-550 px-1 pl-2 h-8 rounded"
|
|
76
|
+
placeholder="max"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<Table class="border-2 border-dark-550 shadow-xl flex-1 flex flex-col">
|
|
81
|
+
<Header class="flex-shrink-0">
|
|
82
|
+
<div class="flex items-center font-bold gap-2 justify-between w-full">
|
|
83
|
+
<div class="flex items-center gap-2">
|
|
84
|
+
<span class="text-base">Filters</span>
|
|
85
|
+
<span class="text-base text-light-300">{{ filterBuilder.filters.length }}</span>
|
|
86
|
+
<PriceSortToggle
|
|
87
|
+
class="w-14 smooth-hover"
|
|
88
|
+
:options="['All', 'WL', 'BL']"
|
|
89
|
+
@change="(e) => (shownFilters = e)"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="flex gap-2 items-center">
|
|
93
|
+
<button
|
|
94
|
+
class="header-btn save-btn"
|
|
95
|
+
@click="saveFilter"
|
|
96
|
+
>
|
|
97
|
+
<EditIcon class="w-4 h-4" />
|
|
98
|
+
<span class="lg:block hidden">Save</span>
|
|
99
|
+
</button>
|
|
100
|
+
<button
|
|
101
|
+
class="header-btn clear-btn"
|
|
102
|
+
@click="filterBuilder.reset(false)"
|
|
103
|
+
>
|
|
104
|
+
<TrashIcon class="w-4 h-4" />
|
|
105
|
+
<span class="lg:block hidden">Clear</span>
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</Header>
|
|
110
|
+
<div class="overflow-auto hidden-scrollbars flex-1">
|
|
111
|
+
<div v-if="filterBuilder.filters.length" class="filters-container">
|
|
112
|
+
<draggable
|
|
113
|
+
:list="filterBuilder.filters"
|
|
114
|
+
handle=".handle"
|
|
115
|
+
item-key="id"
|
|
116
|
+
ghost-class="sortable-ghost"
|
|
117
|
+
chosen-class="sortable-chosen"
|
|
118
|
+
drag-class="sortable-drag"
|
|
119
|
+
:animation="200"
|
|
120
|
+
:easing="'cubic-bezier(0.4, 0, 0.2, 1)'"
|
|
121
|
+
:force-fallback="false"
|
|
122
|
+
@change="
|
|
123
|
+
() => {
|
|
124
|
+
filterBuilder.updateCss();
|
|
125
|
+
cssUpdateTrigger++;
|
|
126
|
+
}
|
|
127
|
+
"
|
|
128
|
+
>
|
|
129
|
+
<template #item="{ element: f, index: i }">
|
|
130
|
+
<Filter
|
|
131
|
+
v-if="doesFilterShow(f)"
|
|
132
|
+
:filter="f"
|
|
133
|
+
:index="i"
|
|
134
|
+
:filterBuilder="filterBuilder"
|
|
135
|
+
class="compact-filter"
|
|
136
|
+
/>
|
|
137
|
+
</template>
|
|
138
|
+
</draggable>
|
|
139
|
+
</div>
|
|
140
|
+
<div v-else class="empty-state flex flex-col items-center justify-center py-8 text-center">
|
|
141
|
+
<FilterIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50" />
|
|
142
|
+
<p class="text-dark-400 text-sm">No filters yet</p>
|
|
143
|
+
<p class="text-dark-500 text-xs mt-1">Click on the map to create filters</p>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</Table>
|
|
147
|
+
<div class="flex items-center justify-between text-white gap-2 mt-1 mb-4 md:mb-0 flex-shrink-0">
|
|
148
|
+
<button
|
|
149
|
+
@click="addWildcardFilter"
|
|
150
|
+
:disabled="hasWildcardFilter"
|
|
151
|
+
:class="[
|
|
152
|
+
'border-2 rounded border-dark-550 px-2 h-7 text-xs bg-dark-500 overflow-hidden shadow transition-all duration-200',
|
|
153
|
+
hasWildcardFilter
|
|
154
|
+
? 'text-gray opacity-50 cursor-not-allowed'
|
|
155
|
+
: 'text-gray smooth-hover hover:bg-dark-400 hover:border-light-300'
|
|
156
|
+
]"
|
|
157
|
+
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'"
|
|
158
|
+
>
|
|
159
|
+
* Wildcard
|
|
160
|
+
</button>
|
|
161
|
+
<button
|
|
162
|
+
@click="ui.toggleModal('preview-filter')"
|
|
163
|
+
class="border-2 gap-1 flex justify-between items-center rounded border-dark-550 px-2 h-7 text-gray text-xs bg-dark-500 overflow-hidden shadow smooth-hover"
|
|
164
|
+
>
|
|
165
|
+
<CameraIcon class="w-3 h-3" />
|
|
166
|
+
JSON
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<transition-group name="fade" mode="out-in">
|
|
174
|
+
<FilterPreview v-if="activeModal === 'preview-filter'" :filter="filterBuilder" />
|
|
175
|
+
</transition-group>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</template>
|
|
179
|
+
|
|
180
|
+
<script setup>
|
|
181
|
+
import draggable from "vuedraggable";
|
|
182
|
+
import { ref, nextTick, watch, computed, onMounted, onUnmounted } from "vue";
|
|
183
|
+
import { onBeforeRouteLeave } from "vue-router";
|
|
184
|
+
import { Table, Header } from "@/components/Table";
|
|
185
|
+
import Filter from "@/components/Filter/Filter.vue";
|
|
186
|
+
import { FilterIcon } from "@/components/icons";
|
|
187
|
+
import DragSelect from "dragselect";
|
|
188
|
+
import { DEBUG } from "@/utils/debug";
|
|
189
|
+
|
|
190
|
+
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon, StadiumIcon } from "@/components/icons";
|
|
191
|
+
import { sendSaveFilter } from "@/stores/requests";
|
|
192
|
+
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
193
|
+
import { useUIStore } from "@/stores/ui";
|
|
194
|
+
import FilterBuilder from "@/libs/Filter";
|
|
195
|
+
import panzoom from "@/libs/panzoom.js";
|
|
196
|
+
import FilterPreview from "@/components/Filter/FilterPreview.vue";
|
|
197
|
+
const activeModal = computed(() => ui.activeModal);
|
|
198
|
+
|
|
199
|
+
const ui = useUIStore();
|
|
200
|
+
const hasLoaded = ref(false);
|
|
201
|
+
|
|
202
|
+
// let isShiftPressed = false;
|
|
203
|
+
// window.onkeyup = (e) => {
|
|
204
|
+
// if (e.keyCode === 16) isShiftPressed = false;
|
|
205
|
+
// };
|
|
206
|
+
// window.onkeydown = (e) => {
|
|
207
|
+
// if (e.keyCode === 16) isShiftPressed = true;
|
|
208
|
+
// };
|
|
209
|
+
|
|
210
|
+
// const doDragSelect = () => {
|
|
211
|
+
// // https://dragselect.com/docs/API/Settings
|
|
212
|
+
// const ds = new DragSelect({
|
|
213
|
+
// selectables: [
|
|
214
|
+
// ...document.querySelectorAll("path[row]"),
|
|
215
|
+
// ...document.querySelectorAll("path[generaladmission]")
|
|
216
|
+
// ],
|
|
217
|
+
// area: document.getElementById("svg-wrapper"),
|
|
218
|
+
// selectionThreshold: 0.1,
|
|
219
|
+
// multiSelectKeys: []
|
|
220
|
+
// });
|
|
221
|
+
|
|
222
|
+
// ds.subscribe("DS:start:pre", () => {
|
|
223
|
+
// // ui.logger.Info("DS:start:pre - shift pressed:", isShiftPressed);
|
|
224
|
+
// if (!isShiftPressed) {
|
|
225
|
+
// ds.stop(false, false);
|
|
226
|
+
// ds.start();
|
|
227
|
+
// } else {
|
|
228
|
+
// panzoomInstance.pause();
|
|
229
|
+
// }
|
|
230
|
+
// });
|
|
231
|
+
|
|
232
|
+
// const parseSelected = (items) => {
|
|
233
|
+
// const parsed = items
|
|
234
|
+
// .map((e) => {
|
|
235
|
+
// return {
|
|
236
|
+
// section: e.attributes.sectionname?.nodeValue || e.attributes.name?.nodeValue,
|
|
237
|
+
// row: e.attributes.row?.nodeValue,
|
|
238
|
+
// GA: e.attributes.generaladmission?.nodeValue,
|
|
239
|
+
// name: e.attributes.name?.nodeValue
|
|
240
|
+
// };
|
|
241
|
+
// })
|
|
242
|
+
// .filter((e) => !filterBuilder.value.isUnselectable(e.section, e.row));
|
|
243
|
+
|
|
244
|
+
// const sectionMapping = {};
|
|
245
|
+
// parsed
|
|
246
|
+
// .filter((p) => !p.GA)
|
|
247
|
+
// .forEach((p) => {
|
|
248
|
+
// if (!sectionMapping[p.section]) sectionMapping[p.section] = [];
|
|
249
|
+
// sectionMapping[p.section].push(p);
|
|
250
|
+
// });
|
|
251
|
+
// parsed
|
|
252
|
+
// .filter((p) => p.GA)
|
|
253
|
+
// .forEach((p) => {
|
|
254
|
+
// sectionMapping[p.section] = {
|
|
255
|
+
// GA: true,
|
|
256
|
+
// section: p.section,
|
|
257
|
+
// name: p.name
|
|
258
|
+
// };
|
|
259
|
+
// });
|
|
260
|
+
// return sectionMapping;
|
|
261
|
+
// };
|
|
262
|
+
|
|
263
|
+
// ds.subscribe("DS:update:pre", (e) => {
|
|
264
|
+
// // ui.logger.Info("DS:update:pre - shift pressed:", isShiftPressed, e.items);
|
|
265
|
+
// filterBuilder.value.isDragging = true;
|
|
266
|
+
// if (!isShiftPressed) return;
|
|
267
|
+
// const sectionMapping = parseSelected(e.items);
|
|
268
|
+
// filterBuilder.value.clearHighlight();
|
|
269
|
+
// Object.entries(sectionMapping).forEach(([section, rows]) => {
|
|
270
|
+
// if (rows.GA) {
|
|
271
|
+
// const isSelected = filterBuilder.value.filters.find(
|
|
272
|
+
// (f) => filterBuilder.value.isForCurrentEvent(f) && f.section === section
|
|
273
|
+
// );
|
|
274
|
+
// if (isSelected) return;
|
|
275
|
+
// filterBuilder.value.highlight({ section: section }, true);
|
|
276
|
+
// } else
|
|
277
|
+
// rows.forEach((row) => {
|
|
278
|
+
// const isSelected = filterBuilder.value.filters.find(
|
|
279
|
+
// (f) =>
|
|
280
|
+
// (f.section === section && f.rows?.includes(row.row)) ||
|
|
281
|
+
// (!f.rows && f.section === section && filterBuilder.value.isForCurrentEvent(f))
|
|
282
|
+
// );
|
|
283
|
+
// if (isSelected) return;
|
|
284
|
+
|
|
285
|
+
// filterBuilder.value.highlight({ section, row: row.row });
|
|
286
|
+
// });
|
|
287
|
+
// });
|
|
288
|
+
// });
|
|
289
|
+
|
|
290
|
+
// ds.subscribe("DS:end", (e) => {
|
|
291
|
+
// filterBuilder.value.isDragging = false;
|
|
292
|
+
// panzoomInstance.resume();
|
|
293
|
+
// filterBuilder.value.clearHighlight();
|
|
294
|
+
// [...document.querySelectorAll("path")].map((f) => (f.style = ""));
|
|
295
|
+
// if (!isShiftPressed) return;
|
|
296
|
+
// const sectionMapping = parseSelected(e.items);
|
|
297
|
+
// const secNameMapping = filterBuilder.value.getSectionNameMapping();
|
|
298
|
+
|
|
299
|
+
// Object.entries(sectionMapping).forEach(([section, rows]) => {
|
|
300
|
+
// if (rows.GA) {
|
|
301
|
+
// const isSelected = filterBuilder.value.filters.find(
|
|
302
|
+
// (f) => filterBuilder.value.isForCurrentEvent(f) && f.section === secNameMapping[section]
|
|
303
|
+
// );
|
|
304
|
+
// if (isSelected) return;
|
|
305
|
+
// filterBuilder.value.addFilter({
|
|
306
|
+
// event: filterBuilder.value.currentEventId,
|
|
307
|
+
// section: secNameMapping[section]
|
|
308
|
+
// });
|
|
309
|
+
// } else {
|
|
310
|
+
// const unselectedRows = [];
|
|
311
|
+
// rows.forEach((row) => {
|
|
312
|
+
// const isSelected = filterBuilder.value.filters.find(
|
|
313
|
+
// (f) =>
|
|
314
|
+
// (f.section === section && f.rows?.includes(row.row)) ||
|
|
315
|
+
// (!f.rows && f.section === section && filterBuilder.value.isForCurrentEvent(f))
|
|
316
|
+
// );
|
|
317
|
+
// if (isSelected) return;
|
|
318
|
+
// unselectedRows.push(row.row);
|
|
319
|
+
// });
|
|
320
|
+
// if (unselectedRows.length === 0) return;
|
|
321
|
+
// filterBuilder.value.addFilter({
|
|
322
|
+
// event: filterBuilder.value.currentEventId,
|
|
323
|
+
// section,
|
|
324
|
+
// rows: unselectedRows
|
|
325
|
+
// });
|
|
326
|
+
// }
|
|
327
|
+
// });
|
|
328
|
+
// isShiftPressed = false;
|
|
329
|
+
// });
|
|
330
|
+
// };
|
|
331
|
+
|
|
332
|
+
const panzoomOptions = {
|
|
333
|
+
maxZoom: 8, // max zoom-in factor
|
|
334
|
+
minZoom: 0.8, // max zoom-out factor
|
|
335
|
+
panOnlyWhenZoomed: true,
|
|
336
|
+
bounds: true,
|
|
337
|
+
boundsPadding: 0.1, // Add padding to ensure it stays within bounds
|
|
338
|
+
transformOrigin: { x: 0.5, y: 0.5 }, // Center the zoom
|
|
339
|
+
contain: true // Force the image to stay within the parent container
|
|
340
|
+
};
|
|
341
|
+
let panzoomInstance;
|
|
342
|
+
|
|
343
|
+
const svg = ref("");
|
|
344
|
+
const eventId = ref("");
|
|
345
|
+
const renderSeats = ref(false);
|
|
346
|
+
let renderer;
|
|
347
|
+
|
|
348
|
+
const shownFilters = ref("All");
|
|
349
|
+
const filterBuilder = ref(new FilterBuilder());
|
|
350
|
+
|
|
351
|
+
const hasWildcardFilter = computed(() => {
|
|
352
|
+
return filterBuilder.value.filters.some(filter =>
|
|
353
|
+
filter.buyAny && filterBuilder.value.isForCurrentEvent(filter)
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const addWildcardFilter = () => {
|
|
358
|
+
if (!hasWildcardFilter.value) {
|
|
359
|
+
filterBuilder.value.addFilter({ buyAny: true, eventId: filterBuilder.value.currentEventId });
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Initialize RendererFactory
|
|
364
|
+
let RendererFactory;
|
|
365
|
+
if (DEBUG) {
|
|
366
|
+
RendererFactory = class {
|
|
367
|
+
constructor() {}
|
|
368
|
+
};
|
|
369
|
+
} else RendererFactory = import("@necrolab/tm-renderer");
|
|
370
|
+
|
|
371
|
+
// Real-time CSS injection system
|
|
372
|
+
let styleElement = null;
|
|
373
|
+
const cssUpdateTrigger = ref(0);
|
|
374
|
+
const STYLE_ELEMENT_ID = "filter-builder-styles";
|
|
375
|
+
|
|
376
|
+
const injectStyles = () => {
|
|
377
|
+
// Only inject styles if we have an SVG to style
|
|
378
|
+
const svgWrapper = document.getElementById("svg-wrapper");
|
|
379
|
+
if (!svgWrapper || !svg.value) return;
|
|
380
|
+
|
|
381
|
+
if (!styleElement) {
|
|
382
|
+
// Remove any existing style element first
|
|
383
|
+
const existingStyle = document.getElementById(STYLE_ELEMENT_ID);
|
|
384
|
+
if (existingStyle) {
|
|
385
|
+
existingStyle.remove();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
styleElement = document.createElement("style");
|
|
389
|
+
styleElement.id = STYLE_ELEMENT_ID;
|
|
390
|
+
document.head.appendChild(styleElement);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const combinedCSS = filterBuilder.value.cssClasses + filterBuilder.value.temporaryCSS;
|
|
394
|
+
if (styleElement.textContent !== combinedCSS) {
|
|
395
|
+
styleElement.textContent = combinedCSS;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Setup real-time CSS updates
|
|
400
|
+
const setupCSSUpdates = () => {
|
|
401
|
+
// Override updateCss method to trigger immediate updates
|
|
402
|
+
const originalUpdateCss = filterBuilder.value.updateCss.bind(filterBuilder.value);
|
|
403
|
+
filterBuilder.value.updateCss = () => {
|
|
404
|
+
originalUpdateCss();
|
|
405
|
+
nextTick(() => {
|
|
406
|
+
injectStyles();
|
|
407
|
+
cssUpdateTrigger.value++;
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Override highlight method for immediate temporary CSS
|
|
412
|
+
const originalHighlight = filterBuilder.value.highlight.bind(filterBuilder.value);
|
|
413
|
+
filterBuilder.value.highlight = (...args) => {
|
|
414
|
+
originalHighlight(...args);
|
|
415
|
+
nextTick(() => {
|
|
416
|
+
injectStyles();
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Override clearHighlight method
|
|
421
|
+
const originalClearHighlight = filterBuilder.value.clearHighlight.bind(filterBuilder.value);
|
|
422
|
+
filterBuilder.value.clearHighlight = () => {
|
|
423
|
+
originalClearHighlight();
|
|
424
|
+
nextTick(() => {
|
|
425
|
+
injectStyles();
|
|
426
|
+
});
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Cleanup function
|
|
431
|
+
const cleanupStyles = () => {
|
|
432
|
+
// Remove by ID to ensure we get it even if reference is lost
|
|
433
|
+
const existingStyle = document.getElementById(STYLE_ELEMENT_ID);
|
|
434
|
+
if (existingStyle) {
|
|
435
|
+
existingStyle.remove();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (styleElement) {
|
|
439
|
+
styleElement.remove();
|
|
440
|
+
styleElement = null;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// Basic cleanup monitoring
|
|
445
|
+
const debugCleanup = () => {
|
|
446
|
+
console.log("FilterBuilder cleanup completed");
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// Clean up any leftover styles from previous instances
|
|
450
|
+
cleanupStyles();
|
|
451
|
+
|
|
452
|
+
// Initialize CSS system
|
|
453
|
+
setupCSSUpdates();
|
|
454
|
+
|
|
455
|
+
// Watch for reactive updates
|
|
456
|
+
watch(
|
|
457
|
+
cssUpdateTrigger,
|
|
458
|
+
() => {
|
|
459
|
+
injectStyles();
|
|
460
|
+
},
|
|
461
|
+
{ immediate: true }
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
// Cleanup on unmount
|
|
465
|
+
onUnmounted(() => {
|
|
466
|
+
cleanupStyles();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Also cleanup on route change using beforeRouteLeave
|
|
470
|
+
onBeforeRouteLeave(() => {
|
|
471
|
+
cleanupStyles();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const doesFilterShow = (filter) => {
|
|
475
|
+
if ((filter.event || filter.eventId) !== filterBuilder.value.currentEventId) return;
|
|
476
|
+
if (!["BL", "WL"].includes(shownFilters.value)) return true;
|
|
477
|
+
const filterState = filter.exclude ? "BL" : "WL";
|
|
478
|
+
return shownFilters.value === filterState;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
let rendererFactory;
|
|
482
|
+
if (!DEBUG)
|
|
483
|
+
RendererFactory.then((r) => {
|
|
484
|
+
rendererFactory = new r.default();
|
|
485
|
+
rendererFactory.init();
|
|
486
|
+
}).catch((error) => {
|
|
487
|
+
console.error("Failed to initialize renderer:", error);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const updateShownVenue = async () => {
|
|
491
|
+
eventId.value = eventId.value.trim();
|
|
492
|
+
if (eventId.value.includes("/event/")) eventId.value = eventId.value.split("/event/")[1];
|
|
493
|
+
|
|
494
|
+
const country = eventId.value.length === 16 ? null : ui.currentCountry.id;
|
|
495
|
+
|
|
496
|
+
if (!country && eventId.value.length !== 16) {
|
|
497
|
+
ui.showError("Invalid eventId!");
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
renderer = rendererFactory.createRenderer(eventId.value, "", country);
|
|
502
|
+
filterBuilder.value.highlightedSeatColor = renderer.c.highlightedSeatColor;
|
|
503
|
+
renderer.c.logFullError = true;
|
|
504
|
+
renderer.c.includeDetailedAttributes = true;
|
|
505
|
+
renderer.c.renderRowBlocks = true;
|
|
506
|
+
renderer.setUrlProxy("/api/cors?url=");
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
await renderer.render();
|
|
510
|
+
svg.value = renderer.svg;
|
|
511
|
+
} catch (e) {
|
|
512
|
+
ui.logger.Error("COULD NOT RENDER SVG", e);
|
|
513
|
+
ui.showError(e.message);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (!svg.value) {
|
|
517
|
+
ui.logger.Error("Could not render SVG");
|
|
518
|
+
ui.showError("Could not fetch event map");
|
|
519
|
+
await loadFilter();
|
|
520
|
+
filterBuilder.value.reload(eventId.value);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
await nextTick();
|
|
525
|
+
if (panzoomInstance?.dispose) panzoomInstance.dispose();
|
|
526
|
+
const elem = document.getElementById("svg-wrapper");
|
|
527
|
+
try {
|
|
528
|
+
// Ensure SVG has appropriate sizing before initializing panzoom
|
|
529
|
+
if (elem && elem.children[0]) {
|
|
530
|
+
const svg = elem.children[0];
|
|
531
|
+
const isDesktop = window.innerWidth >= 1024;
|
|
532
|
+
|
|
533
|
+
// Ensure SVG doesn't exceed container width
|
|
534
|
+
svg.style.maxWidth = "100%";
|
|
535
|
+
svg.style.height = "auto";
|
|
536
|
+
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
537
|
+
|
|
538
|
+
// Additional desktop constraints to prevent expansion
|
|
539
|
+
if (isDesktop) {
|
|
540
|
+
svg.style.maxHeight = "100%";
|
|
541
|
+
svg.style.width = "100%";
|
|
542
|
+
svg.style.objectFit = "contain";
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Initialize panzoom with updated options
|
|
546
|
+
panzoomInstance = panzoom(svg, panzoomOptions);
|
|
547
|
+
|
|
548
|
+
// Reset to initial position
|
|
549
|
+
panzoomInstance.moveTo(0, 0);
|
|
550
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
551
|
+
}
|
|
552
|
+
} catch (e) {
|
|
553
|
+
ui.logger.Error("Couldnt use panzoom", e);
|
|
554
|
+
}
|
|
555
|
+
await nextTick();
|
|
556
|
+
await loadFilter();
|
|
557
|
+
filterBuilder.value.reload(eventId.value);
|
|
558
|
+
|
|
559
|
+
// Re-setup CSS system after reload
|
|
560
|
+
setupCSSUpdates();
|
|
561
|
+
filterBuilder.value.updateCss();
|
|
562
|
+
// doDragSelect();
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const loadFilter = async () => {
|
|
566
|
+
try {
|
|
567
|
+
const res = await fetch(`/api/filter/load?eventId=${eventId.value}`);
|
|
568
|
+
const data = await res.json();
|
|
569
|
+
if (eventId.value) ui.showSuccess("Loaded filter data");
|
|
570
|
+
filterBuilder.value.reset(true);
|
|
571
|
+
filterBuilder.value.updateGlobalFilter(data.globalFilter || {});
|
|
572
|
+
data.filters?.forEach((f) => filterBuilder.value.addFilter(f));
|
|
573
|
+
hasLoaded.value = true;
|
|
574
|
+
} catch (e) {
|
|
575
|
+
ui.showError("Couldn't load filter data");
|
|
576
|
+
ui.logger.Error("Error loading filter data", e);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const saveFilter = async () => {
|
|
581
|
+
const data = filterBuilder.value.out();
|
|
582
|
+
try {
|
|
583
|
+
await sendSaveFilter(data);
|
|
584
|
+
ui.showSuccess("Saved filter data");
|
|
585
|
+
} catch (e) {
|
|
586
|
+
ui.showError("Couldn't save filter data");
|
|
587
|
+
ui.logger.Info("Error saving filter data", e);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const handleZoom = (zoomEvent) => {
|
|
592
|
+
// handle zoom reset
|
|
593
|
+
if (zoomEvent === "r") {
|
|
594
|
+
// Reset both zoom and position
|
|
595
|
+
panzoomInstance.moveTo(0, 0);
|
|
596
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Get current transform
|
|
601
|
+
const { scale } = panzoomInstance.getTransform();
|
|
602
|
+
|
|
603
|
+
// Calculate new scale based on zoom direction
|
|
604
|
+
const newScale = zoomEvent ? scale + scale / 3 : scale - scale / 3;
|
|
605
|
+
|
|
606
|
+
// Apply zoom with bounds checking
|
|
607
|
+
if (newScale >= panzoomOptions.minZoom && newScale <= panzoomOptions.maxZoom) {
|
|
608
|
+
panzoomInstance.smoothZoom(0, 0, newScale / scale);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
watch(renderSeats, async () => {
|
|
613
|
+
ui.logger.Info("Updated renderSeats", renderSeats.value);
|
|
614
|
+
await updateShownVenue();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
(async () => {
|
|
618
|
+
await loadFilter();
|
|
619
|
+
})();
|
|
620
|
+
</script>
|
|
621
|
+
|
|
622
|
+
<style scoped>
|
|
623
|
+
.slider-dim {
|
|
624
|
+
width: 60px;
|
|
625
|
+
height: 30px;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.compact-filter {
|
|
629
|
+
font-size: 0.75rem;
|
|
630
|
+
padding: 0.25rem !important;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.compact-filter .handle {
|
|
634
|
+
transform: scale(0.8);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/* Enhanced table and filter styling */
|
|
638
|
+
.filters-container {
|
|
639
|
+
@apply space-y-1;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/* Dragging states for better UX */
|
|
643
|
+
.sortable-ghost {
|
|
644
|
+
@apply opacity-30;
|
|
645
|
+
border: 1px solid #4A4A61;
|
|
646
|
+
background-color: rgba(74, 74, 97, 0.1);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.sortable-chosen .drag-handle {
|
|
650
|
+
border: 1px solid #4A4A61;
|
|
651
|
+
background-color: rgba(74, 74, 97, 0.2);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.sortable-drag {
|
|
655
|
+
@apply shadow-xl transform rotate-1 scale-105 z-50;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/* Header button styling */
|
|
659
|
+
.header-btn {
|
|
660
|
+
@apply flex items-center gap-2 px-3 py-2 border rounded-md font-medium text-sm transition-all duration-200 hover:scale-105;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.save-btn {
|
|
664
|
+
background-color: rgba(34, 197, 94, 0.2);
|
|
665
|
+
border-color: rgba(74, 222, 128, 0.5);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.save-btn:hover {
|
|
669
|
+
background-color: rgba(34, 197, 94, 0.3);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.clear-btn {
|
|
673
|
+
background-color: rgba(239, 68, 68, 0.2);
|
|
674
|
+
border-color: rgba(248, 113, 113, 0.5);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.clear-btn:hover {
|
|
678
|
+
background-color: rgba(239, 68, 68, 0.3);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.filter-builder-container {
|
|
682
|
+
height: 90vh;
|
|
683
|
+
overflow: hidden;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.card-dark {
|
|
687
|
+
height: calc(100vh - 175px);
|
|
688
|
+
min-height: 500px;
|
|
689
|
+
overflow: hidden;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
@media (max-width: 768px) {
|
|
693
|
+
.filter-builder-container {
|
|
694
|
+
overflow: auto;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.card-dark {
|
|
698
|
+
overflow: auto;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/* Desktop: enforce grid column constraints */
|
|
703
|
+
@media (min-width: 1024px) {
|
|
704
|
+
.lg\\:grid-cols-5 > .lg\\:col-span-3 {
|
|
705
|
+
max-width: 60%; /* 3/5 of grid */
|
|
706
|
+
flex-shrink: 0;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.svg-container {
|
|
711
|
+
min-height: 300px;
|
|
712
|
+
height: 100%;
|
|
713
|
+
width: 100%;
|
|
714
|
+
position: relative;
|
|
715
|
+
overflow: auto;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* Desktop: constrain svg-container to prevent expansion */
|
|
719
|
+
@media (min-width: 1024px) {
|
|
720
|
+
.svg-container {
|
|
721
|
+
position: relative;
|
|
722
|
+
max-width: 100%;
|
|
723
|
+
max-height: 100%;
|
|
724
|
+
overflow: hidden;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
</style>
|
|
728
|
+
|
|
729
|
+
<style>
|
|
730
|
+
/* Global styles needed for dynamically injected SVG content and Filter.js CSS injection */
|
|
731
|
+
.svg-wrapper {
|
|
732
|
+
position: relative;
|
|
733
|
+
transform-origin: 0 0;
|
|
734
|
+
min-width: 100%;
|
|
735
|
+
min-height: 100%;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/* Desktop constraints - prevent svg-wrapper from expanding beyond container */
|
|
739
|
+
@media (min-width: 1024px) {
|
|
740
|
+
.svg-wrapper {
|
|
741
|
+
position: absolute;
|
|
742
|
+
top: 0;
|
|
743
|
+
left: 0;
|
|
744
|
+
width: 100%;
|
|
745
|
+
height: 100%;
|
|
746
|
+
max-width: 100%;
|
|
747
|
+
max-height: 100%;
|
|
748
|
+
min-width: unset;
|
|
749
|
+
min-height: unset;
|
|
750
|
+
overflow: hidden;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.svg-wrapper > svg {
|
|
755
|
+
width: 100%;
|
|
756
|
+
height: auto;
|
|
757
|
+
background: center center no-repeat;
|
|
758
|
+
background-size: contain;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* Desktop SVG constraints */
|
|
762
|
+
@media (min-width: 1024px) {
|
|
763
|
+
.svg-wrapper > svg {
|
|
764
|
+
max-width: 100%;
|
|
765
|
+
max-height: 100%;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* Ensure Filter.js generated CSS works properly */
|
|
770
|
+
.svg-wrapper path {
|
|
771
|
+
pointer-events: auto;
|
|
772
|
+
cursor: pointer;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.svg-wrapper path[generaladmission] {
|
|
776
|
+
pointer-events: auto;
|
|
777
|
+
cursor: pointer;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/* Hover effects for general admission seats */
|
|
781
|
+
.svg-wrapper path[generaladmission]:hover {
|
|
782
|
+
pointer-events: all;
|
|
783
|
+
cursor: pointer;
|
|
784
|
+
}
|
|
785
|
+
</style>
|