@necrolab/dashboard 0.5.21 → 0.5.22
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/package.json +1 -1
- package/src/components/Filter/Filter.vue +13 -9
- package/src/libs/Filter.js +14 -12
- package/src/views/FilterBuilder.vue +146 -31
package/package.json
CHANGED
|
@@ -292,15 +292,7 @@ props.filterBuilder.onUpdate(() => {
|
|
|
292
292
|
|
|
293
293
|
<style scoped>
|
|
294
294
|
.filter-card {
|
|
295
|
-
@apply bg-dark-500 border border-dark-625/30 relative mb-2 transition-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
.filter-card:hover {
|
|
299
|
-
transform: scale(1.03);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.filter-card:active {
|
|
303
|
-
transform: scale(0.98);
|
|
295
|
+
@apply bg-dark-500 border border-dark-625/30 relative mb-2 transition-colors duration-200;
|
|
304
296
|
}
|
|
305
297
|
|
|
306
298
|
.filter-card:hover:not(.expanded-filter) {
|
|
@@ -353,17 +345,29 @@ props.filterBuilder.onUpdate(() => {
|
|
|
353
345
|
|
|
354
346
|
.drag-btn {
|
|
355
347
|
@apply text-light-500;
|
|
348
|
+
transition: all 0.2s ease;
|
|
356
349
|
|
|
357
350
|
&:hover {
|
|
358
351
|
@apply bg-dark-300 border-dark-550 text-yellow-400 shadow-md;
|
|
352
|
+
transform: scale(1.1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
&:active {
|
|
356
|
+
transform: scale(0.95);
|
|
359
357
|
}
|
|
360
358
|
}
|
|
361
359
|
|
|
362
360
|
.delete-btn {
|
|
363
361
|
@apply bg-error-500/30 border-error-400/50 text-error-300;
|
|
362
|
+
transition: all 0.2s ease;
|
|
364
363
|
|
|
365
364
|
&:hover {
|
|
366
365
|
@apply bg-error-400/80 border-error-500 text-white shadow-md;
|
|
366
|
+
transform: scale(1.1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
&:active {
|
|
370
|
+
transform: scale(0.95);
|
|
367
371
|
}
|
|
368
372
|
}
|
|
369
373
|
|
package/src/libs/Filter.js
CHANGED
|
@@ -3,8 +3,8 @@ const log = (...args) => DEBUG && console.log("[filter]", ...args);
|
|
|
3
3
|
|
|
4
4
|
const colors = {
|
|
5
5
|
HIGHLIGHT: "#d3f8e2",
|
|
6
|
-
SELECTED: "#
|
|
7
|
-
SELECTED_EXPANDED: "#
|
|
6
|
+
SELECTED: "#7bc999",
|
|
7
|
+
SELECTED_EXPANDED: "#a0ddb5",
|
|
8
8
|
EXCLUDED: "#f694c1",
|
|
9
9
|
EXCLUDED_EXPANDED: "#f472b6",
|
|
10
10
|
UNSELECTABLE: "#311432"
|
|
@@ -363,14 +363,16 @@ export default class FilterBuilder {
|
|
|
363
363
|
this.filters.forEach((filter) => {
|
|
364
364
|
if (!this.isForCurrentEvent(filter)) return;
|
|
365
365
|
|
|
366
|
-
|
|
367
|
-
|
|
366
|
+
// Try to find the short name (attribute name) from the real name (stored in filter)
|
|
367
|
+
const shortName = Object.entries(gaSectionNameMapping).find(
|
|
368
|
+
([, v]) => v === filter.section
|
|
368
369
|
)?.[0];
|
|
369
370
|
|
|
370
|
-
if
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
371
|
+
// Check if this is a GA section by looking for paths with this name attribute
|
|
372
|
+
const isGA = shortName && document.querySelectorAll(`path[name="${shortName}"][generaladmission="true"]`)?.length > 0;
|
|
373
|
+
|
|
374
|
+
if (shortName && isGA) {
|
|
375
|
+
log(`GA section detected: ${filter.section} -> short name: ${shortName}`);
|
|
374
376
|
}
|
|
375
377
|
|
|
376
378
|
const type = this.getFilterType(filter);
|
|
@@ -386,11 +388,11 @@ export default class FilterBuilder {
|
|
|
386
388
|
case this.filterTypes.NORMAL:
|
|
387
389
|
// If it has no 'rows' property
|
|
388
390
|
if (!Array.isArray(filter.rows)) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
this.cssClasses += `.svg-wrapper path[name="${tryFindRealName}"] {fill: ${color} !important;}\n`;
|
|
391
|
+
if (isGA && shortName) {
|
|
392
|
+
// GA sections use 'name' attribute and 'fill' for styling
|
|
393
|
+
this.cssClasses += `.svg-wrapper path[name="${shortName}"][generaladmission="true"] {fill: ${color} !important;}\n`;
|
|
393
394
|
} else {
|
|
395
|
+
// Regular sections use 'section' attribute and 'stroke' for styling
|
|
394
396
|
this.cssClasses += `.svg-wrapper path[section="${filter.section}"] {stroke: ${color} !important;}\n`;
|
|
395
397
|
}
|
|
396
398
|
} else {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex w-full flex-col">
|
|
3
|
-
<div class="page-header">
|
|
3
|
+
<div class="page-header flex-wrap gap-3">
|
|
4
4
|
<div class="page-header-card flex-shrink-0">
|
|
5
5
|
<FilterIcon />
|
|
6
6
|
<h4>Filter creator</h4>
|
|
7
7
|
</div>
|
|
8
|
-
<div class="unified-search-group flex w-
|
|
8
|
+
<div class="unified-search-group flex w-full flex-1 items-center md:w-auto md:flex-initial md:min-w-64">
|
|
9
9
|
<input
|
|
10
|
-
class="h-10
|
|
10
|
+
class="flex-1 h-10 px-3 text-sm text-white placeholder-light-500"
|
|
11
11
|
placeholder="Event ID"
|
|
12
12
|
v-model="eventId"
|
|
13
13
|
aria-label="Event ID" />
|
|
14
14
|
<button
|
|
15
|
-
class="
|
|
15
|
+
class="hover:bg-dark-450 flex h-10 w-10 flex-shrink-0 items-center justify-center bg-dark-400 text-white"
|
|
16
|
+
style="transition: all 0.2s ease"
|
|
16
17
|
@click="updateShownVenue"
|
|
17
18
|
aria-label="Load venue">
|
|
18
19
|
<ReloadIcon class="icon-md" />
|
|
@@ -21,26 +22,29 @@
|
|
|
21
22
|
</div>
|
|
22
23
|
|
|
23
24
|
<div
|
|
24
|
-
class="mb-3 flex flex-1 flex-col overflow-hidden rounded border border-dark-650 bg-dark-400 p-
|
|
25
|
-
|
|
25
|
+
class="mb-3 flex flex-1 flex-col overflow-hidden rounded border border-dark-650 bg-dark-400 p-2 shadow-sm md:p-3 md:mb-4"
|
|
26
|
+
style="max-height: calc(100vh - 200px);">
|
|
27
|
+
<div class="flex h-full w-full flex-col gap-2 md:gap-3 lg:flex-row lg:gap-4">
|
|
26
28
|
<div
|
|
27
|
-
class="
|
|
28
|
-
|
|
29
|
+
class="relative flex w-full min-w-0 flex-col overflow-hidden rounded-lg flex-1 lg:w-3/5 svg-container"
|
|
30
|
+
:class="{ 'has-svg': svg }">
|
|
31
|
+
<div v-if="svg" class="flex-gap-2 mb-2 flex-shrink-0 items-center">
|
|
29
32
|
<button @click="handleZoom(true)" class="btn-icon-small" aria-label="Zoom in">+</button>
|
|
30
33
|
<button @click="handleZoom(false)" class="btn-icon-small" aria-label="Zoom out">-</button>
|
|
31
34
|
<button @click="handleZoom('r')" class="btn-icon-small" aria-label="Reset zoom">
|
|
32
35
|
<ReloadIcon class="icon-md text-white" />
|
|
33
36
|
</button>
|
|
34
37
|
</div>
|
|
35
|
-
<div class="selecto-wrapper flex-1 overflow-hidden">
|
|
38
|
+
<div class="selecto-wrapper flex-1 overflow-hidden" style="max-height: 100%;">
|
|
36
39
|
<div
|
|
37
40
|
v-if="svg"
|
|
38
|
-
class="hidden-scrollbars
|
|
41
|
+
class="hidden-scrollbars relative h-full w-full overflow-auto rounded border border-dark-550 bg-dark-500 p-1 md:p-2 shadow">
|
|
39
42
|
<div class="svg-wrapper" id="svg-wrapper" v-html="svg"></div>
|
|
40
43
|
</div>
|
|
41
44
|
<div
|
|
42
45
|
v-else
|
|
43
|
-
class="
|
|
46
|
+
class="relative flex h-full w-full items-center justify-center rounded border border-dark-550 bg-dark-500 p-2 shadow"
|
|
47
|
+
style="min-height: 200px;">
|
|
44
48
|
<div class="text-center">
|
|
45
49
|
<FilterIcon class="empty-state-icon mx-auto" />
|
|
46
50
|
<p class="text-sm text-light-400">No Map</p>
|
|
@@ -51,8 +55,8 @@
|
|
|
51
55
|
</div>
|
|
52
56
|
</div>
|
|
53
57
|
</div>
|
|
54
|
-
<div class="
|
|
55
|
-
<div class="mb-2 flex flex-shrink-0 flex-wrap items-center gap-2 text-white" v-if="hasLoaded">
|
|
58
|
+
<div class="flex w-full min-w-0 flex-col flex-1 lg:w-2/5 filters-container">
|
|
59
|
+
<div class="mb-2 flex flex-shrink-0 flex-wrap items-center gap-1.5 md:gap-2 text-white text-xs md:text-sm" v-if="hasLoaded">
|
|
56
60
|
<PriceSortToggle
|
|
57
61
|
:current="filterBuilder.globalFilter.priceSort"
|
|
58
62
|
class="smooth-hover h-8"
|
|
@@ -71,8 +75,9 @@
|
|
|
71
75
|
placeholder="999" />
|
|
72
76
|
</div>
|
|
73
77
|
<div
|
|
74
|
-
class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-dark-550 bg-dark-500 shadow-sm"
|
|
75
|
-
|
|
78
|
+
class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-dark-550 bg-dark-500 shadow-sm"
|
|
79
|
+
style="max-height: 100%;">
|
|
80
|
+
<div class="flex-shrink-0 border-b border-dark-550 bg-dark-300 px-2 md:px-4 py-2 md:py-3 text-xs text-white">
|
|
76
81
|
<div class="flex w-full items-center justify-between gap-2">
|
|
77
82
|
<div class="flex-gap-2 items-center">
|
|
78
83
|
<span class="text-sm font-medium text-white">Filters</span>
|
|
@@ -98,8 +103,8 @@
|
|
|
98
103
|
</div>
|
|
99
104
|
</div>
|
|
100
105
|
</div>
|
|
101
|
-
<div class="hidden-scrollbars flex-1 overflow-auto bg-dark-400">
|
|
102
|
-
<
|
|
106
|
+
<div class="hidden-scrollbars flex-1 overflow-y-auto overflow-x-hidden bg-dark-400" style="max-height: 100%; min-height: 150px;">
|
|
107
|
+
<Draggable
|
|
103
108
|
v-if="filterBuilder.filters.length"
|
|
104
109
|
v-model="draggableFilters"
|
|
105
110
|
handle=".handle"
|
|
@@ -117,7 +122,7 @@
|
|
|
117
122
|
:filterBuilder="filterBuilder"
|
|
118
123
|
class="!p-1 !text-xs" />
|
|
119
124
|
</template>
|
|
120
|
-
</
|
|
125
|
+
</Draggable>
|
|
121
126
|
<div v-else class="empty-state flex flex-col items-center justify-center py-8 text-center">
|
|
122
127
|
<FilterIcon class="empty-state-icon" />
|
|
123
128
|
<p class="text-sm text-light-400">No filters yet</p>
|
|
@@ -129,9 +134,10 @@
|
|
|
129
134
|
<button
|
|
130
135
|
@click="addWildcardFilter"
|
|
131
136
|
:disabled="hasWildcardFilter"
|
|
132
|
-
class="filter-
|
|
137
|
+
class="filter-action-btn"
|
|
133
138
|
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'">
|
|
134
|
-
|
|
139
|
+
<WildcardIcon class="h-3 w-3 flex-shrink-0" />
|
|
140
|
+
<span>* Wildcard</span>
|
|
135
141
|
</button>
|
|
136
142
|
<button @click="ui.toggleModal('preview-filter')" class="filter-action-btn">
|
|
137
143
|
<CameraIcon class="h-3 w-3" />
|
|
@@ -149,11 +155,11 @@
|
|
|
149
155
|
</template>
|
|
150
156
|
|
|
151
157
|
<script setup>
|
|
152
|
-
import
|
|
153
|
-
import { ref, computed, defineAsyncComponent, watch, nextTick } from "vue";
|
|
158
|
+
import Draggable from "vuedraggable";
|
|
159
|
+
import { ref, computed, defineAsyncComponent, watch, nextTick, onMounted } from "vue";
|
|
154
160
|
import { useFilterCSS } from "@/composables/useFilterCSS";
|
|
155
161
|
import Filter from "@/components/Filter/Filter.vue";
|
|
156
|
-
import { FilterIcon } from "@/components/icons";
|
|
162
|
+
import { FilterIcon, WildcardIcon } from "@/components/icons";
|
|
157
163
|
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon } from "@/components/icons";
|
|
158
164
|
import { sendSaveFilter } from "@/stores/requests";
|
|
159
165
|
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
@@ -190,6 +196,8 @@ const draggableFilters = computed({
|
|
|
190
196
|
get: () => filterBuilder.value.filters,
|
|
191
197
|
set: (value) => {
|
|
192
198
|
filterBuilder.value.filters = value;
|
|
199
|
+
filterBuilder.value.updateCss();
|
|
200
|
+
filterBuilder.value.updateHooks.forEach((fn) => fn());
|
|
193
201
|
}
|
|
194
202
|
});
|
|
195
203
|
|
|
@@ -262,9 +270,9 @@ const updateShownVenue = async () => {
|
|
|
262
270
|
renderer = rendererFactory.createRenderer(eventId.value, {
|
|
263
271
|
proxy: "",
|
|
264
272
|
country: country,
|
|
265
|
-
seatColor: "#
|
|
266
|
-
nonAvSeatColor: "#
|
|
267
|
-
highlightedSeatColor: "#
|
|
273
|
+
seatColor: "#6b9b82",
|
|
274
|
+
nonAvSeatColor: "#4b4b4b",
|
|
275
|
+
highlightedSeatColor: "#7bc999"
|
|
268
276
|
});
|
|
269
277
|
|
|
270
278
|
filterBuilder.value.highlightedSeatColor = renderer.config.highlightedSeatColor;
|
|
@@ -414,13 +422,120 @@ watch(renderSeats, async () => {
|
|
|
414
422
|
}
|
|
415
423
|
|
|
416
424
|
.svg-wrapper path[generaladmission] {
|
|
417
|
-
pointer-events: auto;
|
|
418
|
-
cursor: pointer;
|
|
425
|
+
pointer-events: auto !important;
|
|
426
|
+
cursor: pointer !important;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Hover effects */
|
|
430
|
+
.svg-wrapper path:hover {
|
|
431
|
+
opacity: 0.8;
|
|
419
432
|
}
|
|
420
433
|
|
|
421
|
-
/* Hover effects for general admission seats */
|
|
422
434
|
.svg-wrapper path[generaladmission]:hover {
|
|
423
|
-
|
|
424
|
-
|
|
435
|
+
opacity: 1;
|
|
436
|
+
fill: #7ee8fa !important;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Mobile responsiveness - bulletproof on all devices */
|
|
440
|
+
@media (max-width: 768px) {
|
|
441
|
+
.page-header {
|
|
442
|
+
flex-direction: column;
|
|
443
|
+
align-items: stretch !important;
|
|
444
|
+
gap: 0.75rem;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.unified-search-group {
|
|
448
|
+
width: 100% !important;
|
|
449
|
+
min-width: 100% !important;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
@media (max-width: 1024px) {
|
|
454
|
+
.flex.w-full.min-w-0.flex-col.flex-1 {
|
|
455
|
+
min-height: 350px !important;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@media (max-width: 768px) {
|
|
460
|
+
.page-header {
|
|
461
|
+
flex-direction: column !important;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
@media (max-width: 480px) {
|
|
466
|
+
.page-header {
|
|
467
|
+
padding-bottom: 0.75rem;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.unified-search-group input {
|
|
471
|
+
min-width: 0;
|
|
472
|
+
font-size: 13px;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.filter-action-btn {
|
|
476
|
+
font-size: 11px;
|
|
477
|
+
padding: 0.25rem 0.5rem;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.btn-icon-small {
|
|
481
|
+
width: 1.75rem;
|
|
482
|
+
height: 1.75rem;
|
|
483
|
+
font-size: 12px;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.flex.w-full.min-w-0.flex-col.flex-1 {
|
|
487
|
+
min-height: 400px !important;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* SVG and Filters container responsive sizing */
|
|
492
|
+
.svg-container {
|
|
493
|
+
min-height: 250px;
|
|
494
|
+
max-height: 50%;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.svg-container.has-svg {
|
|
498
|
+
min-height: 400px;
|
|
499
|
+
max-height: 55%;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.filters-container {
|
|
503
|
+
min-height: 350px;
|
|
504
|
+
max-height: 100%;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
@media (min-width: 1024px) {
|
|
508
|
+
.svg-container {
|
|
509
|
+
min-height: 400px;
|
|
510
|
+
max-height: 100%;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.svg-container.has-svg {
|
|
514
|
+
min-height: 400px;
|
|
515
|
+
max-height: 100%;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.filters-container {
|
|
519
|
+
min-height: 400px;
|
|
520
|
+
max-height: 100%;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* Ensure filters list always scrolls */
|
|
525
|
+
.hidden-scrollbars::-webkit-scrollbar {
|
|
526
|
+
width: 4px;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.hidden-scrollbars::-webkit-scrollbar-track {
|
|
530
|
+
background: transparent;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.hidden-scrollbars::-webkit-scrollbar-thumb {
|
|
534
|
+
background: oklch(0.35 0 0);
|
|
535
|
+
border-radius: 4px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.hidden-scrollbars::-webkit-scrollbar-thumb:hover {
|
|
539
|
+
background: oklch(0.45 0 0);
|
|
425
540
|
}
|
|
426
541
|
</style>
|