@necrolab/dashboard 0.5.15 → 0.5.17
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 +70 -566
- 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 +61 -74
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +13 -21
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -22
- 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 +295 -0
- package/src/assets/css/main.scss +55 -139
- package/src/components/Auth/LoginForm.vue +7 -86
- package/src/components/Console/ConsoleToolbar.vue +123 -0
- package/src/components/Editors/Account/Account.vue +12 -12
- package/src/components/Editors/Account/AccountView.vue +38 -111
- package/src/components/Editors/Account/CreateAccount.vue +11 -61
- package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
- package/src/components/Editors/AdminFileEditor.vue +179 -0
- package/src/components/Editors/Profile/CreateProfile.vue +77 -150
- package/src/components/Editors/Profile/Profile.vue +20 -21
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +41 -116
- package/src/components/Editors/ProxyFileEditor.vue +86 -0
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +66 -79
- package/src/components/Filter/FilterPreview.vue +153 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +45 -51
- 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 +20 -39
- package/src/components/Tasks/Task.vue +64 -270
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +45 -64
- package/src/components/Tasks/Utilities.vue +10 -44
- 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/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -5
- 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 +49 -49
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +161 -54
- package/src/components/ui/Navbar.vue +63 -44
- 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 +29 -66
- 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 +103 -139
- package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useCodeEditor.js +117 -0
- 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 +1 -4
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useEnableDisable.js +6 -0
- package/src/composables/useFilterCSS.js +71 -0
- package/src/composables/useFormValidation.js +92 -0
- package/src/composables/useGetAllTags.js +9 -0
- package/src/composables/useIOSViewportHandling.js +76 -0
- package/src/composables/useNotchHandling.js +306 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTableRender.js +23 -0
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/composables/useZoomPrevention.js +96 -0
- package/src/constants/tableLayout.js +14 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +58 -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 +3 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +64 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -29
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +80 -71
- package/src/utils/tableHelpers.js +1 -0
- package/src/views/Accounts.vue +19 -38
- package/src/views/Console.vue +74 -253
- package/src/views/Editor.vue +47 -1114
- package/src/views/FilterBuilder.vue +190 -461
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +17 -32
- 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 -2438
- package/exit +0 -209
- package/run +0 -177
- package/src/assets/css/base/color-fallbacks.scss +0 -10
- package/src/assets/img/background.svg.backup +0 -11
- package/src/components/icons/SquareCheck.vue +0 -18
- package/src/components/icons/SquareUncheck.vue +0 -18
- package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
|
@@ -1,196 +1,177 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<div class="page-header-card">
|
|
2
|
+
<div class="flex w-full flex-col">
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<div class="page-header-card flex-shrink-0">
|
|
6
5
|
<FilterIcon />
|
|
7
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-standard hover:bg-dark-450"
|
|
16
|
+
@click="updateShownVenue"
|
|
17
|
+
aria-label="Load venue">
|
|
18
|
+
<ReloadIcon class="icon-md" />
|
|
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-gap-2 items-center">
|
|
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="icon-md 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-gap-2 items-center">
|
|
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-gap-2 items-center">
|
|
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,
|
|
161
|
+
import { ref, computed, defineAsyncComponent, watch, onMounted, onUnmounted, nextTick } from "vue";
|
|
181
162
|
import { onBeforeRouteLeave } from "vue-router";
|
|
182
|
-
import {
|
|
163
|
+
import { useFilterCSS } from "@/composables/useFilterCSS";
|
|
183
164
|
import Filter from "@/components/Filter/Filter.vue";
|
|
184
165
|
import { FilterIcon } from "@/components/icons";
|
|
185
|
-
import {
|
|
186
|
-
|
|
187
|
-
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon, StadiumIcon, SavingsIcon } from "@/components/icons";
|
|
166
|
+
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon } from "@/components/icons";
|
|
188
167
|
import { sendSaveFilter } from "@/stores/requests";
|
|
189
168
|
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
190
169
|
import { useUIStore } from "@/stores/ui";
|
|
191
170
|
import FilterBuilder from "@/libs/Filter";
|
|
192
171
|
import panzoom from "@/libs/panzoom.js";
|
|
193
|
-
|
|
172
|
+
|
|
173
|
+
// Lazy-loaded modal component
|
|
174
|
+
const FilterPreview = defineAsyncComponent(() => import("@/components/Filter/FilterPreview.vue"));
|
|
194
175
|
const activeModal = computed(() => ui.activeModal);
|
|
195
176
|
|
|
196
177
|
const ui = useUIStore();
|
|
@@ -214,6 +195,12 @@ let renderer;
|
|
|
214
195
|
|
|
215
196
|
const shownFilters = ref("All");
|
|
216
197
|
const filterBuilder = ref(new FilterBuilder());
|
|
198
|
+
const draggableFilters = computed({
|
|
199
|
+
get: () => filterBuilder.value.filters,
|
|
200
|
+
set: (value) => {
|
|
201
|
+
filterBuilder.value.filters = value;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
217
204
|
|
|
218
205
|
const hasWildcardFilter = computed(() => {
|
|
219
206
|
return filterBuilder.value.filters.some((filter) => filter.buyAny && filterBuilder.value.isForCurrentEvent(filter));
|
|
@@ -221,118 +208,15 @@ const hasWildcardFilter = computed(() => {
|
|
|
221
208
|
|
|
222
209
|
const addWildcardFilter = () => {
|
|
223
210
|
if (!hasWildcardFilter.value) {
|
|
224
|
-
// Close any expanded filter first
|
|
225
211
|
filterBuilder.value.setExpandedFilter(null);
|
|
226
212
|
filterBuilder.value.addFilter({ buyAny: true, eventId: filterBuilder.value.currentEventId });
|
|
227
213
|
filterBuilder.value.updateCss();
|
|
228
214
|
}
|
|
229
215
|
};
|
|
230
216
|
|
|
231
|
-
// Initialize RendererFactory
|
|
232
217
|
let RendererFactory = import("@necrolab/tm-renderer");
|
|
233
218
|
|
|
234
|
-
|
|
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
|
-
});
|
|
219
|
+
const { injectStyles, cssUpdateTrigger } = useFilterCSS(filterBuilder, svg);
|
|
336
220
|
|
|
337
221
|
const doesFilterShow = (filter) => {
|
|
338
222
|
if ((filter.event || filter.eventId) !== filterBuilder.value.currentEventId) return;
|
|
@@ -346,42 +230,63 @@ RendererFactory.then((r) => {
|
|
|
346
230
|
rendererFactory = new r.default();
|
|
347
231
|
rendererFactory.init();
|
|
348
232
|
}).catch((error) => {
|
|
349
|
-
|
|
233
|
+
ui.logger.Error("Failed to initialize renderer:", error);
|
|
350
234
|
});
|
|
351
235
|
|
|
352
236
|
const updateShownVenue = async () => {
|
|
353
237
|
eventId.value = eventId.value.trim();
|
|
354
238
|
if (eventId.value.includes("/event/")) eventId.value = eventId.value.split("/event/")[1];
|
|
355
239
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
240
|
+
// Determine country based on eventId length and current module
|
|
241
|
+
let country = null;
|
|
242
|
+
if (eventId.value.length === 16) {
|
|
243
|
+
// 16-char eventIds are global, but try current country first as fallback
|
|
244
|
+
country = ui.currentCountry?.id || null;
|
|
245
|
+
} else if (eventId.value.length > 0) {
|
|
246
|
+
// Shorter eventIds need country specification
|
|
247
|
+
country = ui.currentCountry?.id;
|
|
248
|
+
if (!country) {
|
|
249
|
+
ui.showError("Invalid eventId or missing country!");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
ui.showError("Event ID is required!");
|
|
360
254
|
return;
|
|
361
255
|
}
|
|
362
256
|
|
|
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
257
|
try {
|
|
258
|
+
if (!rendererFactory) {
|
|
259
|
+
ui.showError("Renderer not initialized yet. Please wait and try again.");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
renderer = rendererFactory.createRenderer(eventId.value, {
|
|
264
|
+
proxy: "",
|
|
265
|
+
country: country,
|
|
266
|
+
seatColor: "#0557ae",
|
|
267
|
+
nonAvSeatColor: "#dadcde",
|
|
268
|
+
highlightedSeatColor: "#d0006f"
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
filterBuilder.value.highlightedSeatColor = renderer.config.highlightedSeatColor;
|
|
272
|
+
renderer.setCustomConfig({
|
|
273
|
+
logFullError: true,
|
|
274
|
+
includeDetailedAttributes: true,
|
|
275
|
+
renderRowBlocks: true
|
|
276
|
+
});
|
|
277
|
+
|
|
375
278
|
await renderer.render();
|
|
376
279
|
svg.value = renderer.svg;
|
|
280
|
+
|
|
281
|
+
if (!svg.value) {
|
|
282
|
+
throw new Error("Renderer returned empty SVG");
|
|
283
|
+
}
|
|
377
284
|
} catch (e) {
|
|
378
285
|
ui.logger.Error("COULD NOT RENDER SVG", e);
|
|
379
|
-
|
|
380
|
-
|
|
286
|
+
const errorMsg = e?.message || "Unknown rendering error";
|
|
287
|
+
ui.showError(`Failed to render venue: ${errorMsg}`);
|
|
381
288
|
|
|
382
|
-
|
|
383
|
-
ui.logger.Error("Could not render SVG");
|
|
384
|
-
ui.showError("Could not fetch event map");
|
|
289
|
+
// Try to load existing filter even if rendering fails
|
|
385
290
|
await loadFilter();
|
|
386
291
|
filterBuilder.value.reload(eventId.value);
|
|
387
292
|
return;
|
|
@@ -485,203 +390,27 @@ watch(renderSeats, async () => {
|
|
|
485
390
|
})();
|
|
486
391
|
</script>
|
|
487
392
|
|
|
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
393
|
<style>
|
|
646
394
|
/* Global styles needed for dynamically injected SVG content and Filter.js CSS injection */
|
|
647
395
|
.svg-wrapper {
|
|
648
396
|
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
|
-
}
|
|
397
|
+
transform-origin: center center;
|
|
398
|
+
width: 100%;
|
|
399
|
+
height: 100%;
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
justify-content: center;
|
|
668
403
|
}
|
|
669
404
|
|
|
670
405
|
.svg-wrapper > svg {
|
|
671
406
|
width: 100%;
|
|
672
407
|
height: auto;
|
|
408
|
+
max-height: 100%;
|
|
409
|
+
object-fit: contain;
|
|
673
410
|
background: center center no-repeat;
|
|
674
411
|
background-size: contain;
|
|
675
412
|
}
|
|
676
413
|
|
|
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
414
|
/* Ensure Filter.js generated CSS works properly */
|
|
686
415
|
.svg-wrapper path {
|
|
687
416
|
pointer-events: auto;
|