@necrolab/dashboard 0.5.14 → 0.5.16
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/backend/api.js +2 -3
- package/eslint.config.js +46 -0
- package/index.html +2 -1
- package/package.json +5 -2
- package/src/App.vue +140 -170
- package/src/assets/css/base/mixins.scss +72 -0
- package/src/assets/css/base/reset.scss +0 -2
- package/src/assets/css/base/scroll.scss +43 -36
- package/src/assets/css/base/typography.scss +9 -10
- package/src/assets/css/base/variables.scss +43 -0
- package/src/assets/css/components/accessibility.scss +37 -0
- package/src/assets/css/components/buttons.scss +58 -15
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +119 -0
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -19
- package/src/assets/css/components/tables.scss +5 -7
- package/src/assets/css/components/toasts.scss +7 -7
- package/src/assets/css/components/utilities.scss +220 -0
- package/src/assets/css/main.scss +72 -75
- package/src/components/Auth/LoginForm.vue +5 -84
- package/src/components/Editors/Account/Account.vue +8 -10
- package/src/components/Editors/Account/AccountCreator.vue +28 -59
- package/src/components/Editors/Account/AccountView.vue +38 -86
- package/src/components/Editors/Account/CreateAccount.vue +8 -50
- package/src/components/Editors/Profile/CreateProfile.vue +74 -131
- package/src/components/Editors/Profile/Profile.vue +15 -17
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +46 -96
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +62 -75
- package/src/components/Filter/FilterPreview.vue +161 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +61 -12
- package/src/components/Tasks/CheckStock.vue +7 -16
- package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
- package/src/components/Tasks/Controls/MobileControls.vue +5 -20
- package/src/components/Tasks/CreateTaskAXS.vue +20 -118
- package/src/components/Tasks/CreateTaskTM.vue +33 -189
- package/src/components/Tasks/EventDetailRow.vue +21 -0
- package/src/components/Tasks/MassEdit.vue +6 -16
- package/src/components/Tasks/QuickSettings.vue +140 -216
- package/src/components/Tasks/ScrapeVenue.vue +4 -13
- package/src/components/Tasks/Stats.vue +19 -38
- package/src/components/Tasks/Task.vue +65 -268
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +43 -63
- package/src/components/Tasks/Utilities.vue +10 -42
- package/src/components/Tasks/ViewTask.vue +23 -107
- package/src/components/icons/Close.vue +2 -8
- package/src/components/icons/Gear.vue +8 -8
- package/src/components/icons/Hash.vue +5 -0
- package/src/components/icons/Key.vue +2 -8
- package/src/components/icons/Pencil.vue +2 -8
- package/src/components/icons/Profile.vue +2 -8
- package/src/components/icons/Sell.vue +2 -8
- package/src/components/icons/Spinner.vue +4 -7
- package/src/components/icons/SquareCheck.vue +2 -8
- package/src/components/icons/SquareUncheck.vue +2 -8
- package/src/components/icons/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -1
- package/src/components/ui/ActionButtonGroup.vue +113 -52
- package/src/components/ui/BalanceIndicator.vue +60 -0
- package/src/components/ui/EmptyState.vue +24 -0
- package/src/components/ui/EnableDisableToggle.vue +23 -0
- package/src/components/ui/FormField.vue +48 -48
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +78 -37
- package/src/components/ui/Navbar.vue +60 -41
- package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
- package/src/components/ui/ReconnectIndicator.vue +111 -124
- package/src/components/ui/SectionCard.vue +6 -14
- package/src/components/ui/Splash.vue +2 -10
- package/src/components/ui/StatusBadge.vue +26 -28
- package/src/components/ui/TaskToggle.vue +54 -0
- package/src/components/ui/controls/CountryChooser.vue +27 -64
- package/src/components/ui/controls/EyeToggle.vue +1 -1
- package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
- package/src/components/ui/controls/atomic/Dropdown.vue +102 -95
- package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -94
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useColorMapping.js +15 -0
- package/src/composables/useCopyToClipboard.js +1 -1
- package/src/composables/useDateFormatting.js +21 -0
- package/src/composables/useDeviceDetection.js +14 -0
- package/src/composables/useDropdownPosition.js +5 -6
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +60 -0
- package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
- package/src/libs/utils/eventUrl.js +40 -0
- package/src/libs/utils/string.js +28 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +88 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -4
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +59 -36
- package/src/views/Accounts.vue +17 -31
- package/src/views/Console.vue +76 -176
- package/src/views/Editor.vue +217 -383
- package/src/views/FilterBuilder.vue +190 -373
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +12 -22
- package/src/views/Tasks.vue +51 -38
- package/tailwind.config.js +82 -71
- package/workbox-config.cjs +47 -5
- package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2416
- package/exit +0 -209
- package/run +0 -177
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
|
@@ -1,196 +1,176 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<
|
|
7
|
-
<h4 class="text-base font-semibold text-light-300">Filter creator</h4>
|
|
2
|
+
<div class="flex w-full flex-col">
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<div class="page-header-card flex-shrink-0">
|
|
5
|
+
<FilterIcon />
|
|
6
|
+
<h4>Filter creator</h4>
|
|
8
7
|
</div>
|
|
9
|
-
<div class="unified-
|
|
8
|
+
<div class="unified-search-group flex w-auto items-center">
|
|
10
9
|
<input
|
|
11
|
-
class="
|
|
10
|
+
class="h-10 w-32 flex-1 px-3 text-sm text-white placeholder-light-500 sm:w-48 md:w-64"
|
|
12
11
|
placeholder="Event ID"
|
|
13
|
-
v-model="eventId"
|
|
12
|
+
v-model="eventId"
|
|
13
|
+
aria-label="Event ID" />
|
|
14
14
|
<button
|
|
15
|
-
class="
|
|
16
|
-
@click="updateShownVenue"
|
|
17
|
-
Load
|
|
15
|
+
class="flex h-10 w-9 flex-shrink-0 items-center justify-center bg-dark-400 text-white transition-all duration-150 hover:bg-dark-450"
|
|
16
|
+
@click="updateShownVenue"
|
|
17
|
+
aria-label="Load venue">
|
|
18
|
+
<ReloadIcon class="h-4 w-4" />
|
|
18
19
|
</button>
|
|
19
20
|
</div>
|
|
20
21
|
</div>
|
|
21
22
|
|
|
22
|
-
<div class="mb-
|
|
23
|
-
<div class="h-full w-full">
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
<div class="mb-3 flex flex-1 flex-col overflow-hidden rounded border border-dark-650 bg-dark-400 p-3 shadow-sm md:mb-4">
|
|
24
|
+
<div class="flex h-full w-full flex-col gap-3 lg:flex-row lg:gap-4">
|
|
25
|
+
<div class="relative flex min-h-75 min-w-0 w-full flex-col overflow-hidden rounded-lg lg:min-h-125 lg:w-3/5">
|
|
26
|
+
<div v-if="svg" class="mb-2 flex items-center gap-2">
|
|
27
|
+
<button @click="handleZoom(true)" class="btn-icon-small" aria-label="Zoom in">
|
|
28
|
+
+
|
|
29
|
+
</button>
|
|
30
|
+
<button @click="handleZoom(false)" class="btn-icon-small" aria-label="Zoom out">
|
|
31
|
+
-
|
|
32
|
+
</button>
|
|
33
|
+
<button @click="handleZoom('r')" class="btn-icon-small" aria-label="Reset zoom">
|
|
34
|
+
<ReloadIcon class="h-4 w-4 text-white" />
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="selecto-wrapper flex-1 overflow-hidden">
|
|
38
|
+
<div
|
|
39
|
+
v-if="svg"
|
|
40
|
+
class="hidden-scrollbars relative h-full min-h-87.5 w-full overflow-auto rounded border border-dark-550 bg-dark-500 p-2 shadow">
|
|
41
|
+
<div class="svg-wrapper" id="svg-wrapper" v-html="svg"></div>
|
|
35
42
|
</div>
|
|
36
|
-
<div
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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>
|
|
43
|
+
<div
|
|
44
|
+
v-else
|
|
45
|
+
class="relative flex h-full min-h-87.5 w-full items-center justify-center rounded border border-dark-550 bg-dark-500 p-2 shadow">
|
|
46
|
+
<div class="text-center">
|
|
47
|
+
<FilterIcon class="mx-auto empty-state-icon" />
|
|
48
|
+
<p class="text-sm text-light-400">No Map</p>
|
|
49
|
+
<p class="mt-1 text-xs text-light-500">
|
|
50
|
+
Enter an event ID and click "Load" to display the venue map
|
|
51
|
+
</p>
|
|
60
52
|
</div>
|
|
61
53
|
</div>
|
|
62
54
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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>
|
|
93
|
-
</div>
|
|
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>
|
|
107
|
-
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="flex min-h-75 min-w-0 w-full flex-col lg:min-h-125 lg:w-2/5">
|
|
57
|
+
<div class="mb-2 flex flex-shrink-0 flex-wrap items-center gap-2 text-white" v-if="hasLoaded">
|
|
58
|
+
<PriceSortToggle
|
|
59
|
+
:current="filterBuilder.globalFilter.priceSort"
|
|
60
|
+
class="smooth-hover h-8"
|
|
61
|
+
@change="(e) => filterBuilder.updateGlobalFilter({ priceSort: e })" />
|
|
62
|
+
<label class="text-xs text-light-400">Max:</label>
|
|
63
|
+
<input
|
|
64
|
+
type="number"
|
|
65
|
+
:value="filterBuilder.globalFilter.maxPrice"
|
|
66
|
+
@input="
|
|
67
|
+
(e) =>
|
|
68
|
+
filterBuilder.updateGlobalFilter({
|
|
69
|
+
maxPrice: parseInt(e.target.value || 0)
|
|
70
|
+
})
|
|
71
|
+
"
|
|
72
|
+
class="input-default h-8 w-16 px-2 text-sm"
|
|
73
|
+
placeholder="999" />
|
|
74
|
+
</div>
|
|
75
|
+
<div class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-dark-550 bg-dark-500 shadow-sm">
|
|
76
|
+
<div class="flex-shrink-0 border-b border-dark-550 bg-dark-300 px-4 py-3 text-xs text-white">
|
|
77
|
+
<div class="flex w-full items-center justify-between gap-2">
|
|
78
|
+
<div class="flex items-center gap-2">
|
|
79
|
+
<span class="text-sm font-medium text-white">Filters</span>
|
|
80
|
+
<span class="text-sm text-light-400">{{ filterBuilder.filters.length }}</span>
|
|
108
81
|
</div>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<Filter
|
|
130
|
-
v-if="doesFilterShow(f)"
|
|
131
|
-
:filter="f"
|
|
132
|
-
:index="i"
|
|
133
|
-
:filterBuilder="filterBuilder"
|
|
134
|
-
class="compact-filter" />
|
|
135
|
-
</template>
|
|
136
|
-
</draggable>
|
|
82
|
+
<div class="flex items-center gap-2">
|
|
83
|
+
<PriceSortToggle
|
|
84
|
+
class="h-8 w-14 flex-shrink-0 text-xs"
|
|
85
|
+
:options="['All', 'WL', 'BL']"
|
|
86
|
+
:current="shownFilters"
|
|
87
|
+
@change="(e) => (shownFilters = e)" />
|
|
88
|
+
<button
|
|
89
|
+
class="filter-action-btn"
|
|
90
|
+
@click="saveFilter"
|
|
91
|
+
title="Save filter">
|
|
92
|
+
<EditIcon class="h-3 w-3 flex-shrink-0" />
|
|
93
|
+
<span>Save</span>
|
|
94
|
+
</button>
|
|
95
|
+
<button
|
|
96
|
+
class="filter-action-btn"
|
|
97
|
+
@click="filterBuilder.reset(false)"
|
|
98
|
+
title="Clear filters">
|
|
99
|
+
<TrashIcon class="h-3 w-3 flex-shrink-0" />
|
|
100
|
+
<span>Clear</span>
|
|
101
|
+
</button>
|
|
137
102
|
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="hidden-scrollbars flex-1 overflow-auto bg-dark-400">
|
|
106
|
+
<draggable
|
|
107
|
+
v-if="filterBuilder.filters.length"
|
|
108
|
+
v-model="draggableFilters"
|
|
109
|
+
handle=".handle"
|
|
110
|
+
item-key="id"
|
|
111
|
+
tag="div"
|
|
112
|
+
class="space-y-0 p-1"
|
|
113
|
+
ghost-class="opacity-30 border border-dark-550 bg-dark-550/10"
|
|
114
|
+
drag-class="z-50 shadow-xl"
|
|
115
|
+
:animation="200">
|
|
116
|
+
<template #item="{ element: f, index: i }">
|
|
117
|
+
<Filter
|
|
118
|
+
v-show="doesFilterShow(f)"
|
|
119
|
+
:filter="f"
|
|
120
|
+
:index="i"
|
|
121
|
+
:filterBuilder="filterBuilder"
|
|
122
|
+
class="!p-1 !text-xs" />
|
|
123
|
+
</template>
|
|
124
|
+
</draggable>
|
|
138
125
|
<div
|
|
139
126
|
v-else
|
|
140
127
|
class="empty-state flex flex-col items-center justify-center py-8 text-center">
|
|
141
|
-
<FilterIcon class="
|
|
128
|
+
<FilterIcon class="empty-state-icon" />
|
|
142
129
|
<p class="text-sm text-light-400">No filters yet</p>
|
|
143
130
|
<p class="mt-1 text-xs text-light-500">Click on the map to create filters</p>
|
|
144
131
|
</div>
|
|
145
132
|
</div>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
</
|
|
160
|
-
|
|
161
|
-
@click="ui.toggleModal('preview-filter')"
|
|
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" />
|
|
164
|
-
JSON
|
|
165
|
-
</button>
|
|
166
|
-
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="mt-2 flex flex-shrink-0 items-center justify-between gap-2">
|
|
135
|
+
<button
|
|
136
|
+
@click="addWildcardFilter"
|
|
137
|
+
:disabled="hasWildcardFilter"
|
|
138
|
+
class="filter-wildcard-btn"
|
|
139
|
+
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'">
|
|
140
|
+
* Wildcard
|
|
141
|
+
</button>
|
|
142
|
+
<button
|
|
143
|
+
@click="ui.toggleModal('preview-filter')"
|
|
144
|
+
class="filter-action-btn">
|
|
145
|
+
<CameraIcon class="h-3 w-3" />
|
|
146
|
+
<span>JSON</span>
|
|
147
|
+
</button>
|
|
167
148
|
</div>
|
|
168
149
|
</div>
|
|
169
150
|
</div>
|
|
170
|
-
|
|
171
|
-
<transition-group name="fade">
|
|
172
|
-
<FilterPreview v-if="activeModal === 'preview-filter'" :filter="filterBuilder" />
|
|
173
|
-
</transition-group>
|
|
174
151
|
</div>
|
|
152
|
+
|
|
153
|
+
<transition-group name="fade">
|
|
154
|
+
<FilterPreview v-if="activeModal === 'preview-filter'" :filter="filterBuilder" />
|
|
155
|
+
</transition-group>
|
|
175
156
|
</div>
|
|
176
157
|
</template>
|
|
177
158
|
|
|
178
159
|
<script setup>
|
|
179
160
|
import draggable from "vuedraggable";
|
|
180
|
-
import { ref, nextTick, watch, computed,
|
|
161
|
+
import { ref, nextTick, watch, computed, defineAsyncComponent, onUnmounted } from "vue";
|
|
181
162
|
import { onBeforeRouteLeave } from "vue-router";
|
|
182
|
-
import { Table, Header } from "@/components/Table";
|
|
183
163
|
import Filter from "@/components/Filter/Filter.vue";
|
|
184
164
|
import { FilterIcon } from "@/components/icons";
|
|
185
|
-
import {
|
|
186
|
-
|
|
187
|
-
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon, StadiumIcon, SavingsIcon } from "@/components/icons";
|
|
165
|
+
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon } from "@/components/icons";
|
|
188
166
|
import { sendSaveFilter } from "@/stores/requests";
|
|
189
167
|
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
190
168
|
import { useUIStore } from "@/stores/ui";
|
|
191
169
|
import FilterBuilder from "@/libs/Filter";
|
|
192
170
|
import panzoom from "@/libs/panzoom.js";
|
|
193
|
-
|
|
171
|
+
|
|
172
|
+
// Lazy-loaded modal component
|
|
173
|
+
const FilterPreview = defineAsyncComponent(() => import("@/components/Filter/FilterPreview.vue"));
|
|
194
174
|
const activeModal = computed(() => ui.activeModal);
|
|
195
175
|
|
|
196
176
|
const ui = useUIStore();
|
|
@@ -214,6 +194,12 @@ let renderer;
|
|
|
214
194
|
|
|
215
195
|
const shownFilters = ref("All");
|
|
216
196
|
const filterBuilder = ref(new FilterBuilder());
|
|
197
|
+
const draggableFilters = computed({
|
|
198
|
+
get: () => filterBuilder.value.filters,
|
|
199
|
+
set: (value) => {
|
|
200
|
+
filterBuilder.value.filters = value;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
217
203
|
|
|
218
204
|
const hasWildcardFilter = computed(() => {
|
|
219
205
|
return filterBuilder.value.filters.some((filter) => filter.buyAny && filterBuilder.value.isForCurrentEvent(filter));
|
|
@@ -221,28 +207,23 @@ const hasWildcardFilter = computed(() => {
|
|
|
221
207
|
|
|
222
208
|
const addWildcardFilter = () => {
|
|
223
209
|
if (!hasWildcardFilter.value) {
|
|
224
|
-
// Close any expanded filter first
|
|
225
210
|
filterBuilder.value.setExpandedFilter(null);
|
|
226
211
|
filterBuilder.value.addFilter({ buyAny: true, eventId: filterBuilder.value.currentEventId });
|
|
227
212
|
filterBuilder.value.updateCss();
|
|
228
213
|
}
|
|
229
214
|
};
|
|
230
215
|
|
|
231
|
-
// Initialize RendererFactory
|
|
232
216
|
let RendererFactory = import("@necrolab/tm-renderer");
|
|
233
217
|
|
|
234
|
-
// Real-time CSS injection system
|
|
235
218
|
let styleElement = null;
|
|
236
219
|
const cssUpdateTrigger = ref(0);
|
|
237
220
|
const STYLE_ELEMENT_ID = "filter-builder-styles";
|
|
238
221
|
|
|
239
222
|
const injectStyles = () => {
|
|
240
|
-
// Only inject styles if we have an SVG to style
|
|
241
223
|
const svgWrapper = document.getElementById("svg-wrapper");
|
|
242
224
|
if (!svgWrapper || !svg.value) return;
|
|
243
225
|
|
|
244
226
|
if (!styleElement) {
|
|
245
|
-
// Remove any existing style element first
|
|
246
227
|
const existingStyle = document.getElementById(STYLE_ELEMENT_ID);
|
|
247
228
|
if (existingStyle) {
|
|
248
229
|
existingStyle.remove();
|
|
@@ -259,9 +240,7 @@ const injectStyles = () => {
|
|
|
259
240
|
}
|
|
260
241
|
};
|
|
261
242
|
|
|
262
|
-
// Setup real-time CSS updates
|
|
263
243
|
const setupCSSUpdates = () => {
|
|
264
|
-
// Override updateCss method to trigger immediate updates
|
|
265
244
|
const originalUpdateCss = filterBuilder.value.updateCss.bind(filterBuilder.value);
|
|
266
245
|
filterBuilder.value.updateCss = () => {
|
|
267
246
|
originalUpdateCss();
|
|
@@ -271,7 +250,6 @@ const setupCSSUpdates = () => {
|
|
|
271
250
|
});
|
|
272
251
|
};
|
|
273
252
|
|
|
274
|
-
// Override highlight method for immediate temporary CSS
|
|
275
253
|
const originalHighlight = filterBuilder.value.highlight.bind(filterBuilder.value);
|
|
276
254
|
filterBuilder.value.highlight = (...args) => {
|
|
277
255
|
originalHighlight(...args);
|
|
@@ -280,7 +258,6 @@ const setupCSSUpdates = () => {
|
|
|
280
258
|
});
|
|
281
259
|
};
|
|
282
260
|
|
|
283
|
-
// Override clearHighlight method
|
|
284
261
|
const originalClearHighlight = filterBuilder.value.clearHighlight.bind(filterBuilder.value);
|
|
285
262
|
filterBuilder.value.clearHighlight = () => {
|
|
286
263
|
originalClearHighlight();
|
|
@@ -304,11 +281,6 @@ const cleanupStyles = () => {
|
|
|
304
281
|
}
|
|
305
282
|
};
|
|
306
283
|
|
|
307
|
-
// Basic cleanup monitoring
|
|
308
|
-
const debugCleanup = () => {
|
|
309
|
-
console.log("FilterBuilder cleanup completed");
|
|
310
|
-
};
|
|
311
|
-
|
|
312
284
|
// Clean up any leftover styles from previous instances
|
|
313
285
|
cleanupStyles();
|
|
314
286
|
|
|
@@ -346,42 +318,63 @@ RendererFactory.then((r) => {
|
|
|
346
318
|
rendererFactory = new r.default();
|
|
347
319
|
rendererFactory.init();
|
|
348
320
|
}).catch((error) => {
|
|
349
|
-
|
|
321
|
+
ui.logger.Error("Failed to initialize renderer:", error);
|
|
350
322
|
});
|
|
351
323
|
|
|
352
324
|
const updateShownVenue = async () => {
|
|
353
325
|
eventId.value = eventId.value.trim();
|
|
354
326
|
if (eventId.value.includes("/event/")) eventId.value = eventId.value.split("/event/")[1];
|
|
355
327
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
328
|
+
// Determine country based on eventId length and current module
|
|
329
|
+
let country = null;
|
|
330
|
+
if (eventId.value.length === 16) {
|
|
331
|
+
// 16-char eventIds are global, but try current country first as fallback
|
|
332
|
+
country = ui.currentCountry?.id || null;
|
|
333
|
+
} else if (eventId.value.length > 0) {
|
|
334
|
+
// Shorter eventIds need country specification
|
|
335
|
+
country = ui.currentCountry?.id;
|
|
336
|
+
if (!country) {
|
|
337
|
+
ui.showError("Invalid eventId or missing country!");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
ui.showError("Event ID is required!");
|
|
360
342
|
return;
|
|
361
343
|
}
|
|
362
344
|
|
|
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
|
-
});
|
|
373
|
-
|
|
374
345
|
try {
|
|
346
|
+
if (!rendererFactory) {
|
|
347
|
+
ui.showError("Renderer not initialized yet. Please wait and try again.");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
renderer = rendererFactory.createRenderer(eventId.value, {
|
|
352
|
+
proxy: "",
|
|
353
|
+
country: country,
|
|
354
|
+
seatColor: "#0557ae",
|
|
355
|
+
nonAvSeatColor: "#dadcde",
|
|
356
|
+
highlightedSeatColor: "#d0006f"
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
filterBuilder.value.highlightedSeatColor = renderer.config.highlightedSeatColor;
|
|
360
|
+
renderer.setCustomConfig({
|
|
361
|
+
logFullError: true,
|
|
362
|
+
includeDetailedAttributes: true,
|
|
363
|
+
renderRowBlocks: true
|
|
364
|
+
});
|
|
365
|
+
|
|
375
366
|
await renderer.render();
|
|
376
367
|
svg.value = renderer.svg;
|
|
368
|
+
|
|
369
|
+
if (!svg.value) {
|
|
370
|
+
throw new Error("Renderer returned empty SVG");
|
|
371
|
+
}
|
|
377
372
|
} catch (e) {
|
|
378
373
|
ui.logger.Error("COULD NOT RENDER SVG", e);
|
|
379
|
-
|
|
380
|
-
|
|
374
|
+
const errorMsg = e?.message || "Unknown rendering error";
|
|
375
|
+
ui.showError(`Failed to render venue: ${errorMsg}`);
|
|
381
376
|
|
|
382
|
-
|
|
383
|
-
ui.logger.Error("Could not render SVG");
|
|
384
|
-
ui.showError("Could not fetch event map");
|
|
377
|
+
// Try to load existing filter even if rendering fails
|
|
385
378
|
await loadFilter();
|
|
386
379
|
filterBuilder.value.reload(eventId.value);
|
|
387
380
|
return;
|
|
@@ -485,203 +478,27 @@ watch(renderSeats, async () => {
|
|
|
485
478
|
})();
|
|
486
479
|
</script>
|
|
487
480
|
|
|
488
|
-
<style scoped>
|
|
489
|
-
.compact-filter {
|
|
490
|
-
font-size: 0.75rem;
|
|
491
|
-
padding: 0.25rem !important;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.compact-filter .handle {
|
|
495
|
-
transform: scale(0.8);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/* Enhanced table and filter styling */
|
|
499
|
-
.filters-container {
|
|
500
|
-
@apply space-y-0;
|
|
501
|
-
padding: 4px;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/* Dragging states for better UX */
|
|
505
|
-
.sortable-ghost {
|
|
506
|
-
@apply opacity-30;
|
|
507
|
-
border: 1px solid oklch(0.28 0 0);
|
|
508
|
-
background-color: rgba(68, 69, 75, 0.1);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
.sortable-chosen .drag-handle {
|
|
512
|
-
border: 1px solid oklch(0.28 0 0);
|
|
513
|
-
background-color: rgba(68, 69, 75, 0.2);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
.sortable-drag {
|
|
517
|
-
@apply z-50 rotate-1 scale-105 transform shadow-xl;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/* Unified Event ID + Load button group */
|
|
521
|
-
.unified-load-group {
|
|
522
|
-
border: 2px solid oklch(0.2809 0 0);
|
|
523
|
-
border-radius: 0.5rem;
|
|
524
|
-
overflow: hidden;
|
|
525
|
-
background: oklch(0.2603 0 0);
|
|
526
|
-
transition: all 0.15s ease;
|
|
527
|
-
|
|
528
|
-
input, button {
|
|
529
|
-
border: none !important;
|
|
530
|
-
border-radius: 0 !important;
|
|
531
|
-
outline: none !important;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
input {
|
|
535
|
-
flex: 1;
|
|
536
|
-
|
|
537
|
-
&::placeholder {
|
|
538
|
-
color: oklch(0.50 0 0);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
button {
|
|
543
|
-
border-left: 1px solid oklch(0.2809 0 0) !important;
|
|
544
|
-
padding: 0 1rem;
|
|
545
|
-
font-weight: 500;
|
|
546
|
-
transition: all 0.15s ease;
|
|
547
|
-
|
|
548
|
-
&:hover {
|
|
549
|
-
background: oklch(0.72 0.15 145 / 0.1);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
&:hover {
|
|
554
|
-
border-color: oklch(0.30 0 0);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
&:focus-within {
|
|
558
|
-
border-color: oklch(0.72 0.15 145);
|
|
559
|
-
outline: 1px solid oklch(0.72 0.15 145);
|
|
560
|
-
outline-offset: 0;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/* Header button styling */
|
|
565
|
-
.header-btn {
|
|
566
|
-
@apply flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium transition-all duration-150;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
.save-btn {
|
|
570
|
-
background-color: oklch(0.72 0.15 145 / 0.15);
|
|
571
|
-
border-color: oklch(0.72 0.15 145 / 0.5);
|
|
572
|
-
color: oklch(0.90 0 0);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
.save-btn:hover {
|
|
576
|
-
background-color: oklch(0.72 0.15 145 / 0.25);
|
|
577
|
-
border-color: oklch(0.72 0.15 145);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
.clear-btn {
|
|
581
|
-
background-color: oklch(0.60 0.20 25 / 0.15);
|
|
582
|
-
border-color: oklch(0.60 0.20 25 / 0.5);
|
|
583
|
-
color: oklch(0.90 0 0);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
.clear-btn:hover {
|
|
587
|
-
background-color: oklch(0.60 0.20 25 / 0.25);
|
|
588
|
-
border-color: oklch(0.60 0.20 25);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
.filter-builder-container {
|
|
592
|
-
height: 90vh;
|
|
593
|
-
overflow: hidden;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
@media (max-width: 768px) {
|
|
597
|
-
.filter-builder-container {
|
|
598
|
-
overflow: auto;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/* Mobile portrait header spacing fixes */
|
|
603
|
-
@media (max-width: 640px) {
|
|
604
|
-
.filter-builder-container .flex.items-center.justify-center {
|
|
605
|
-
gap: 0.5rem;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/* iPhone landscape and tablet portrait mode fixes */
|
|
610
|
-
@media (max-width: 1023px) and (max-height: 768px) {
|
|
611
|
-
.filter-builder-container {
|
|
612
|
-
height: auto;
|
|
613
|
-
min-height: 100vh;
|
|
614
|
-
overflow: auto;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/* Desktop: enforce grid column constraints */
|
|
619
|
-
@media (min-width: 1024px) {
|
|
620
|
-
.lg\\:grid-cols-5 > .lg\\:col-span-3 {
|
|
621
|
-
max-width: 60%; /* 3/5 of grid */
|
|
622
|
-
flex-shrink: 0;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
.svg-container {
|
|
627
|
-
min-height: 300px;
|
|
628
|
-
height: 100%;
|
|
629
|
-
width: 100%;
|
|
630
|
-
position: relative;
|
|
631
|
-
overflow: auto;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/* Desktop: constrain svg-container to prevent expansion */
|
|
635
|
-
@media (min-width: 1024px) {
|
|
636
|
-
.svg-container {
|
|
637
|
-
position: relative;
|
|
638
|
-
max-width: 100%;
|
|
639
|
-
max-height: 100%;
|
|
640
|
-
overflow: hidden;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
</style>
|
|
644
|
-
|
|
645
481
|
<style>
|
|
646
482
|
/* Global styles needed for dynamically injected SVG content and Filter.js CSS injection */
|
|
647
483
|
.svg-wrapper {
|
|
648
484
|
position: relative;
|
|
649
|
-
transform-origin:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
@media (min-width: 1024px) {
|
|
656
|
-
.svg-wrapper {
|
|
657
|
-
position: absolute;
|
|
658
|
-
top: 0;
|
|
659
|
-
left: 0;
|
|
660
|
-
width: 100%;
|
|
661
|
-
height: 100%;
|
|
662
|
-
max-width: 100%;
|
|
663
|
-
max-height: 100%;
|
|
664
|
-
min-width: unset;
|
|
665
|
-
min-height: unset;
|
|
666
|
-
overflow: hidden;
|
|
667
|
-
}
|
|
485
|
+
transform-origin: center center;
|
|
486
|
+
width: 100%;
|
|
487
|
+
height: 100%;
|
|
488
|
+
display: flex;
|
|
489
|
+
align-items: center;
|
|
490
|
+
justify-content: center;
|
|
668
491
|
}
|
|
669
492
|
|
|
670
493
|
.svg-wrapper > svg {
|
|
671
494
|
width: 100%;
|
|
672
495
|
height: auto;
|
|
496
|
+
max-height: 100%;
|
|
497
|
+
object-fit: contain;
|
|
673
498
|
background: center center no-repeat;
|
|
674
499
|
background-size: contain;
|
|
675
500
|
}
|
|
676
501
|
|
|
677
|
-
/* Desktop SVG constraints */
|
|
678
|
-
@media (min-width: 1024px) {
|
|
679
|
-
.svg-wrapper > svg {
|
|
680
|
-
max-width: 100%;
|
|
681
|
-
max-height: 100%;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
502
|
/* Ensure Filter.js generated CSS works properly */
|
|
686
503
|
.svg-wrapper path {
|
|
687
504
|
pointer-events: auto;
|