@necrolab/dashboard 0.4.220 → 0.5.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/.prettierrc +27 -1
- package/.vscode/extensions.json +1 -1
- package/README.md +64 -2
- package/artwork/image.png +0 -0
- package/backend/api.js +26 -24
- package/backend/auth.js +2 -2
- package/backend/batching.js +1 -1
- package/backend/endpoints.js +8 -11
- package/backend/index.js +2 -2
- package/backend/mock-data.js +27 -36
- package/backend/mock-src/classes/logger.js +5 -7
- package/backend/mock-src/classes/utils.js +3 -2
- package/backend/mock-src/ticketmaster.js +4 -4
- package/backend/validator.js +2 -2
- package/config/configs.json +0 -1
- package/dev-server.js +134 -0
- package/exit +209 -0
- package/index.html +78 -8
- package/index.js +1 -1
- package/jsconfig.json +16 -0
- package/package.json +39 -25
- package/postcss.config.js +1 -1
- package/postinstall.js +124 -20
- 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/img/logo_trans.png +0 -0
- package/public/img/necro_logo.png +0 -0
- package/public/manifest.json +16 -10
- package/run +176 -9
- package/src/App.vue +498 -85
- package/src/assets/css/base/reset.scss +43 -0
- package/src/assets/css/base/scroll.scss +114 -0
- package/src/assets/css/base/typography.scss +37 -0
- package/src/assets/css/components/buttons.scss +216 -0
- package/src/assets/css/components/forms.scss +221 -0
- package/src/assets/css/components/modals.scss +13 -0
- package/src/assets/css/components/tables.scss +27 -0
- package/src/assets/css/components/toasts.scss +100 -0
- package/src/assets/css/main.scss +201 -122
- package/src/assets/img/background.svg +2 -2
- package/src/assets/img/background.svg.backup +11 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/components/Auth/LoginForm.vue +62 -11
- package/src/components/Editors/Account/Account.vue +116 -40
- package/src/components/Editors/Account/AccountCreator.vue +88 -39
- package/src/components/Editors/Account/AccountView.vue +102 -34
- package/src/components/Editors/Account/CreateAccount.vue +80 -32
- package/src/components/Editors/Profile/CreateProfile.vue +269 -83
- package/src/components/Editors/Profile/Profile.vue +132 -47
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
- package/src/components/Editors/Profile/ProfileView.vue +89 -32
- package/src/components/Editors/TagLabel.vue +67 -6
- package/src/components/Editors/TagToggle.vue +7 -2
- package/src/components/Filter/Filter.vue +288 -71
- package/src/components/Filter/FilterPreview.vue +202 -31
- package/src/components/Filter/PriceSortToggle.vue +76 -6
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Row.vue +1 -1
- package/src/components/Table/Table.vue +19 -2
- package/src/components/Tasks/CheckStock.vue +6 -8
- package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
- package/src/components/Tasks/Controls/MobileControls.vue +8 -45
- package/src/components/Tasks/CreateTaskAXS.vue +80 -72
- package/src/components/Tasks/CreateTaskTM.vue +95 -141
- package/src/components/Tasks/MassEdit.vue +4 -6
- package/src/components/Tasks/QuickSettings.vue +199 -30
- package/src/components/Tasks/ScrapeVenue.vue +5 -6
- package/src/components/Tasks/Stats.vue +50 -24
- package/src/components/Tasks/Task.vue +384 -179
- package/src/components/Tasks/TaskLabel.vue +2 -2
- package/src/components/Tasks/TaskView.vue +136 -48
- package/src/components/Tasks/Utilities.vue +25 -10
- package/src/components/Tasks/ViewTask.vue +321 -0
- package/src/components/icons/Bag.vue +1 -1
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Eye.vue +6 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Loyalty.vue +1 -1
- package/src/components/icons/Mail.vue +2 -2
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +2 -2
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +4 -5
- package/src/components/icons/Sandclock.vue +2 -2
- package/src/components/icons/Sell.vue +21 -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 +1 -1
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +26 -1
- package/src/components/ui/Modal.vue +107 -13
- package/src/components/ui/Navbar.vue +175 -40
- package/src/components/ui/ReconnectIndicator.vue +351 -55
- package/src/components/ui/Splash.vue +5 -35
- package/src/components/ui/controls/CountryChooser.vue +200 -62
- package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
- package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
- package/src/components/ui/controls/atomic/Switch.vue +53 -25
- package/src/composables/useClickOutside.js +21 -0
- package/src/composables/useDropdownPosition.js +174 -0
- package/src/libs/Filter.js +60 -24
- package/src/registerServiceWorker.js +1 -1
- package/src/stores/connection.js +4 -4
- package/src/stores/sampleData.js +172 -199
- package/src/stores/ui.js +55 -20
- package/src/stores/utils.js +30 -4
- package/src/types/index.js +41 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +116 -50
- package/src/views/Console.vue +394 -79
- package/src/views/Editor.vue +1176 -123
- package/src/views/FilterBuilder.vue +528 -250
- package/src/views/Login.vue +76 -14
- package/src/views/Profiles.vue +119 -34
- package/src/views/Tasks.vue +266 -98
- package/static/offline.html +192 -50
- package/switch-branch.sh +41 -0
- package/tailwind.config.js +119 -27
- package/vite.config.js +73 -16
- package/workbox-config.cjs +63 -0
- package/ICONS.md +0 -21
- package/public/img/background.svg +0 -14
- 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/src/assets/css/_input.scss +0 -143
- 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/vue.config.js +0 -32
- package/workbox-config.js +0 -7
|
@@ -1,60 +1,76 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
<div class="filter-builder-container flex min-h-0 w-full flex-col overflow-hidden">
|
|
3
|
+
<!-- Heading -->
|
|
4
|
+
<div class="mt-3 flex items-center justify-between pb-1 pt-4">
|
|
5
|
+
<div class="flex items-center justify-center gap-4">
|
|
6
|
+
<FilterIcon class="smooth-hover cursor-pointer text-white" />
|
|
7
|
+
<h4 class="text-base font-semibold text-light-300">Filter creator</h4>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="unified-load-group flex items-center h-9">
|
|
10
|
+
<input
|
|
11
|
+
class="relative flex h-full w-40 items-center bg-transparent px-2 text-sm text-white"
|
|
12
|
+
placeholder="Event ID"
|
|
13
|
+
v-model="eventId" />
|
|
14
|
+
<button
|
|
15
|
+
class="relative flex h-full items-center px-3 text-sm font-medium text-white bg-transparent"
|
|
16
|
+
@click="updateShownVenue">
|
|
17
|
+
Load
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="mb-2 overflow-hidden rounded border border-dark-650 bg-dark-400 p-3 shadow-sm">
|
|
23
|
+
<div class="h-full w-full">
|
|
7
24
|
<!-- Main -->
|
|
8
|
-
<div class="grid grid-cols-1
|
|
25
|
+
<div class="grid h-full w-full grid-cols-1 gap-3 lg:grid-cols-5 lg:gap-4">
|
|
9
26
|
<!-- Map -->
|
|
10
|
-
<div
|
|
11
|
-
|
|
12
|
-
<div class="
|
|
13
|
-
<
|
|
14
|
-
<div class="flex items-center">
|
|
15
|
-
<input
|
|
16
|
-
class="h-6 mt-1 text-sm p-2 bg-dark-500 w-40 flex items-center rounded-l relative border-2 border-dark-550"
|
|
17
|
-
placeholder="Event"
|
|
18
|
-
v-model="eventId"
|
|
19
|
-
/>
|
|
20
|
-
<button
|
|
21
|
-
class="h-6 mt-1 text-sm p-2 bg-dark-550 flex items-center rounded-r relative font-bold border-2 border-dark-550 smooth-hover"
|
|
22
|
-
@click="updateShownVenue"
|
|
23
|
-
>
|
|
24
|
-
Load
|
|
25
|
-
</button>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
<div v-if="svg" class="flex items-center">
|
|
29
|
-
<div class="flex items-center justify-between w-20 px-2 text-white font-black text-xl">
|
|
27
|
+
<div
|
|
28
|
+
class="relative col-span-1 flex h-full w-full flex-col rounded-lg lg:col-span-3 lg:max-w-none lg:overflow-hidden">
|
|
29
|
+
<div v-if="svg" class="mb-1 flex items-center">
|
|
30
|
+
<div class="flex w-20 items-center justify-between px-2 text-sm font-black text-white">
|
|
30
31
|
<span class="cursor-pointer" @click="handleZoom(true)">+</span>
|
|
31
32
|
<span class="cursor-pointer" @click="handleZoom(false)">-</span>
|
|
32
|
-
<ReloadIcon class="cursor-pointer" @click="handleZoom('r')" />
|
|
33
|
-
|
|
34
|
-
<!-- <h3 class="ml-10 text-sm text-white">Auto</h3>
|
|
35
|
-
<Switch class="scale-75 slider-dim" v-model="renderSeats" /> -->
|
|
33
|
+
<ReloadIcon class="h-4 w-4 cursor-pointer" @click="handleZoom('r')" />
|
|
36
34
|
</div>
|
|
37
35
|
</div>
|
|
38
|
-
<div class="overflow-hidden
|
|
36
|
+
<div class="selecto-wrapper flex-1 overflow-hidden">
|
|
39
37
|
<div
|
|
40
38
|
v-if="svg"
|
|
41
|
-
class="
|
|
42
|
-
>
|
|
43
|
-
<!-- <drag-select v-model="selection"> -->
|
|
39
|
+
class="hidden-scrollbars svg-container h-full overflow-auto rounded border-2 border-dark-550 p-2 shadow">
|
|
44
40
|
<div class="svg-wrapper" id="svg-wrapper" v-html="svg"></div>
|
|
45
|
-
|
|
41
|
+
</div>
|
|
42
|
+
<div
|
|
43
|
+
v-else
|
|
44
|
+
class="svg-container flex h-full items-center justify-center rounded border-2 border-dark-550 p-2 shadow">
|
|
45
|
+
<div class="text-center">
|
|
46
|
+
<svg
|
|
47
|
+
class="mx-auto mb-3 h-12 w-12 opacity-50"
|
|
48
|
+
viewBox="0 0 19 19"
|
|
49
|
+
fill="none"
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
51
|
+
<path
|
|
52
|
+
d="M2.37499 5.54165V2.37498L5.54166 3.95831L2.37499 5.54165ZM14.25 5.54165V2.37498L17.4167 3.95831L14.25 5.54165ZM8.70833 4.74998V1.58331L11.875 3.16665L8.70833 4.74998ZM8.70833 17.4166C7.70555 17.3903 6.77218 17.3079 5.9082 17.1696C5.0437 17.0308 4.29162 16.8559 3.65195 16.6448C3.01176 16.4337 2.50694 16.1896 2.13749 15.9125C1.76805 15.6354 1.58333 15.3451 1.58333 15.0416V7.91665C1.58333 7.58678 1.79127 7.27988 2.20716 6.99594C2.62252 6.71252 3.18645 6.46183 3.89895 6.24385C4.61145 6.02641 5.4493 5.85488 6.4125 5.72927C7.37569 5.60419 8.40486 5.54165 9.49999 5.54165C10.5951 5.54165 11.6243 5.60419 12.5875 5.72927C13.5507 5.85488 14.3885 6.02641 15.101 6.24385C15.8135 6.46183 16.3775 6.71252 16.7928 6.99594C17.2087 7.27988 17.4167 7.58678 17.4167 7.91665V15.0416C17.4167 15.3451 17.2319 15.6354 16.8625 15.9125C16.493 16.1896 15.9885 16.4337 15.3488 16.6448C14.7086 16.8559 13.9565 17.0308 13.0926 17.1696C12.2281 17.3079 11.2944 17.3903 10.2917 17.4166V14.25H8.70833V17.4166ZM9.49999 8.70831C10.7799 8.70831 11.885 8.63231 12.8155 8.48031C13.7454 8.32884 14.4875 8.15415 15.0417 7.95623C15.0417 7.89026 14.5403 7.73509 13.5375 7.49073C12.5347 7.2469 11.1889 7.12498 9.49999 7.12498C7.81111 7.12498 6.46527 7.2469 5.46249 7.49073C4.45972 7.73509 3.95833 7.89026 3.95833 7.95623C4.51249 8.15415 5.25481 8.32884 6.18529 8.48031C7.11523 8.63231 8.22013 8.70831 9.49999 8.70831ZM7.12499 15.7146V12.6666H11.875V15.7146C12.9305 15.609 13.7948 15.4538 14.4677 15.2491C15.1406 15.0448 15.5958 14.8635 15.8333 14.7052V9.34165C15.1076 9.63192 14.1972 9.86283 13.1021 10.0344C12.0069 10.2059 10.8062 10.2916 9.49999 10.2916C8.19374 10.2916 6.99305 10.2059 5.89791 10.0344C4.80277 9.86283 3.89236 9.63192 3.16666 9.34165V14.7052C3.40416 14.8635 3.85937 15.0448 4.53229 15.2491C5.2052 15.4538 6.06944 15.609 7.12499 15.7146Z"
|
|
53
|
+
fill="#F5F5F5" />
|
|
54
|
+
</svg>
|
|
55
|
+
<p class="text-sm text-light-400">No Map</p>
|
|
56
|
+
<p class="text-xs text-light-500">
|
|
57
|
+
Enter an event ID and click "Load" to display the venue map
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
46
60
|
</div>
|
|
47
61
|
</div>
|
|
48
62
|
</div>
|
|
49
|
-
<div class="col-span-
|
|
50
|
-
<div class="
|
|
51
|
-
<div class="
|
|
63
|
+
<div class="col-span-1 flex h-full w-full flex-col lg:col-span-2">
|
|
64
|
+
<div class="mb-2 flex items-center justify-between text-white">
|
|
65
|
+
<div class="flex items-center gap-2 rounded" v-if="hasLoaded">
|
|
52
66
|
<PriceSortToggle
|
|
53
67
|
:current="filterBuilder.globalFilter.priceSort"
|
|
54
68
|
class="smooth-hover"
|
|
55
|
-
@change="(e) => filterBuilder.updateGlobalFilter({ priceSort: e })"
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
@change="(e) => filterBuilder.updateGlobalFilter({ priceSort: e })" />
|
|
70
|
+
</div>
|
|
71
|
+
<div class="flex items-center gap-1" v-if="hasLoaded">
|
|
72
|
+
<SavingsIcon class="h-4 w-4 text-light-400" />
|
|
73
|
+
<label class="text-sm text-light-400">Max Price:</label>
|
|
58
74
|
<input
|
|
59
75
|
type="number"
|
|
60
76
|
:value="filterBuilder.globalFilter.maxPrice"
|
|
@@ -64,72 +80,87 @@
|
|
|
64
80
|
maxPrice: parseInt(e.target.value || 0)
|
|
65
81
|
})
|
|
66
82
|
"
|
|
67
|
-
class="w-14
|
|
68
|
-
placeholder="max"
|
|
69
|
-
/>
|
|
83
|
+
class="h-8 w-14 rounded border-2 border-dark-550 bg-dark-500 px-1 pl-2"
|
|
84
|
+
placeholder="max" />
|
|
70
85
|
</div>
|
|
71
86
|
</div>
|
|
72
|
-
<Table class="border-2 border-dark-550 shadow-xl">
|
|
73
|
-
<Header>
|
|
74
|
-
<div class="flex items-center
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class="w-14 smooth-hover"
|
|
79
|
-
:options="['All', 'WL', 'BL']"
|
|
80
|
-
@change="(e) => (shownFilters = e)"
|
|
81
|
-
/>
|
|
82
|
-
<div
|
|
83
|
-
class="flex gap-1 h-8 items-center justify-self-end border-2 border-dark-550 p-2 rounded smooth-hover"
|
|
84
|
-
@click="saveFilter"
|
|
85
|
-
>
|
|
86
|
-
<EditIcon />
|
|
87
|
-
<button class="lg:block hidden">Save</button>
|
|
87
|
+
<Table class="flex flex-1 flex-col border-2 border-dark-550 shadow-xl">
|
|
88
|
+
<Header class="flex-shrink-0">
|
|
89
|
+
<div class="flex w-full items-center justify-between gap-2 font-bold">
|
|
90
|
+
<div class="flex items-center gap-2">
|
|
91
|
+
<span class="text-base">Filters</span>
|
|
92
|
+
<span class="text-base text-light-300">{{ filterBuilder.filters.length }}</span>
|
|
88
93
|
</div>
|
|
89
|
-
<div
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<button class="
|
|
94
|
+
<div class="flex items-center gap-2">
|
|
95
|
+
<PriceSortToggle
|
|
96
|
+
class="smooth-hover w-14"
|
|
97
|
+
:options="['All', 'WL', 'BL']"
|
|
98
|
+
@change="(e) => (shownFilters = e)" />
|
|
99
|
+
<button class="header-btn save-btn" @click="saveFilter">
|
|
100
|
+
<EditIcon class="h-4 w-4" />
|
|
101
|
+
<span class="hidden lg:block">Save</span>
|
|
102
|
+
</button>
|
|
103
|
+
<button class="header-btn clear-btn" @click="filterBuilder.reset(false)">
|
|
104
|
+
<TrashIcon class="h-4 w-4" />
|
|
105
|
+
<span class="hidden lg:block">Clear</span>
|
|
106
|
+
</button>
|
|
95
107
|
</div>
|
|
96
108
|
</div>
|
|
97
109
|
</Header>
|
|
98
|
-
<div class="
|
|
99
|
-
<div v-if="filterBuilder.filters.length">
|
|
110
|
+
<div class="hidden-scrollbars flex-1 overflow-auto bg-dark-400">
|
|
111
|
+
<div v-if="filterBuilder.filters.length" class="filters-container">
|
|
100
112
|
<draggable
|
|
101
113
|
:list="filterBuilder.filters"
|
|
102
114
|
handle=".handle"
|
|
103
115
|
item-key="id"
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
">
|
|
106
128
|
<template #item="{ element: f, index: i }">
|
|
107
129
|
<Filter
|
|
108
130
|
v-if="doesFilterShow(f)"
|
|
109
131
|
:filter="f"
|
|
110
132
|
:index="i"
|
|
111
133
|
:filterBuilder="filterBuilder"
|
|
112
|
-
|
|
134
|
+
class="compact-filter" />
|
|
113
135
|
</template>
|
|
114
136
|
</draggable>
|
|
115
137
|
</div>
|
|
116
|
-
<
|
|
138
|
+
<div
|
|
139
|
+
v-else
|
|
140
|
+
class="empty-state flex flex-col items-center justify-center py-8 text-center">
|
|
141
|
+
<FilterIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
|
|
142
|
+
<p class="text-sm text-light-400">No filters yet</p>
|
|
143
|
+
<p class="mt-1 text-xs text-light-500">Click on the map to create filters</p>
|
|
144
|
+
</div>
|
|
117
145
|
</div>
|
|
118
146
|
</Table>
|
|
119
|
-
<div class="flex items-center justify-between
|
|
147
|
+
<div class="mb-2 mt-1 flex flex-shrink-0 items-center justify-between gap-2 text-white md:mb-0">
|
|
120
148
|
<button
|
|
121
|
-
@click="
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
@click="addWildcardFilter"
|
|
150
|
+
:disabled="hasWildcardFilter"
|
|
151
|
+
:class="[
|
|
152
|
+
'h-7 overflow-hidden rounded border-2 border-dark-550 bg-dark-500 px-2 text-xs shadow transition-all duration-200',
|
|
153
|
+
hasWildcardFilter
|
|
154
|
+
? 'text-gray cursor-not-allowed opacity-50'
|
|
155
|
+
: 'text-gray smooth-hover hover:border-light-300 hover:bg-dark-400'
|
|
156
|
+
]"
|
|
157
|
+
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'">
|
|
126
158
|
* Wildcard
|
|
127
159
|
</button>
|
|
128
160
|
<button
|
|
129
161
|
@click="ui.toggleModal('preview-filter')"
|
|
130
|
-
class="
|
|
131
|
-
|
|
132
|
-
<CameraIcon />
|
|
162
|
+
class="text-gray smooth-hover flex h-7 items-center justify-between gap-1 overflow-hidden rounded border-2 border-dark-550 bg-dark-500 px-2 text-xs shadow">
|
|
163
|
+
<CameraIcon class="h-3 w-3" />
|
|
133
164
|
JSON
|
|
134
165
|
</button>
|
|
135
166
|
</div>
|
|
@@ -137,7 +168,7 @@
|
|
|
137
168
|
</div>
|
|
138
169
|
</div>
|
|
139
170
|
|
|
140
|
-
<transition-group name="fade"
|
|
171
|
+
<transition-group name="fade">
|
|
141
172
|
<FilterPreview v-if="activeModal === 'preview-filter'" :filter="filterBuilder" />
|
|
142
173
|
</transition-group>
|
|
143
174
|
</div>
|
|
@@ -146,20 +177,14 @@
|
|
|
146
177
|
|
|
147
178
|
<script setup>
|
|
148
179
|
import draggable from "vuedraggable";
|
|
149
|
-
import { ref, nextTick, watch, computed } from "vue";
|
|
180
|
+
import { ref, nextTick, watch, computed, onMounted, onUnmounted } from "vue";
|
|
181
|
+
import { onBeforeRouteLeave } from "vue-router";
|
|
150
182
|
import { Table, Header } from "@/components/Table";
|
|
151
183
|
import Filter from "@/components/Filter/Filter.vue";
|
|
152
|
-
import
|
|
184
|
+
import { FilterIcon } from "@/components/icons";
|
|
185
|
+
import { DEBUG } from "@/utils/debug";
|
|
153
186
|
|
|
154
|
-
|
|
155
|
-
var RendererFactory;
|
|
156
|
-
if (DEBUG) {
|
|
157
|
-
RendererFactory = class {
|
|
158
|
-
constructor() {}
|
|
159
|
-
};
|
|
160
|
-
} else RendererFactory = import("@necrolab/tm-renderer");
|
|
161
|
-
|
|
162
|
-
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon } from "@/components/icons";
|
|
187
|
+
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon, StadiumIcon, SavingsIcon } from "@/components/icons";
|
|
163
188
|
import { sendSaveFilter } from "@/stores/requests";
|
|
164
189
|
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
165
190
|
import { useUIStore } from "@/stores/ui";
|
|
@@ -171,141 +196,14 @@ const activeModal = computed(() => ui.activeModal);
|
|
|
171
196
|
const ui = useUIStore();
|
|
172
197
|
const hasLoaded = ref(false);
|
|
173
198
|
|
|
174
|
-
// let isShiftPressed = false;
|
|
175
|
-
// window.onkeyup = (e) => {
|
|
176
|
-
// if (e.keyCode === 16) isShiftPressed = false;
|
|
177
|
-
// };
|
|
178
|
-
// window.onkeydown = (e) => {
|
|
179
|
-
// if (e.keyCode === 16) isShiftPressed = true;
|
|
180
|
-
// };
|
|
181
|
-
|
|
182
|
-
// const doDragSelect = () => {
|
|
183
|
-
// // https://dragselect.com/docs/API/Settings
|
|
184
|
-
// const ds = new DragSelect({
|
|
185
|
-
// selectables: [
|
|
186
|
-
// ...document.querySelectorAll("path[row]"),
|
|
187
|
-
// ...document.querySelectorAll("path[generaladmission]")
|
|
188
|
-
// ],
|
|
189
|
-
// area: document.getElementById("svg-wrapper"),
|
|
190
|
-
// selectionThreshold: 0.1,
|
|
191
|
-
// multiSelectKeys: []
|
|
192
|
-
// });
|
|
193
|
-
|
|
194
|
-
// ds.subscribe("DS:start:pre", () => {
|
|
195
|
-
// // ui.logger.Info("DS:start:pre - shift pressed:", isShiftPressed);
|
|
196
|
-
// if (!isShiftPressed) {
|
|
197
|
-
// ds.stop(false, false);
|
|
198
|
-
// ds.start();
|
|
199
|
-
// } else {
|
|
200
|
-
// panzoomInstance.pause();
|
|
201
|
-
// }
|
|
202
|
-
// });
|
|
203
|
-
|
|
204
|
-
// const parseSelected = (items) => {
|
|
205
|
-
// const parsed = items
|
|
206
|
-
// .map((e) => {
|
|
207
|
-
// return {
|
|
208
|
-
// section: e.attributes.sectionname?.nodeValue || e.attributes.name?.nodeValue,
|
|
209
|
-
// row: e.attributes.row?.nodeValue,
|
|
210
|
-
// GA: e.attributes.generaladmission?.nodeValue,
|
|
211
|
-
// name: e.attributes.name?.nodeValue
|
|
212
|
-
// };
|
|
213
|
-
// })
|
|
214
|
-
// .filter((e) => !filterBuilder.value.isUnselectable(e.section, e.row));
|
|
215
|
-
|
|
216
|
-
// const sectionMapping = {};
|
|
217
|
-
// parsed
|
|
218
|
-
// .filter((p) => !p.GA)
|
|
219
|
-
// .forEach((p) => {
|
|
220
|
-
// if (!sectionMapping[p.section]) sectionMapping[p.section] = [];
|
|
221
|
-
// sectionMapping[p.section].push(p);
|
|
222
|
-
// });
|
|
223
|
-
// parsed
|
|
224
|
-
// .filter((p) => p.GA)
|
|
225
|
-
// .forEach((p) => {
|
|
226
|
-
// sectionMapping[p.section] = {
|
|
227
|
-
// GA: true,
|
|
228
|
-
// section: p.section,
|
|
229
|
-
// name: p.name
|
|
230
|
-
// };
|
|
231
|
-
// });
|
|
232
|
-
// return sectionMapping;
|
|
233
|
-
// };
|
|
234
|
-
|
|
235
|
-
// ds.subscribe("DS:update:pre", (e) => {
|
|
236
|
-
// // ui.logger.Info("DS:update:pre - shift pressed:", isShiftPressed, e.items);
|
|
237
|
-
// filterBuilder.value.isDragging = true;
|
|
238
|
-
// if (!isShiftPressed) return;
|
|
239
|
-
// const sectionMapping = parseSelected(e.items);
|
|
240
|
-
// filterBuilder.value.clearHighlight();
|
|
241
|
-
// Object.entries(sectionMapping).forEach(([section, rows]) => {
|
|
242
|
-
// if (rows.GA) {
|
|
243
|
-
// const isSelected = filterBuilder.value.filters.find(
|
|
244
|
-
// (f) => filterBuilder.value.isForCurrentEvent(f) && f.section === section
|
|
245
|
-
// );
|
|
246
|
-
// if (isSelected) return;
|
|
247
|
-
// filterBuilder.value.highlight({ section: section }, true);
|
|
248
|
-
// } else
|
|
249
|
-
// rows.forEach((row) => {
|
|
250
|
-
// const isSelected = filterBuilder.value.filters.find(
|
|
251
|
-
// (f) =>
|
|
252
|
-
// (f.section === section && f.rows?.includes(row.row)) ||
|
|
253
|
-
// (!f.rows && f.section === section && filterBuilder.value.isForCurrentEvent(f))
|
|
254
|
-
// );
|
|
255
|
-
// if (isSelected) return;
|
|
256
|
-
|
|
257
|
-
// filterBuilder.value.highlight({ section, row: row.row });
|
|
258
|
-
// });
|
|
259
|
-
// });
|
|
260
|
-
// });
|
|
261
|
-
|
|
262
|
-
// ds.subscribe("DS:end", (e) => {
|
|
263
|
-
// filterBuilder.value.isDragging = false;
|
|
264
|
-
// panzoomInstance.resume();
|
|
265
|
-
// filterBuilder.value.clearHighlight();
|
|
266
|
-
// [...document.querySelectorAll("path")].map((f) => (f.style = ""));
|
|
267
|
-
// if (!isShiftPressed) return;
|
|
268
|
-
// const sectionMapping = parseSelected(e.items);
|
|
269
|
-
// const secNameMapping = filterBuilder.value.getSectionNameMapping();
|
|
270
|
-
|
|
271
|
-
// Object.entries(sectionMapping).forEach(([section, rows]) => {
|
|
272
|
-
// if (rows.GA) {
|
|
273
|
-
// const isSelected = filterBuilder.value.filters.find(
|
|
274
|
-
// (f) => filterBuilder.value.isForCurrentEvent(f) && f.section === secNameMapping[section]
|
|
275
|
-
// );
|
|
276
|
-
// if (isSelected) return;
|
|
277
|
-
// filterBuilder.value.addFilter({
|
|
278
|
-
// event: filterBuilder.value.currentEventId,
|
|
279
|
-
// section: secNameMapping[section]
|
|
280
|
-
// });
|
|
281
|
-
// } else {
|
|
282
|
-
// const unselectedRows = [];
|
|
283
|
-
// rows.forEach((row) => {
|
|
284
|
-
// const isSelected = filterBuilder.value.filters.find(
|
|
285
|
-
// (f) =>
|
|
286
|
-
// (f.section === section && f.rows?.includes(row.row)) ||
|
|
287
|
-
// (!f.rows && f.section === section && filterBuilder.value.isForCurrentEvent(f))
|
|
288
|
-
// );
|
|
289
|
-
// if (isSelected) return;
|
|
290
|
-
// unselectedRows.push(row.row);
|
|
291
|
-
// });
|
|
292
|
-
// if (unselectedRows.length === 0) return;
|
|
293
|
-
// filterBuilder.value.addFilter({
|
|
294
|
-
// event: filterBuilder.value.currentEventId,
|
|
295
|
-
// section,
|
|
296
|
-
// rows: unselectedRows
|
|
297
|
-
// });
|
|
298
|
-
// }
|
|
299
|
-
// });
|
|
300
|
-
// isShiftPressed = false;
|
|
301
|
-
// });
|
|
302
|
-
// };
|
|
303
|
-
|
|
304
199
|
const panzoomOptions = {
|
|
305
|
-
maxZoom: 8, // max zoom-in
|
|
200
|
+
maxZoom: 8, // max zoom-in factor
|
|
306
201
|
minZoom: 0.8, // max zoom-out factor
|
|
307
202
|
panOnlyWhenZoomed: true,
|
|
308
|
-
bounds: true
|
|
203
|
+
bounds: true,
|
|
204
|
+
boundsPadding: 0.1, // Add padding to ensure it stays within bounds
|
|
205
|
+
transformOrigin: { x: 0.5, y: 0.5 }, // Center the zoom
|
|
206
|
+
contain: true // Force the image to stay within the parent container
|
|
309
207
|
};
|
|
310
208
|
let panzoomInstance;
|
|
311
209
|
|
|
@@ -317,6 +215,125 @@ let renderer;
|
|
|
317
215
|
const shownFilters = ref("All");
|
|
318
216
|
const filterBuilder = ref(new FilterBuilder());
|
|
319
217
|
|
|
218
|
+
const hasWildcardFilter = computed(() => {
|
|
219
|
+
return filterBuilder.value.filters.some((filter) => filter.buyAny && filterBuilder.value.isForCurrentEvent(filter));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const addWildcardFilter = () => {
|
|
223
|
+
if (!hasWildcardFilter.value) {
|
|
224
|
+
// Close any expanded filter first
|
|
225
|
+
filterBuilder.value.setExpandedFilter(null);
|
|
226
|
+
filterBuilder.value.addFilter({ buyAny: true, eventId: filterBuilder.value.currentEventId });
|
|
227
|
+
filterBuilder.value.updateCss();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Initialize RendererFactory
|
|
232
|
+
let RendererFactory = import("@necrolab/tm-renderer");
|
|
233
|
+
|
|
234
|
+
// Real-time CSS injection system
|
|
235
|
+
let styleElement = null;
|
|
236
|
+
const cssUpdateTrigger = ref(0);
|
|
237
|
+
const STYLE_ELEMENT_ID = "filter-builder-styles";
|
|
238
|
+
|
|
239
|
+
const injectStyles = () => {
|
|
240
|
+
// Only inject styles if we have an SVG to style
|
|
241
|
+
const svgWrapper = document.getElementById("svg-wrapper");
|
|
242
|
+
if (!svgWrapper || !svg.value) return;
|
|
243
|
+
|
|
244
|
+
if (!styleElement) {
|
|
245
|
+
// Remove any existing style element first
|
|
246
|
+
const existingStyle = document.getElementById(STYLE_ELEMENT_ID);
|
|
247
|
+
if (existingStyle) {
|
|
248
|
+
existingStyle.remove();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
styleElement = document.createElement("style");
|
|
252
|
+
styleElement.id = STYLE_ELEMENT_ID;
|
|
253
|
+
document.head.appendChild(styleElement);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const combinedCSS = filterBuilder.value.cssClasses + filterBuilder.value.temporaryCSS;
|
|
257
|
+
if (styleElement.textContent !== combinedCSS) {
|
|
258
|
+
styleElement.textContent = combinedCSS;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Setup real-time CSS updates
|
|
263
|
+
const setupCSSUpdates = () => {
|
|
264
|
+
// Override updateCss method to trigger immediate updates
|
|
265
|
+
const originalUpdateCss = filterBuilder.value.updateCss.bind(filterBuilder.value);
|
|
266
|
+
filterBuilder.value.updateCss = () => {
|
|
267
|
+
originalUpdateCss();
|
|
268
|
+
nextTick(() => {
|
|
269
|
+
injectStyles();
|
|
270
|
+
cssUpdateTrigger.value++;
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Override highlight method for immediate temporary CSS
|
|
275
|
+
const originalHighlight = filterBuilder.value.highlight.bind(filterBuilder.value);
|
|
276
|
+
filterBuilder.value.highlight = (...args) => {
|
|
277
|
+
originalHighlight(...args);
|
|
278
|
+
nextTick(() => {
|
|
279
|
+
injectStyles();
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Override clearHighlight method
|
|
284
|
+
const originalClearHighlight = filterBuilder.value.clearHighlight.bind(filterBuilder.value);
|
|
285
|
+
filterBuilder.value.clearHighlight = () => {
|
|
286
|
+
originalClearHighlight();
|
|
287
|
+
nextTick(() => {
|
|
288
|
+
injectStyles();
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Cleanup function
|
|
294
|
+
const cleanupStyles = () => {
|
|
295
|
+
// Remove by ID to ensure we get it even if reference is lost
|
|
296
|
+
const existingStyle = document.getElementById(STYLE_ELEMENT_ID);
|
|
297
|
+
if (existingStyle) {
|
|
298
|
+
existingStyle.remove();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (styleElement) {
|
|
302
|
+
styleElement.remove();
|
|
303
|
+
styleElement = null;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Basic cleanup monitoring
|
|
308
|
+
const debugCleanup = () => {
|
|
309
|
+
console.log("FilterBuilder cleanup completed");
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Clean up any leftover styles from previous instances
|
|
313
|
+
cleanupStyles();
|
|
314
|
+
|
|
315
|
+
// Initialize CSS system
|
|
316
|
+
setupCSSUpdates();
|
|
317
|
+
|
|
318
|
+
// Watch for reactive updates
|
|
319
|
+
watch(
|
|
320
|
+
cssUpdateTrigger,
|
|
321
|
+
() => {
|
|
322
|
+
injectStyles();
|
|
323
|
+
},
|
|
324
|
+
{ immediate: true }
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Cleanup on unmount
|
|
328
|
+
onUnmounted(() => {
|
|
329
|
+
cleanupStyles();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Also cleanup on route change using beforeRouteLeave
|
|
333
|
+
onBeforeRouteLeave(() => {
|
|
334
|
+
cleanupStyles();
|
|
335
|
+
});
|
|
336
|
+
|
|
320
337
|
const doesFilterShow = (filter) => {
|
|
321
338
|
if ((filter.event || filter.eventId) !== filterBuilder.value.currentEventId) return;
|
|
322
339
|
if (!["BL", "WL"].includes(shownFilters.value)) return true;
|
|
@@ -325,13 +342,12 @@ const doesFilterShow = (filter) => {
|
|
|
325
342
|
};
|
|
326
343
|
|
|
327
344
|
let rendererFactory;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
});
|
|
345
|
+
RendererFactory.then((r) => {
|
|
346
|
+
rendererFactory = new r.default();
|
|
347
|
+
rendererFactory.init();
|
|
348
|
+
}).catch((error) => {
|
|
349
|
+
console.error("Failed to initialize renderer:", error);
|
|
350
|
+
});
|
|
335
351
|
|
|
336
352
|
const updateShownVenue = async () => {
|
|
337
353
|
eventId.value = eventId.value.trim();
|
|
@@ -344,12 +360,16 @@ const updateShownVenue = async () => {
|
|
|
344
360
|
return;
|
|
345
361
|
}
|
|
346
362
|
|
|
347
|
-
renderer = rendererFactory.createRenderer(eventId.value,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
renderer.
|
|
363
|
+
renderer = rendererFactory.createRenderer(eventId.value, {
|
|
364
|
+
proxy: "",
|
|
365
|
+
country: country
|
|
366
|
+
});
|
|
367
|
+
filterBuilder.value.highlightedSeatColor = renderer.config.highlightedSeatColor;
|
|
368
|
+
renderer.setCustomConfig({
|
|
369
|
+
logFullError: true,
|
|
370
|
+
includeDetailedAttributes: true,
|
|
371
|
+
renderRowBlocks: true
|
|
372
|
+
});
|
|
353
373
|
|
|
354
374
|
try {
|
|
355
375
|
await renderer.render();
|
|
@@ -371,18 +391,45 @@ const updateShownVenue = async () => {
|
|
|
371
391
|
if (panzoomInstance?.dispose) panzoomInstance.dispose();
|
|
372
392
|
const elem = document.getElementById("svg-wrapper");
|
|
373
393
|
try {
|
|
374
|
-
|
|
394
|
+
// Ensure SVG has appropriate sizing before initializing panzoom
|
|
395
|
+
if (elem && elem.children[0]) {
|
|
396
|
+
const svg = elem.children[0];
|
|
397
|
+
const isDesktop = window.innerWidth >= 1024;
|
|
398
|
+
|
|
399
|
+
// Ensure SVG doesn't exceed container width
|
|
400
|
+
svg.style.maxWidth = "100%";
|
|
401
|
+
svg.style.height = "auto";
|
|
402
|
+
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
403
|
+
|
|
404
|
+
// Additional desktop constraints to prevent expansion
|
|
405
|
+
if (isDesktop) {
|
|
406
|
+
svg.style.maxHeight = "100%";
|
|
407
|
+
svg.style.width = "100%";
|
|
408
|
+
svg.style.objectFit = "contain";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Initialize panzoom with updated options
|
|
412
|
+
panzoomInstance = panzoom(svg, panzoomOptions);
|
|
413
|
+
|
|
414
|
+
// Reset to initial position
|
|
415
|
+
panzoomInstance.moveTo(0, 0);
|
|
416
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
417
|
+
}
|
|
375
418
|
} catch (e) {
|
|
376
419
|
ui.logger.Error("Couldnt use panzoom", e);
|
|
377
420
|
}
|
|
378
421
|
await nextTick();
|
|
379
422
|
await loadFilter();
|
|
380
423
|
filterBuilder.value.reload(eventId.value);
|
|
381
|
-
|
|
424
|
+
|
|
425
|
+
// Re-setup CSS system after reload
|
|
426
|
+
setupCSSUpdates();
|
|
427
|
+
filterBuilder.value.updateCss();
|
|
382
428
|
};
|
|
383
429
|
|
|
384
430
|
const loadFilter = async () => {
|
|
385
431
|
try {
|
|
432
|
+
if (!eventId.value) return;
|
|
386
433
|
const res = await fetch(`/api/filter/load?eventId=${eventId.value}`);
|
|
387
434
|
const data = await res.json();
|
|
388
435
|
if (eventId.value) ui.showSuccess("Loaded filter data");
|
|
@@ -410,16 +457,22 @@ const saveFilter = async () => {
|
|
|
410
457
|
const handleZoom = (zoomEvent) => {
|
|
411
458
|
// handle zoom reset
|
|
412
459
|
if (zoomEvent === "r") {
|
|
413
|
-
//
|
|
414
|
-
panzoomInstance.
|
|
415
|
-
|
|
460
|
+
// Reset both zoom and position
|
|
461
|
+
panzoomInstance.moveTo(0, 0);
|
|
462
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
416
463
|
return;
|
|
417
464
|
}
|
|
418
465
|
|
|
419
|
-
//
|
|
466
|
+
// Get current transform
|
|
420
467
|
const { scale } = panzoomInstance.getTransform();
|
|
421
|
-
|
|
422
|
-
|
|
468
|
+
|
|
469
|
+
// Calculate new scale based on zoom direction
|
|
470
|
+
const newScale = zoomEvent ? scale + scale / 3 : scale - scale / 3;
|
|
471
|
+
|
|
472
|
+
// Apply zoom with bounds checking
|
|
473
|
+
if (newScale >= panzoomOptions.minZoom && newScale <= panzoomOptions.maxZoom) {
|
|
474
|
+
panzoomInstance.smoothZoom(0, 0, newScale / scale);
|
|
475
|
+
}
|
|
423
476
|
};
|
|
424
477
|
|
|
425
478
|
watch(renderSeats, async () => {
|
|
@@ -432,15 +485,240 @@ watch(renderSeats, async () => {
|
|
|
432
485
|
})();
|
|
433
486
|
</script>
|
|
434
487
|
|
|
488
|
+
<style scoped>
|
|
489
|
+
.slider-dim {
|
|
490
|
+
width: 60px;
|
|
491
|
+
height: 30px;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.compact-filter {
|
|
495
|
+
font-size: 0.75rem;
|
|
496
|
+
padding: 0.25rem !important;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.compact-filter .handle {
|
|
500
|
+
transform: scale(0.8);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* Enhanced table and filter styling */
|
|
504
|
+
.filters-container {
|
|
505
|
+
@apply space-y-0;
|
|
506
|
+
padding: 4px;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/* Dragging states for better UX */
|
|
510
|
+
.sortable-ghost {
|
|
511
|
+
@apply opacity-30;
|
|
512
|
+
border: 1px solid oklch(0.28 0 0);
|
|
513
|
+
background-color: rgba(68, 69, 75, 0.1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.sortable-chosen .drag-handle {
|
|
517
|
+
border: 1px solid oklch(0.28 0 0);
|
|
518
|
+
background-color: rgba(68, 69, 75, 0.2);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.sortable-drag {
|
|
522
|
+
@apply z-50 rotate-1 scale-105 transform shadow-xl;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* Unified Event ID + Load button group */
|
|
526
|
+
.unified-load-group {
|
|
527
|
+
border: 2px solid oklch(0.2809 0 0);
|
|
528
|
+
border-radius: 0.5rem;
|
|
529
|
+
overflow: hidden;
|
|
530
|
+
background: oklch(0.2603 0 0);
|
|
531
|
+
transition: all 0.15s ease;
|
|
532
|
+
|
|
533
|
+
input, button {
|
|
534
|
+
border: none !important;
|
|
535
|
+
border-radius: 0 !important;
|
|
536
|
+
outline: none !important;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
input {
|
|
540
|
+
flex: 1;
|
|
541
|
+
|
|
542
|
+
&::placeholder {
|
|
543
|
+
color: oklch(0.50 0 0);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
button {
|
|
548
|
+
border-left: 1px solid oklch(0.2809 0 0) !important;
|
|
549
|
+
padding: 0 1rem;
|
|
550
|
+
font-weight: 500;
|
|
551
|
+
transition: all 0.15s ease;
|
|
552
|
+
|
|
553
|
+
&:hover {
|
|
554
|
+
background: oklch(0.72 0.15 145 / 0.1);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
&:hover {
|
|
559
|
+
border-color: oklch(0.30 0 0);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
&:focus-within {
|
|
563
|
+
border-color: oklch(0.72 0.15 145);
|
|
564
|
+
outline: 1px solid oklch(0.72 0.15 145);
|
|
565
|
+
outline-offset: 0;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* Header button styling */
|
|
570
|
+
.header-btn {
|
|
571
|
+
@apply flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium transition-all duration-150;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.save-btn {
|
|
575
|
+
background-color: oklch(0.72 0.15 145 / 0.15);
|
|
576
|
+
border-color: oklch(0.72 0.15 145 / 0.5);
|
|
577
|
+
color: oklch(0.90 0 0);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.save-btn:hover {
|
|
581
|
+
background-color: oklch(0.72 0.15 145 / 0.25);
|
|
582
|
+
border-color: oklch(0.72 0.15 145);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.clear-btn {
|
|
586
|
+
background-color: oklch(0.60 0.20 25 / 0.15);
|
|
587
|
+
border-color: oklch(0.60 0.20 25 / 0.5);
|
|
588
|
+
color: oklch(0.90 0 0);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.clear-btn:hover {
|
|
592
|
+
background-color: oklch(0.60 0.20 25 / 0.25);
|
|
593
|
+
border-color: oklch(0.60 0.20 25);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.filter-builder-container {
|
|
597
|
+
height: 90vh;
|
|
598
|
+
overflow: hidden;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* Component-scoped definition (not global utility) */
|
|
602
|
+
.card-dark {
|
|
603
|
+
height: calc(100vh - 175px);
|
|
604
|
+
min-height: 500px;
|
|
605
|
+
overflow: hidden;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
@media (max-width: 768px) {
|
|
609
|
+
.filter-builder-container {
|
|
610
|
+
overflow: auto;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.card-dark {
|
|
614
|
+
height: auto;
|
|
615
|
+
overflow: auto;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* Mobile portrait header spacing fixes */
|
|
620
|
+
@media (max-width: 640px) {
|
|
621
|
+
.filter-builder-container .flex.items-center.justify-center {
|
|
622
|
+
gap: 0.5rem;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* iPhone landscape and tablet portrait mode fixes */
|
|
627
|
+
@media (max-width: 1023px) and (max-height: 768px) {
|
|
628
|
+
.filter-builder-container {
|
|
629
|
+
height: auto;
|
|
630
|
+
min-height: 100vh;
|
|
631
|
+
overflow: auto;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.card-dark {
|
|
635
|
+
height: auto;
|
|
636
|
+
min-height: 500px;
|
|
637
|
+
overflow: auto;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/* Desktop: enforce grid column constraints */
|
|
642
|
+
@media (min-width: 1024px) {
|
|
643
|
+
.lg\\:grid-cols-5 > .lg\\:col-span-3 {
|
|
644
|
+
max-width: 60%; /* 3/5 of grid */
|
|
645
|
+
flex-shrink: 0;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.svg-container {
|
|
650
|
+
min-height: 300px;
|
|
651
|
+
height: 100%;
|
|
652
|
+
width: 100%;
|
|
653
|
+
position: relative;
|
|
654
|
+
overflow: auto;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* Desktop: constrain svg-container to prevent expansion */
|
|
658
|
+
@media (min-width: 1024px) {
|
|
659
|
+
.svg-container {
|
|
660
|
+
position: relative;
|
|
661
|
+
max-width: 100%;
|
|
662
|
+
max-height: 100%;
|
|
663
|
+
overflow: hidden;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
</style>
|
|
667
|
+
|
|
435
668
|
<style>
|
|
669
|
+
/* Global styles needed for dynamically injected SVG content and Filter.js CSS injection */
|
|
670
|
+
.svg-wrapper {
|
|
671
|
+
position: relative;
|
|
672
|
+
transform-origin: 0 0;
|
|
673
|
+
min-width: 100%;
|
|
674
|
+
min-height: 100%;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* Desktop constraints - prevent svg-wrapper from expanding beyond container */
|
|
678
|
+
@media (min-width: 1024px) {
|
|
679
|
+
.svg-wrapper {
|
|
680
|
+
position: absolute;
|
|
681
|
+
top: 0;
|
|
682
|
+
left: 0;
|
|
683
|
+
width: 100%;
|
|
684
|
+
height: 100%;
|
|
685
|
+
max-width: 100%;
|
|
686
|
+
max-height: 100%;
|
|
687
|
+
min-width: unset;
|
|
688
|
+
min-height: unset;
|
|
689
|
+
overflow: hidden;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
436
693
|
.svg-wrapper > svg {
|
|
694
|
+
width: 100%;
|
|
695
|
+
height: auto;
|
|
437
696
|
background: center center no-repeat;
|
|
438
|
-
overflow: hidden;
|
|
439
697
|
background-size: contain;
|
|
440
698
|
}
|
|
441
699
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
700
|
+
/* Desktop SVG constraints */
|
|
701
|
+
@media (min-width: 1024px) {
|
|
702
|
+
.svg-wrapper > svg {
|
|
703
|
+
max-width: 100%;
|
|
704
|
+
max-height: 100%;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* Ensure Filter.js generated CSS works properly */
|
|
709
|
+
.svg-wrapper path {
|
|
710
|
+
pointer-events: auto;
|
|
711
|
+
cursor: pointer;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.svg-wrapper path[generaladmission] {
|
|
715
|
+
pointer-events: auto;
|
|
716
|
+
cursor: pointer;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/* Hover effects for general admission seats */
|
|
720
|
+
.svg-wrapper path[generaladmission]:hover {
|
|
721
|
+
pointer-events: all;
|
|
722
|
+
cursor: pointer;
|
|
445
723
|
}
|
|
446
724
|
</style>
|