@necrolab/dashboard 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +45 -0
- package/.eslintrc.js +24 -0
- package/.prettierignore +1 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +3 -0
- package/ICONS.md +21 -0
- package/README.md +65 -0
- package/backend/api.js +430 -0
- package/backend/auth.js +62 -0
- package/backend/batching.js +43 -0
- package/backend/endpoints.js +343 -0
- package/backend/index.js +23 -0
- package/backend/mock-data.js +66 -0
- package/backend/mock-src/classes/logger.js +112 -0
- package/backend/mock-src/classes/utils.js +42 -0
- package/backend/mock-src/ticketmaster.js +92 -0
- package/backend/validator.js +62 -0
- package/config/configs.json +20 -0
- package/config/filter.json +3 -0
- package/config/presale.csv +3 -0
- package/config/proxies.txt +6 -0
- package/config/used-codes.json +4 -0
- package/index.html +114 -0
- package/index.js +2 -0
- package/jsconfig.json +16 -0
- package/package.json +48 -0
- package/postcss.config.js +6 -0
- package/postinstall.js +9 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/flags/ae.svg +1 -0
- package/public/flags/at.svg +1 -0
- package/public/flags/au.svg +1 -0
- package/public/flags/be.svg +1 -0
- package/public/flags/ch.svg +1 -0
- package/public/flags/cz.svg +1 -0
- package/public/flags/de.svg +1 -0
- package/public/flags/dk.svg +1 -0
- package/public/flags/es.svg +1 -0
- package/public/flags/nl.svg +1 -0
- package/public/flags/no.svg +1 -0
- package/public/flags/nz.svg +1 -0
- package/public/flags/pl.svg +1 -0
- package/public/flags/se.svg +1 -0
- package/public/flags/uk.svg +1 -0
- package/public/flags/us.svg +1 -0
- package/public/img/award.svg +3 -0
- package/public/img/background.svg +14 -0
- package/public/img/bag_w.svg +12 -0
- package/public/img/banks/amex.svg +4 -0
- package/public/img/banks/mastercard.svg +4 -0
- package/public/img/banks/visa.svg +4 -0
- package/public/img/camera.svg +3 -0
- package/public/img/close.svg +3 -0
- package/public/img/controls/disable.svg +5 -0
- package/public/img/controls/enable.svg +5 -0
- package/public/img/groups.svg +3 -0
- package/public/img/hand.svg +3 -0
- package/public/img/key.svg +3 -0
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/loyalty.svg +3 -0
- package/public/img/mail.svg +3 -0
- package/public/img/pencil.svg +3 -0
- package/public/img/profile.svg +4 -0
- package/public/img/reload.svg +3 -0
- package/public/img/sandclock.svg +25 -0
- package/public/img/save.svg +5 -0
- package/public/img/savings.svg +3 -0
- package/public/img/scanner.svg +3 -0
- package/public/img/sell.svg +3 -0
- package/public/img/shield.svg +3 -0
- package/public/img/ski.svg +3 -0
- package/public/img/stadium.svg +8 -0
- package/public/img/stadium_w.svg +8 -0
- package/public/img/timer.svg +3 -0
- package/public/manifest.json +27 -0
- package/public/robots.txt +2 -0
- package/run +10 -0
- package/src/App.vue +307 -0
- package/src/assets/css/_input.scss +197 -0
- package/src/assets/css/main.scss +269 -0
- package/src/assets/css/tailwind.css +3 -0
- package/src/assets/img/award.svg +3 -0
- package/src/assets/img/background.svg +11 -0
- package/src/assets/img/camera.svg +3 -0
- package/src/assets/img/close.svg +3 -0
- package/src/assets/img/eyes/closed.svg +13 -0
- package/src/assets/img/eyes/open.svg +12 -0
- package/src/assets/img/groups.svg +3 -0
- package/src/assets/img/hand.svg +3 -0
- package/src/assets/img/key.svg +3 -0
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/assets/img/loyalty.svg +3 -0
- package/src/assets/img/mail.svg +3 -0
- package/src/assets/img/pencil.svg +3 -0
- package/src/assets/img/reload.svg +3 -0
- package/src/assets/img/savings.svg +3 -0
- package/src/assets/img/scanner.svg +3 -0
- package/src/assets/img/sell.svg +3 -0
- package/src/assets/img/shield.svg +3 -0
- package/src/assets/img/ski.svg +3 -0
- package/src/assets/img/square_check.svg +5 -0
- package/src/assets/img/square_uncheck.svg +5 -0
- package/src/assets/img/stadium.svg +8 -0
- package/src/assets/img/timer.svg +3 -0
- package/src/assets/img/wildcard.svg +7 -0
- package/src/components/Auth/LoginForm.vue +48 -0
- package/src/components/Editors/Account/Account.vue +119 -0
- package/src/components/Editors/Account/AccountCreator.vue +147 -0
- package/src/components/Editors/Account/AccountView.vue +87 -0
- package/src/components/Editors/Account/CreateAccount.vue +106 -0
- package/src/components/Editors/Profile/CreateProfile.vue +321 -0
- package/src/components/Editors/Profile/Profile.vue +142 -0
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
- package/src/components/Editors/Profile/ProfileView.vue +96 -0
- package/src/components/Editors/TagLabel.vue +16 -0
- package/src/components/Editors/TagToggle.vue +41 -0
- package/src/components/Filter/Filter.vue +409 -0
- package/src/components/Filter/FilterPreview.vue +236 -0
- package/src/components/Filter/PriceSortToggle.vue +105 -0
- package/src/components/Table/Header.vue +5 -0
- package/src/components/Table/Row.vue +5 -0
- package/src/components/Table/Table.vue +14 -0
- package/src/components/Table/index.js +4 -0
- package/src/components/Tasks/CheckStock.vue +62 -0
- package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
- package/src/components/Tasks/Controls/MobileControls.vue +32 -0
- package/src/components/Tasks/Controls/index.js +3 -0
- package/src/components/Tasks/CreateTaskAXS.vue +339 -0
- package/src/components/Tasks/CreateTaskTM.vue +459 -0
- package/src/components/Tasks/MassEdit.vue +50 -0
- package/src/components/Tasks/QuickSettings.vue +167 -0
- package/src/components/Tasks/ScrapeVenue.vue +42 -0
- package/src/components/Tasks/Stats.vue +66 -0
- package/src/components/Tasks/Task.vue +296 -0
- package/src/components/Tasks/TaskLabel.vue +20 -0
- package/src/components/Tasks/TaskView.vue +126 -0
- package/src/components/Tasks/Utilities.vue +33 -0
- package/src/components/icons/Award.vue +8 -0
- package/src/components/icons/Bag.vue +8 -0
- package/src/components/icons/BagWhite.vue +8 -0
- package/src/components/icons/Box.vue +8 -0
- package/src/components/icons/Camera.vue +8 -0
- package/src/components/icons/Cart.vue +8 -0
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Checkmark.vue +11 -0
- package/src/components/icons/Click.vue +8 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Console.vue +13 -0
- package/src/components/icons/Down.vue +8 -0
- package/src/components/icons/Edit.vue +13 -0
- package/src/components/icons/Event.vue +8 -0
- package/src/components/icons/Expand.vue +8 -0
- package/src/components/icons/Filter.vue +13 -0
- package/src/components/icons/Gear.vue +8 -0
- package/src/components/icons/Group.vue +8 -0
- package/src/components/icons/Hand.vue +8 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Logout.vue +13 -0
- package/src/components/icons/Loyalty.vue +8 -0
- package/src/components/icons/Mail.vue +8 -0
- package/src/components/icons/Menu.vue +8 -0
- package/src/components/icons/Pause.vue +5 -0
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +8 -0
- package/src/components/icons/Plus.vue +8 -0
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +7 -0
- package/src/components/icons/Sandclock.vue +33 -0
- package/src/components/icons/Savings.vue +8 -0
- package/src/components/icons/Scanner.vue +8 -0
- package/src/components/icons/Scrape.vue +8 -0
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Shield.vue +8 -0
- package/src/components/icons/Shrink.vue +8 -0
- package/src/components/icons/Ski.vue +8 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +13 -0
- package/src/components/icons/StadiumWhite.vue +13 -0
- package/src/components/icons/Status.vue +8 -0
- package/src/components/icons/Tag.vue +8 -0
- package/src/components/icons/Tasks.vue +13 -0
- package/src/components/icons/Ticket.vue +8 -0
- package/src/components/icons/Timer.vue +8 -0
- package/src/components/icons/Trash.vue +8 -0
- package/src/components/icons/Up.vue +10 -0
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +111 -0
- package/src/components/ui/Modal.vue +61 -0
- package/src/components/ui/Navbar.vue +207 -0
- package/src/components/ui/ReconnectIndicator.vue +90 -0
- package/src/components/ui/Splash.vue +24 -0
- package/src/components/ui/controls/CountryChooser.vue +87 -0
- package/src/components/ui/controls/EyeToggle.vue +11 -0
- package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
- package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
- package/src/components/ui/controls/atomic/Switch.vue +84 -0
- package/src/libs/Filter.js +593 -0
- package/src/libs/ansii.js +565 -0
- package/src/libs/panzoom.js +1413 -0
- package/src/main.js +23 -0
- package/src/registerServiceWorker.js +32 -0
- package/src/router/index.js +65 -0
- package/src/stores/cities.json +1 -0
- package/src/stores/connection.js +399 -0
- package/src/stores/countries.js +88 -0
- package/src/stores/logger.js +103 -0
- package/src/stores/requests.js +88 -0
- package/src/stores/sampleData.js +1034 -0
- package/src/stores/ui.js +584 -0
- package/src/stores/utils.js +554 -0
- package/src/types/index.js +42 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +191 -0
- package/src/views/Console.vue +224 -0
- package/src/views/Editor.vue +785 -0
- package/src/views/FilterBuilder.vue +785 -0
- package/src/views/Login.vue +27 -0
- package/src/views/Profiles.vue +209 -0
- package/src/views/Tasks.vue +157 -0
- package/static/offline.html +184 -0
- package/tailwind.config.js +57 -0
- package/vite.config.js +63 -0
- package/vue.config.js +32 -0
- package/workbox-config.js +66 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
@click="increase"
|
|
4
|
+
class="h-10 px-1 text-white bg-dark-500 relative border-2 border-dark-550 overflow-hidden"
|
|
5
|
+
>
|
|
6
|
+
<span v-if="sortOptions[currentOpt % sortOptions.length] === 'Enabled'"
|
|
7
|
+
><img class="mx-auto" height="16px" width="14px" src="@/assets/img/square_check.svg"
|
|
8
|
+
/></span>
|
|
9
|
+
<span v-else-if="sortOptions[currentOpt % sortOptions.length] === 'Disabled'"
|
|
10
|
+
><img class="mx-auto" height="16px" width="14px" src="@/assets/img/square_uncheck.svg"
|
|
11
|
+
/></span>
|
|
12
|
+
<!-- <span v-else-if="sortOptions[currentOpt % sortOptions.length] === 'All'"
|
|
13
|
+
><img height="16px" width="14px" src="@/assets/img/wildcard.svg"
|
|
14
|
+
/></span> -->
|
|
15
|
+
<span v-else>{{ sortOptions[currentOpt % sortOptions.length] }}</span>
|
|
16
|
+
</button>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup>
|
|
20
|
+
import { ref } from "vue";
|
|
21
|
+
|
|
22
|
+
let sortOptions = ref([]);
|
|
23
|
+
const currentOpt = ref(0);
|
|
24
|
+
|
|
25
|
+
const props = defineProps({
|
|
26
|
+
filter: Object,
|
|
27
|
+
index: Number,
|
|
28
|
+
expandedFilter: Number,
|
|
29
|
+
filterBuilder: Object,
|
|
30
|
+
options: Object
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
sortOptions.value = props.options;
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits(["change"]);
|
|
36
|
+
|
|
37
|
+
const increase = () => {
|
|
38
|
+
currentOpt.value++;
|
|
39
|
+
emit("change", sortOptions.value[currentOpt.value % sortOptions.value.length]);
|
|
40
|
+
};
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="filter-card group text-white text-sm transition-all duration-200 hover:bg-dark-400 relative"
|
|
4
|
+
:class="{
|
|
5
|
+
'expanded-filter': filterBuilder.expandedFilter === filter.id,
|
|
6
|
+
'excluded-filter': filter.exclude,
|
|
7
|
+
'normal-filter': !filterBuilder.expandedFilter && !filter.exclude
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<div class="grid grid-cols-12 items-center py-3 px-2 sm:px-4 gap-2 sm:gap-3">
|
|
11
|
+
<div class="col-span-9 sm:col-span-10">
|
|
12
|
+
<div class="flex items-center gap-2 sm:gap-3 cursor-pointer flex-1" @click="handleFilterClick(filter.id)">
|
|
13
|
+
<div class="filter-type-badge flex-shrink-0">
|
|
14
|
+
<component :is="getFilterIcon()" class="w-3 h-3 sm:w-4 sm:h-4" />
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex-1 min-w-0">
|
|
17
|
+
<h3 class="font-medium text-white group-hover:text-light-400 transition-colors text-sm sm:text-base truncate">{{ filterTitle }}</h3>
|
|
18
|
+
<p class="text-xs text-gray mt-1 truncate" v-if="getFilterSubtitle()">{{ getFilterSubtitle() }}</p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="expanded-content mt-4 mx-2 sm:mx-0" v-if="filterBuilder.expandedFilter === filter.id">
|
|
22
|
+
<!-- NORMAL -->
|
|
23
|
+
<div v-if="filterType === 2" class="space-y-2">
|
|
24
|
+
<div class="info-row">
|
|
25
|
+
<span class="label">Section:</span>
|
|
26
|
+
<span class="value">{{ filter?.section }}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="info-row" v-if="filter.rows?.length">
|
|
29
|
+
<span class="label">Rows:</span>
|
|
30
|
+
<span class="value">{{ filter?.rows?.join(", ") }}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="info-row">
|
|
33
|
+
<span class="label">Event:</span>
|
|
34
|
+
<span class="value">{{ filter?.event || filter?.eventId || "-" }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<!-- NORMAL_FIRSTROW -->
|
|
38
|
+
<div v-if="filterType === 3" class="space-y-2">
|
|
39
|
+
<div class="info-row">
|
|
40
|
+
<span class="label">Section:</span>
|
|
41
|
+
<span class="value">{{ filter?.section }}</span>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="info-row">
|
|
44
|
+
<span class="label">Row:</span>
|
|
45
|
+
<span class="value">{{ filter?._row }}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="info-row">
|
|
48
|
+
<span class="label">Event:</span>
|
|
49
|
+
<span class="value">{{ filter?.event || "-" }}</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<!-- CATCH_ALL_GA -->
|
|
53
|
+
<div v-if="filterType === 1">
|
|
54
|
+
<button
|
|
55
|
+
@click.stop="update({ buyAny: true, floor: false, generalAdmission: false })"
|
|
56
|
+
class="conversion-btn"
|
|
57
|
+
>
|
|
58
|
+
<component :is="'WildcardIcon'" class="w-4 h-4" />
|
|
59
|
+
Convert to wildcard
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<!-- CATCH_ALL -->
|
|
63
|
+
<div v-if="filterType === 0">
|
|
64
|
+
<button
|
|
65
|
+
@click.stop="update({ floor: true, buyAny: false, generalAdmission: false })"
|
|
66
|
+
class="conversion-btn"
|
|
67
|
+
>
|
|
68
|
+
<component :is="'BoxIcon'" class="w-4 h-4" />
|
|
69
|
+
Convert to Floor Wildcard
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
<!-- CATCH_ALL_FLOOR -->
|
|
73
|
+
<div v-if="filterType === 5">
|
|
74
|
+
<button
|
|
75
|
+
@click.stop="update({ generalAdmission: true, floor: false, buyAny: false })"
|
|
76
|
+
class="conversion-btn"
|
|
77
|
+
>
|
|
78
|
+
<component :is="'GroupIcon'" class="w-4 h-4" />
|
|
79
|
+
Convert to GA Wildcard
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<div v-if="filterBuilder.expandedFilter === filter.id" class="controls-section mt-4 mx-2 sm:mx-0">
|
|
84
|
+
<div class="flex flex-col sm:flex-row flex-wrap gap-3 sm:items-center">
|
|
85
|
+
<div class="flex items-center gap-2 control-group">
|
|
86
|
+
<Checkbox :toggled="filter.exclude || false" @valueUpdate="handleExcludeClick" />
|
|
87
|
+
<label class="text-sm font-medium">Excluded</label>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="flex items-center gap-2 control-group">
|
|
90
|
+
<label class="text-xs text-gray whitespace-nowrap">Min:</label>
|
|
91
|
+
<input
|
|
92
|
+
type="number"
|
|
93
|
+
:value="filter.minPrice"
|
|
94
|
+
@change="
|
|
95
|
+
(e) =>
|
|
96
|
+
update(
|
|
97
|
+
{
|
|
98
|
+
minPrice: e.target.value - 0
|
|
99
|
+
},
|
|
100
|
+
true
|
|
101
|
+
)
|
|
102
|
+
"
|
|
103
|
+
placeholder="0"
|
|
104
|
+
class="filter-input w-16 sm:w-20"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="flex items-center gap-2 control-group">
|
|
108
|
+
<label class="text-xs text-gray whitespace-nowrap">Max:</label>
|
|
109
|
+
<input
|
|
110
|
+
type="number"
|
|
111
|
+
:value="filter.maxPrice"
|
|
112
|
+
@change="
|
|
113
|
+
(e) =>
|
|
114
|
+
update(
|
|
115
|
+
{
|
|
116
|
+
maxPrice: e.target.value - 0
|
|
117
|
+
},
|
|
118
|
+
true
|
|
119
|
+
)
|
|
120
|
+
"
|
|
121
|
+
placeholder="1000"
|
|
122
|
+
class="filter-input w-16 sm:w-20"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="control-group">
|
|
126
|
+
<PriceSortToggle
|
|
127
|
+
:current="filter.priceSort"
|
|
128
|
+
@change="(e) => props.filterBuilder.replaceById(filter.id, { priceSort: e })"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="col-span-3 sm:col-span-2 flex justify-end items-center gap-1 sm:gap-2">
|
|
135
|
+
<div class="drag-handle handle filter-action-btn drag-btn cursor-grab active:cursor-grabbing" title="Drag to reorder">
|
|
136
|
+
<MenuIcon class="w-3 h-3 sm:w-4 sm:h-4" />
|
|
137
|
+
</div>
|
|
138
|
+
<button
|
|
139
|
+
@click="filterBuilder.deleteFilterById(filter.id)"
|
|
140
|
+
class="delete-btn filter-action-btn"
|
|
141
|
+
title="Delete filter"
|
|
142
|
+
>
|
|
143
|
+
<TrashIcon class="w-3 h-3 sm:w-4 sm:h-4" />
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</template>
|
|
149
|
+
|
|
150
|
+
<script setup>
|
|
151
|
+
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
152
|
+
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
153
|
+
import { ref } from "vue";
|
|
154
|
+
|
|
155
|
+
import { useUIStore } from "@/stores/ui";
|
|
156
|
+
const ui = useUIStore();
|
|
157
|
+
|
|
158
|
+
// eslint-disable-next-line no-unused-vars
|
|
159
|
+
import { UpIcon, DownIcon, ReloadIcon, TrashIcon, MenuIcon, WildcardIcon, BoxIcon, GroupIcon, FilterIcon, StadiumIcon } from "@/components/icons";
|
|
160
|
+
|
|
161
|
+
let isAllRows = ref(false);
|
|
162
|
+
|
|
163
|
+
const props = defineProps({
|
|
164
|
+
filter: Object,
|
|
165
|
+
index: Number,
|
|
166
|
+
expandedFilter: String,
|
|
167
|
+
filterBuilder: Object
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const handleFilterClick = (id) => {
|
|
171
|
+
if (props.filterBuilder.expandedFilter === id) {
|
|
172
|
+
props.filterBuilder.setExpandedFilter(null);
|
|
173
|
+
props.filterBuilder.updateCss();
|
|
174
|
+
} else {
|
|
175
|
+
ui.logger.Info("Expanding filter", filter);
|
|
176
|
+
props.filterBuilder.setExpandedFilter(id);
|
|
177
|
+
props.filterBuilder.updateCss();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const filterTypes = props.filterBuilder.filterTypes;
|
|
182
|
+
|
|
183
|
+
const getFilterIcon = () => {
|
|
184
|
+
switch (filterType.value) {
|
|
185
|
+
case filterTypes.CATCH_ALL:
|
|
186
|
+
return WildcardIcon;
|
|
187
|
+
case filterTypes.CATCH_ALL_GA:
|
|
188
|
+
return GroupIcon;
|
|
189
|
+
case filterTypes.CATCH_ALL_FLOOR:
|
|
190
|
+
return BoxIcon;
|
|
191
|
+
case filterTypes.NORMAL:
|
|
192
|
+
case filterTypes.NORMAL_FIRSTROW:
|
|
193
|
+
return StadiumIcon;
|
|
194
|
+
default:
|
|
195
|
+
return FilterIcon;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const getFilterSubtitle = () => {
|
|
200
|
+
switch (filterType.value) {
|
|
201
|
+
case filterTypes.NORMAL: {
|
|
202
|
+
const selectedRowAmount = filter.value?.rows?.length;
|
|
203
|
+
if (filter.value?.rows?.length && selectedRowAmount <= 5) {
|
|
204
|
+
return `Rows: ${filter.value.rows.join(", ")}`;
|
|
205
|
+
} else if (selectedRowAmount > 5) {
|
|
206
|
+
return `${selectedRowAmount} rows selected`;
|
|
207
|
+
}
|
|
208
|
+
return selectedRowAmount === 0 ? "Full section" : null;
|
|
209
|
+
}
|
|
210
|
+
case filterTypes.NORMAL_FIRSTROW:
|
|
211
|
+
return `First row in ${filter.value.section}`;
|
|
212
|
+
case filterTypes.CATCH_ALL:
|
|
213
|
+
return "Matches all tickets";
|
|
214
|
+
case filterTypes.CATCH_ALL_GA:
|
|
215
|
+
return "Matches all GA tickets";
|
|
216
|
+
case filterTypes.CATCH_ALL_FLOOR:
|
|
217
|
+
return "Matches all floor tickets";
|
|
218
|
+
default:
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const getFilterTitle = () => {
|
|
223
|
+
const excluded = filter.value.exclude ? "[BL]" : "";
|
|
224
|
+
switch (filterType.value) {
|
|
225
|
+
case filterTypes.INVALID:
|
|
226
|
+
ui.logger.Error("Invalid:", filter.value);
|
|
227
|
+
return `🟥 INVALID FILTER ${excluded}`;
|
|
228
|
+
case filterTypes.CATCH_ALL:
|
|
229
|
+
return `Catch-All ${excluded}`;
|
|
230
|
+
case filterTypes.CATCH_ALL_GA:
|
|
231
|
+
return `Catch-All (GA) ${excluded}`;
|
|
232
|
+
case filterTypes.GLOBAL_FIRSTROW:
|
|
233
|
+
return `All first rows ${excluded}`;
|
|
234
|
+
case filterTypes.NORMAL_FIRSTROW:
|
|
235
|
+
return `${filter.value.section} (first row) ${excluded}`;
|
|
236
|
+
case filterTypes.NORMAL: {
|
|
237
|
+
const selectedRowAmount = filter.value?.rows?.length;
|
|
238
|
+
const totalRowAmount = document.querySelectorAll(`path[section='${filter.value.section}']`)?.length;
|
|
239
|
+
isAllRows.value = selectedRowAmount === totalRowAmount || !filter.value?.rows?.length;
|
|
240
|
+
|
|
241
|
+
if (isAllRows.value) return `${filter.value.section} (full section) ${excluded}`;
|
|
242
|
+
else if (selectedRowAmount > 5) return `${filter.value.section} (${selectedRowAmount} rows) ${excluded}`;
|
|
243
|
+
else if (selectedRowAmount === 0) return `${filter.value.section} (no rows selected) ${excluded}`;
|
|
244
|
+
else {
|
|
245
|
+
const rows = "(" + filter.value.rows?.join(", ") + ")";
|
|
246
|
+
return `${filter.value.section} ${filter.value.rows?.length ? rows : ""} ${excluded}`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
case filterTypes.CATCH_ALL_FLOOR:
|
|
251
|
+
return `Catch-All (Floor) ${excluded}`;
|
|
252
|
+
default:
|
|
253
|
+
ui.logger.Info("Title: Other:", filter.value);
|
|
254
|
+
return `Other ${filterType.value}`;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const handleExcludeClick = (e) => {
|
|
259
|
+
filter.value = props.filterBuilder.replaceById(filter.value.id, { exclude: e });
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const update = (args, updateAll = false) => {
|
|
263
|
+
ui.logger.Info(`[${props.filter.id}] update`, args, updateAll);
|
|
264
|
+
delete args.id;
|
|
265
|
+
args.event = props.filterBuilder.currentEventId;
|
|
266
|
+
const updated = props.filterBuilder.replaceById(filter.value.id, args);
|
|
267
|
+
if (!updated) return;
|
|
268
|
+
filter.value = updated;
|
|
269
|
+
filterType.value = props.filterBuilder.getFilterType(updated);
|
|
270
|
+
filterTitle.value = getFilterTitle();
|
|
271
|
+
if (updateAll) props.filterBuilder.updateHooks.forEach((fn) => fn());
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
let filter = ref(props.filter);
|
|
275
|
+
let filterType = ref(props.filterBuilder.getFilterType(filter.value));
|
|
276
|
+
let filterTitle = ref(getFilterTitle());
|
|
277
|
+
|
|
278
|
+
props.filterBuilder.onUpdate(() => {
|
|
279
|
+
const newData = props.filterBuilder.filters.find((e) => e.id === filter.value.id) || {};
|
|
280
|
+
ui.logger.Info(`[${props.filter.id}] calling onUpdate`, newData);
|
|
281
|
+
if (typeof newData.maxPrice === "number") update({ maxPrice: newData.maxPrice });
|
|
282
|
+
else {
|
|
283
|
+
filterTitle.value = getFilterTitle();
|
|
284
|
+
}
|
|
285
|
+
// else if (newData.id === filter.value.id) update(newData);
|
|
286
|
+
});
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
<style scoped>
|
|
290
|
+
.filter-card {
|
|
291
|
+
@apply bg-dark-500 border border-dark-550 mb-1 shadow-sm relative;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.filter-card:not(:last-child) {
|
|
295
|
+
border-bottom: 2px solid #1B1C2B;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.filter-type-badge {
|
|
299
|
+
@apply w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-dark-400 flex items-center justify-center flex-shrink-0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.info-row {
|
|
303
|
+
@apply flex justify-between items-center;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.label {
|
|
307
|
+
@apply text-xs text-gray font-medium;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.value {
|
|
311
|
+
@apply text-sm text-white;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.conversion-btn {
|
|
315
|
+
@apply flex items-center gap-2 px-3 py-2 bg-dark-400 hover:bg-dark-300 border border-dark-550 rounded-md text-sm font-medium transition-colors;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.control-group {
|
|
319
|
+
@apply flex items-center gap-2;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.filter-input {
|
|
323
|
+
@apply border border-dark-550 rounded px-2 py-1.5 text-sm text-white focus:outline-none transition-colors;
|
|
324
|
+
background-color: rgba(32, 32, 54, 0.9);
|
|
325
|
+
color: white;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.filter-input:focus {
|
|
329
|
+
border-color: #4A4A61;
|
|
330
|
+
background-color: rgba(45, 47, 74, 0.9);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.filter-input::placeholder {
|
|
334
|
+
color: #9CA3AF;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.filter-action-btn {
|
|
338
|
+
@apply flex items-center justify-center rounded-full transition-all duration-200 border-2 border-transparent;
|
|
339
|
+
width: 28px;
|
|
340
|
+
height: 28px;
|
|
341
|
+
color: #9CA3AF;
|
|
342
|
+
background-color: rgba(32, 32, 54, 0.8);
|
|
343
|
+
backdrop-filter: blur(4px);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@media (min-width: 640px) {
|
|
347
|
+
.filter-action-btn {
|
|
348
|
+
width: 32px;
|
|
349
|
+
height: 32px;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.filter-action-btn:hover {
|
|
354
|
+
color: white;
|
|
355
|
+
transform: scale(1.15);
|
|
356
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.drag-btn:hover {
|
|
360
|
+
background-color: rgba(74, 74, 97, 0.8);
|
|
361
|
+
border-color: #4A4A61;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.delete-btn:hover {
|
|
365
|
+
background-color: rgba(239, 68, 68, 0.8);
|
|
366
|
+
border-color: #F87171;
|
|
367
|
+
color: #F87171;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.drag-handle.sortable-chosen {
|
|
371
|
+
border: 1px solid #4A4A61;
|
|
372
|
+
background-color: rgba(74, 74, 97, 0.2);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.filter-card.sortable-ghost {
|
|
376
|
+
@apply opacity-50;
|
|
377
|
+
border: 1px solid #4A4A61;
|
|
378
|
+
background-color: rgba(74, 74, 97, 0.1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.filter-card.sortable-drag {
|
|
382
|
+
@apply shadow-lg transform rotate-2 scale-105;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.expanded-filter {
|
|
386
|
+
border-left: 4px solid #4A4A61;
|
|
387
|
+
background-color: rgba(27, 28, 43, 0.95);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.expanded-content {
|
|
391
|
+
background-color: rgba(27, 28, 43, 0.95);
|
|
392
|
+
border-radius: 6px;
|
|
393
|
+
padding: 12px;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.controls-section {
|
|
397
|
+
background-color: rgba(27, 28, 43, 0.98);
|
|
398
|
+
border-radius: 6px;
|
|
399
|
+
padding: 12px;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.excluded-filter {
|
|
403
|
+
border-left: 4px solid #EE8282;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.normal-filter {
|
|
407
|
+
border-left: 4px solid transparent;
|
|
408
|
+
}
|
|
409
|
+
</style>
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Modal class="overflow-y-scroll max-w-screen">
|
|
3
|
+
<template #header> Filter JSON data <FilterIcon class="ml-4" /> </template>
|
|
4
|
+
|
|
5
|
+
<div class="my-3">
|
|
6
|
+
<div class="editor-container">
|
|
7
|
+
<div class="editor-wrapper">
|
|
8
|
+
<pre ref="codeDisplay" class="language-json code-highlight"></pre>
|
|
9
|
+
<textarea
|
|
10
|
+
ref="codeEditor"
|
|
11
|
+
v-model="text"
|
|
12
|
+
class="code-editor"
|
|
13
|
+
spellcheck="false"
|
|
14
|
+
@scroll="syncScroll"
|
|
15
|
+
@input="highlightCode"
|
|
16
|
+
@keydown.tab.prevent="handleTab"
|
|
17
|
+
></textarea>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<p class="text-red-400 text-bold mt-2">{{ errorMessage }}</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="ml-auto flex">
|
|
24
|
+
<button
|
|
25
|
+
class="btn-action"
|
|
26
|
+
@click="save()"
|
|
27
|
+
>
|
|
28
|
+
Apply
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<button
|
|
32
|
+
class="btn-action ml-2"
|
|
33
|
+
@click="done()"
|
|
34
|
+
>
|
|
35
|
+
Close
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</Modal>
|
|
39
|
+
</template>
|
|
40
|
+
<style lang="scss" scoped>
|
|
41
|
+
.input-wrapper {
|
|
42
|
+
label {
|
|
43
|
+
@apply flex;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Prism.js syntax highlighting styles */
|
|
48
|
+
.editor-container {
|
|
49
|
+
position: relative;
|
|
50
|
+
min-height: 300px;
|
|
51
|
+
max-height: 500px;
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
background-color: #1a1a2e;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.editor-wrapper {
|
|
59
|
+
position: relative;
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 100%;
|
|
62
|
+
min-height: 300px;
|
|
63
|
+
max-height: 500px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.code-editor {
|
|
67
|
+
width: 100%;
|
|
68
|
+
height: 100%;
|
|
69
|
+
min-height: 300px;
|
|
70
|
+
max-height: 500px;
|
|
71
|
+
background-color: transparent;
|
|
72
|
+
/* Make text completely transparent */
|
|
73
|
+
color: rgba(0, 0, 0, 0);
|
|
74
|
+
caret-color: #e2e8f0;
|
|
75
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Menlo', 'Monaco', 'Courier New', monospace;
|
|
76
|
+
padding: 12px;
|
|
77
|
+
border: none;
|
|
78
|
+
resize: none;
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
line-height: 1.6;
|
|
81
|
+
tab-size: 4;
|
|
82
|
+
outline: none;
|
|
83
|
+
border: 1px solid #2d2d3b;
|
|
84
|
+
border-radius: 8px;
|
|
85
|
+
z-index: 10;
|
|
86
|
+
position: absolute;
|
|
87
|
+
top: 0;
|
|
88
|
+
left: 0;
|
|
89
|
+
right: 0;
|
|
90
|
+
bottom: 0;
|
|
91
|
+
white-space: pre;
|
|
92
|
+
overflow: auto;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Enhance the text selection color */
|
|
96
|
+
.code-editor::selection {
|
|
97
|
+
background: rgba(98, 114, 164, 0.4);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.code-highlight {
|
|
101
|
+
width: 100%;
|
|
102
|
+
height: 100%;
|
|
103
|
+
min-height: 300px;
|
|
104
|
+
max-height: 500px;
|
|
105
|
+
overflow: auto;
|
|
106
|
+
white-space: pre;
|
|
107
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Menlo', 'Monaco', 'Courier New', monospace;
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
line-height: 1.6;
|
|
110
|
+
background-color: transparent !important;
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
z-index: 5;
|
|
113
|
+
position: absolute;
|
|
114
|
+
top: 0;
|
|
115
|
+
left: 0;
|
|
116
|
+
right: 0;
|
|
117
|
+
bottom: 0;
|
|
118
|
+
padding: 12px;
|
|
119
|
+
margin: 0;
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
122
|
+
<script setup>
|
|
123
|
+
import Modal from "@/components/ui/Modal.vue";
|
|
124
|
+
import { FilterIcon } from "@/components/icons";
|
|
125
|
+
import { useUIStore } from "@/stores/ui";
|
|
126
|
+
import { ref, computed, onMounted, nextTick } from "vue";
|
|
127
|
+
|
|
128
|
+
const props = defineProps({
|
|
129
|
+
filter: Object
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const ui = useUIStore();
|
|
133
|
+
const text = ref(JSON.stringify(props.filter.out(), null, 4));
|
|
134
|
+
const codeEditor = ref(null);
|
|
135
|
+
const codeDisplay = ref(null);
|
|
136
|
+
|
|
137
|
+
// Function to highlight code using Prism
|
|
138
|
+
const highlightCode = () => {
|
|
139
|
+
if (!codeDisplay.value || !codeEditor.value) return;
|
|
140
|
+
|
|
141
|
+
// Ensure Prism is available
|
|
142
|
+
if (typeof Prism === 'undefined') {
|
|
143
|
+
console.error('Prism is not loaded');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Use requestAnimationFrame for smoother updates
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
try {
|
|
150
|
+
// Update the pre element with highlighted HTML
|
|
151
|
+
const highlighted = Prism.highlight(
|
|
152
|
+
text.value || '',
|
|
153
|
+
Prism.languages.json,
|
|
154
|
+
'json'
|
|
155
|
+
);
|
|
156
|
+
codeDisplay.value.innerHTML = highlighted;
|
|
157
|
+
codeDisplay.value.className = 'language-json code-highlight';
|
|
158
|
+
} catch (e) {
|
|
159
|
+
console.error("Highlight error:", e);
|
|
160
|
+
// Fallback to plain text if highlighting fails
|
|
161
|
+
codeDisplay.value.textContent = text.value || '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Ensure scroll positions are synced after highlighting
|
|
165
|
+
syncScroll();
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Function to sync scrolling between textarea and highlighted code
|
|
170
|
+
const syncScroll = () => {
|
|
171
|
+
if (!codeDisplay.value || !codeEditor.value) return;
|
|
172
|
+
|
|
173
|
+
// Synchronize scrolling between the textarea and the highlighted code
|
|
174
|
+
requestAnimationFrame(() => {
|
|
175
|
+
codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
|
|
176
|
+
codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Function to handle tab key press in the editor
|
|
181
|
+
const handleTab = (e) => {
|
|
182
|
+
const textarea = codeEditor.value;
|
|
183
|
+
const start = textarea.selectionStart;
|
|
184
|
+
const end = textarea.selectionEnd;
|
|
185
|
+
|
|
186
|
+
// Insert 4 spaces at cursor position
|
|
187
|
+
const spaces = ' ';
|
|
188
|
+
text.value = text.value.substring(0, start) + spaces + text.value.substring(end);
|
|
189
|
+
|
|
190
|
+
// Move cursor position after the inserted tab
|
|
191
|
+
nextTick(() => {
|
|
192
|
+
textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
|
|
193
|
+
highlightCode();
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Initialize syntax highlighting
|
|
198
|
+
onMounted(() => {
|
|
199
|
+
// Apply highlighting when the component is mounted
|
|
200
|
+
nextTick(() => {
|
|
201
|
+
highlightCode();
|
|
202
|
+
|
|
203
|
+
// Ensure scroll synchronization on initial load
|
|
204
|
+
if (codeEditor.value && codeDisplay.value) {
|
|
205
|
+
syncScroll();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Focus the editor
|
|
209
|
+
if (codeEditor.value) {
|
|
210
|
+
codeEditor.value.focus();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
function done() {
|
|
216
|
+
ui.toggleModal("");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function save() {
|
|
220
|
+
const out = JSON.parse(text.value);
|
|
221
|
+
props.filter.reset();
|
|
222
|
+
props.filter.globalFilter = out.globalFilter;
|
|
223
|
+
props.filter.filters = out.filters;
|
|
224
|
+
props.filter.updateCss();
|
|
225
|
+
done();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const errorMessage = computed(() => {
|
|
229
|
+
try {
|
|
230
|
+
JSON.stringify(JSON.parse(text.value), null, 4);
|
|
231
|
+
return "";
|
|
232
|
+
} catch (e) {
|
|
233
|
+
return e.message;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
</script>
|