@necrolab/dashboard 0.4.221 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +27 -1
- package/.vscode/extensions.json +1 -1
- package/README.md +64 -2
- package/artwork/image.png +0 -0
- package/backend/api.js +26 -24
- package/backend/auth.js +2 -2
- package/backend/batching.js +1 -1
- package/backend/endpoints.js +8 -11
- package/backend/index.js +2 -2
- package/backend/mock-data.js +27 -36
- package/backend/mock-src/classes/logger.js +5 -7
- package/backend/mock-src/classes/utils.js +3 -2
- package/backend/mock-src/ticketmaster.js +4 -4
- package/backend/validator.js +2 -2
- package/config/configs.json +0 -1
- package/dev-server.js +134 -0
- package/exit +209 -0
- package/index.html +78 -8
- package/index.js +1 -1
- package/jsconfig.json +16 -0
- package/package.json +39 -25
- package/postcss.config.js +1 -1
- package/postinstall.js +124 -20
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/necro_logo.png +0 -0
- package/public/manifest.json +16 -10
- package/run +176 -9
- package/src/App.vue +498 -85
- package/src/assets/css/base/reset.scss +43 -0
- package/src/assets/css/base/scroll.scss +114 -0
- package/src/assets/css/base/typography.scss +37 -0
- package/src/assets/css/components/buttons.scss +216 -0
- package/src/assets/css/components/forms.scss +221 -0
- package/src/assets/css/components/modals.scss +13 -0
- package/src/assets/css/components/tables.scss +27 -0
- package/src/assets/css/components/toasts.scss +100 -0
- package/src/assets/css/main.scss +201 -122
- package/src/assets/img/background.svg +2 -2
- package/src/assets/img/background.svg.backup +11 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/components/Auth/LoginForm.vue +62 -11
- package/src/components/Editors/Account/Account.vue +116 -40
- package/src/components/Editors/Account/AccountCreator.vue +88 -39
- package/src/components/Editors/Account/AccountView.vue +102 -34
- package/src/components/Editors/Account/CreateAccount.vue +80 -32
- package/src/components/Editors/Profile/CreateProfile.vue +269 -83
- package/src/components/Editors/Profile/Profile.vue +132 -47
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
- package/src/components/Editors/Profile/ProfileView.vue +89 -32
- package/src/components/Editors/TagLabel.vue +67 -6
- package/src/components/Editors/TagToggle.vue +7 -2
- package/src/components/Filter/Filter.vue +288 -71
- package/src/components/Filter/FilterPreview.vue +202 -31
- package/src/components/Filter/PriceSortToggle.vue +76 -6
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Row.vue +1 -1
- package/src/components/Table/Table.vue +19 -2
- package/src/components/Tasks/CheckStock.vue +6 -8
- package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
- package/src/components/Tasks/Controls/MobileControls.vue +8 -45
- package/src/components/Tasks/CreateTaskAXS.vue +80 -72
- package/src/components/Tasks/CreateTaskTM.vue +95 -141
- package/src/components/Tasks/MassEdit.vue +4 -6
- package/src/components/Tasks/QuickSettings.vue +199 -30
- package/src/components/Tasks/ScrapeVenue.vue +5 -6
- package/src/components/Tasks/Stats.vue +50 -24
- package/src/components/Tasks/Task.vue +384 -179
- package/src/components/Tasks/TaskLabel.vue +2 -2
- package/src/components/Tasks/TaskView.vue +136 -48
- package/src/components/Tasks/Utilities.vue +25 -10
- package/src/components/Tasks/ViewTask.vue +321 -0
- package/src/components/icons/Bag.vue +1 -1
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Eye.vue +6 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Loyalty.vue +1 -1
- package/src/components/icons/Mail.vue +2 -2
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +2 -2
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +4 -5
- package/src/components/icons/Sandclock.vue +2 -2
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +1 -1
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +26 -1
- package/src/components/ui/Modal.vue +107 -13
- package/src/components/ui/Navbar.vue +175 -40
- package/src/components/ui/ReconnectIndicator.vue +351 -55
- package/src/components/ui/Splash.vue +5 -35
- package/src/components/ui/controls/CountryChooser.vue +200 -62
- package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
- package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
- package/src/components/ui/controls/atomic/Switch.vue +53 -25
- package/src/composables/useClickOutside.js +21 -0
- package/src/composables/useDropdownPosition.js +174 -0
- package/src/libs/Filter.js +60 -24
- package/src/registerServiceWorker.js +1 -1
- package/src/stores/connection.js +4 -4
- package/src/stores/sampleData.js +172 -199
- package/src/stores/ui.js +55 -20
- package/src/stores/utils.js +30 -4
- package/src/types/index.js +41 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +116 -50
- package/src/views/Console.vue +394 -77
- package/src/views/Editor.vue +1176 -123
- package/src/views/FilterBuilder.vue +528 -250
- package/src/views/Login.vue +76 -14
- package/src/views/Profiles.vue +119 -34
- package/src/views/Tasks.vue +266 -98
- package/static/offline.html +192 -50
- package/switch-branch.sh +41 -0
- package/tailwind.config.js +119 -27
- package/vite.config.js +73 -16
- package/workbox-config.cjs +63 -0
- package/ICONS.md +0 -21
- package/public/img/background.svg +0 -14
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/src/assets/css/_input.scss +0 -143
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/vue.config.js +0 -32
- package/workbox-config.js +0 -7
|
@@ -1,64 +1,64 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Row
|
|
3
|
-
class="relative
|
|
3
|
+
class="relative grid-cols-7 text-white lg:grid-cols-8"
|
|
4
4
|
@click="ui.setOpenContextMenu('')"
|
|
5
|
-
@click.right.prevent="ui.setOpenContextMenu('')"
|
|
6
|
-
|
|
7
|
-
<div class="col-span-3 lg:col-span-2 flex">
|
|
5
|
+
@click.right.prevent="ui.setOpenContextMenu('')">
|
|
6
|
+
<div class="col-span-3 flex lg:col-span-2">
|
|
8
7
|
<Checkbox
|
|
9
8
|
class="ml-0 mr-4"
|
|
10
|
-
:toggled="props.
|
|
11
|
-
@valueUpdate="ui.toggleProfileSelected(props.
|
|
12
|
-
/>
|
|
9
|
+
:toggled="props.profile.selected"
|
|
10
|
+
@valueUpdate="ui.toggleProfileSelected(props.profile.id)" />
|
|
13
11
|
<h4 class="mx-auto text-white">
|
|
14
|
-
{{ props.
|
|
12
|
+
{{ props.profile.profileName }}
|
|
15
13
|
</h4>
|
|
16
14
|
</div>
|
|
17
15
|
<div class="col-span-1 lg:col-span-2">
|
|
18
|
-
<h4 class="
|
|
19
|
-
<span class="hidden
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
<h4 class="flex items-center justify-center gap-2 text-white">
|
|
17
|
+
<span class="hidden sm:block">
|
|
18
|
+
{{
|
|
19
|
+
props.profile.privacy
|
|
20
|
+
? props.profile.cardNumber[0] +
|
|
21
|
+
"•".repeat(props.profile.cardNumber.length - 5) +
|
|
22
|
+
props.profile.cardNumber.slice(-4)
|
|
23
|
+
: validateCard(props.profile.cardNumber).formatted
|
|
24
|
+
}}
|
|
25
|
+
</span>
|
|
26
|
+
<img class="h-6 w-6" :src="getAccountType()" />
|
|
27
27
|
</h4>
|
|
28
28
|
</div>
|
|
29
29
|
<div class="col-span-1">
|
|
30
30
|
<h4 class="text-white">{{ expDate() }}</h4>
|
|
31
31
|
</div>
|
|
32
32
|
<div class="col-span-1">
|
|
33
|
-
<h4 v-if="props.
|
|
34
|
-
<img
|
|
33
|
+
<h4 v-if="props.profile.enabled" class="flex justify-center text-green-400">
|
|
34
|
+
<img class="green h-3 w-3" src="/img/controls/enable.svg" />
|
|
35
35
|
</h4>
|
|
36
|
-
<h4 v-else class="text-red-400
|
|
37
|
-
<img
|
|
36
|
+
<h4 v-else class="flex justify-center text-red-400">
|
|
37
|
+
<img class="h-3 w-3 fill-red-400" src="/img/close.svg" />
|
|
38
38
|
</h4>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
<div class="col-span-1 hidden lg:block">
|
|
42
|
-
<h4 class="
|
|
43
|
-
<TagLabel v-for="tag in props.
|
|
42
|
+
<h4 class="flex justify-center gap-1 text-white">
|
|
43
|
+
<TagLabel v-for="tag in props.profile.tags" :key="tag" :text="tag" />
|
|
44
44
|
</h4>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<div class="col-span-1 flex">
|
|
48
|
-
<ul class="
|
|
48
|
+
<ul class="profile-buttons">
|
|
49
49
|
<li>
|
|
50
50
|
<button @click="edit">
|
|
51
51
|
<EditIcon />
|
|
52
52
|
</button>
|
|
53
53
|
</li>
|
|
54
|
-
<li v-if="props.
|
|
54
|
+
<li v-if="props.profile.enabled">
|
|
55
55
|
<button @click="disable">
|
|
56
|
-
<img
|
|
56
|
+
<img class="h-4 w-4" src="/img/controls/disable.svg" />
|
|
57
57
|
</button>
|
|
58
58
|
</li>
|
|
59
59
|
<li v-else>
|
|
60
60
|
<button @click="enable">
|
|
61
|
-
<img
|
|
61
|
+
<img class="h-4 w-4" src="/img/controls/enable.svg" />
|
|
62
62
|
</button>
|
|
63
63
|
</li>
|
|
64
64
|
<li>
|
|
@@ -76,28 +76,111 @@
|
|
|
76
76
|
h4 {
|
|
77
77
|
@apply text-center;
|
|
78
78
|
}
|
|
79
|
-
.
|
|
80
|
-
@apply
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
.profile-buttons {
|
|
80
|
+
@apply mx-auto flex items-center justify-center rounded border border-dark-650 bg-dark-500;
|
|
81
|
+
padding: 3px;
|
|
82
|
+
gap: 2px;
|
|
83
|
+
|
|
84
|
+
button {
|
|
85
|
+
@apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
|
|
86
|
+
background: transparent;
|
|
87
|
+
width: 28px;
|
|
88
|
+
height: 28px;
|
|
89
|
+
color: oklch(0.82 0 0);
|
|
90
|
+
|
|
91
|
+
&:hover {
|
|
92
|
+
background: rgba(255, 255, 255, 0.1);
|
|
93
|
+
color: #ffffff;
|
|
94
|
+
transform: scale(1.05);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&:active {
|
|
98
|
+
background: rgba(255, 255, 255, 0.2);
|
|
99
|
+
transform: scale(0.95);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
svg,
|
|
104
|
+
img {
|
|
105
|
+
width: 16px;
|
|
106
|
+
height: 16px;
|
|
107
|
+
position: relative;
|
|
108
|
+
z-index: 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
svg path {
|
|
112
|
+
fill: currentColor;
|
|
84
113
|
}
|
|
85
114
|
}
|
|
86
115
|
|
|
116
|
+
// Tablet optimization
|
|
87
117
|
@media (max-width: 1024px) {
|
|
88
118
|
h4 {
|
|
89
119
|
font-size: 10px !important;
|
|
90
120
|
}
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
|
|
122
|
+
.profile-buttons {
|
|
123
|
+
padding: 3px;
|
|
124
|
+
gap: 2px;
|
|
125
|
+
|
|
126
|
+
button {
|
|
127
|
+
width: 26px;
|
|
128
|
+
height: 26px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
svg,
|
|
132
|
+
img {
|
|
133
|
+
width: 14px;
|
|
134
|
+
height: 14px;
|
|
135
|
+
}
|
|
93
136
|
}
|
|
94
|
-
|
|
137
|
+
|
|
138
|
+
.profile-id {
|
|
95
139
|
font-size: 6px !important;
|
|
96
140
|
margin-right: -12px;
|
|
97
141
|
margin-top: 20px;
|
|
98
142
|
}
|
|
99
|
-
|
|
100
|
-
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Mobile optimization
|
|
146
|
+
@media (max-width: 768px) {
|
|
147
|
+
.profile-buttons {
|
|
148
|
+
padding: 2px;
|
|
149
|
+
gap: 1px;
|
|
150
|
+
|
|
151
|
+
button {
|
|
152
|
+
width: 22px;
|
|
153
|
+
height: 22px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
svg,
|
|
157
|
+
img {
|
|
158
|
+
width: 12px;
|
|
159
|
+
height: 12px;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// iPhone vertical (portrait) specific
|
|
165
|
+
@media (max-width: 480px) and (orientation: portrait) {
|
|
166
|
+
.profile-buttons {
|
|
167
|
+
padding: 2px;
|
|
168
|
+
gap: 1px;
|
|
169
|
+
|
|
170
|
+
button {
|
|
171
|
+
width: 18px;
|
|
172
|
+
height: 18px;
|
|
173
|
+
|
|
174
|
+
&:hover {
|
|
175
|
+
transform: scale(1.1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
svg,
|
|
180
|
+
img {
|
|
181
|
+
width: 10px;
|
|
182
|
+
height: 10px;
|
|
183
|
+
}
|
|
101
184
|
}
|
|
102
185
|
}
|
|
103
186
|
</style>
|
|
@@ -107,28 +190,30 @@ import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/compon
|
|
|
107
190
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
108
191
|
import { useUIStore } from "@/stores/ui";
|
|
109
192
|
import { validateCard } from "@/stores/utils";
|
|
110
|
-
import TagLabel from "@/components/
|
|
193
|
+
import TagLabel from "@/components/Editors/TagLabel.vue";
|
|
111
194
|
|
|
112
195
|
const ui = useUIStore();
|
|
113
196
|
|
|
114
197
|
const props = defineProps({
|
|
115
|
-
|
|
198
|
+
profile: { type: Object }
|
|
116
199
|
});
|
|
117
200
|
|
|
118
201
|
const getAccountType = () => {
|
|
119
|
-
var cn = props.
|
|
120
|
-
if (cn.startsWith("4"))
|
|
121
|
-
|
|
202
|
+
var cn = props.profile.cardNumber;
|
|
203
|
+
if (cn.startsWith("4"))
|
|
204
|
+
return `/img/banks/visa.svg`; // visa
|
|
205
|
+
else if (cn.startsWith("3"))
|
|
206
|
+
return `/img/banks/amex.svg`; // amex
|
|
122
207
|
else if (cn.startsWith("5")) return `/img/banks/mastercard.svg`; // master
|
|
123
208
|
};
|
|
124
209
|
|
|
125
210
|
const expDate = () =>
|
|
126
|
-
props.
|
|
127
|
-
const enable = async () => await ui.addProfile({ ...props.
|
|
128
|
-
const disable = async () => await ui.addProfile({ ...props.
|
|
211
|
+
props.profile.privacy ? "••/••" : `${props.profile.expMonth}/${props.profile.expYear?.replace("20", "")}`;
|
|
212
|
+
const enable = async () => await ui.addProfile({ ...props.profile, enabled: true });
|
|
213
|
+
const disable = async () => await ui.addProfile({ ...props.profile, enabled: false });
|
|
129
214
|
const edit = () => {
|
|
130
|
-
ui.currentlyEditing = props.
|
|
215
|
+
ui.currentlyEditing = props.profile;
|
|
131
216
|
ui.toggleModal("create-profile");
|
|
132
217
|
};
|
|
133
|
-
const deleteProfile = async () => await ui.deleteProfile(props.
|
|
218
|
+
const deleteProfile = async () => await ui.deleteProfile(props.profile.id);
|
|
134
219
|
</script>
|
|
@@ -1,43 +1,75 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown rounded-lg">
|
|
4
|
-
<span @click="
|
|
3
|
+
<div class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown rounded-lg" ref="dropdownRef">
|
|
4
|
+
<span @click="toggleOpen" class="flex justify-between items-center z-50 text-white">
|
|
5
5
|
<div class="flex gap-3 justify-center">
|
|
6
6
|
<img class="w-5" :src="`/flags/${current?.toLowerCase()}.svg`" />
|
|
7
7
|
</div>
|
|
8
8
|
</span>
|
|
9
|
-
<
|
|
10
|
-
v-if="open && !disabled"
|
|
11
|
-
class="dropdown-content special-dropdown snap-mandatory snap-y z-inf max-h-48 overflow-scroll hidden-scrollbars"
|
|
12
|
-
>
|
|
9
|
+
<Teleport to="body">
|
|
13
10
|
<div
|
|
14
|
-
v-
|
|
15
|
-
|
|
16
|
-
:
|
|
17
|
-
@click
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
v-if="open && !disabled"
|
|
12
|
+
class="dropdown-content-portal special-dropdown"
|
|
13
|
+
:style="menuStyle"
|
|
14
|
+
@click.stop
|
|
15
|
+
@wheel.stop
|
|
16
|
+
@touchmove.stop>
|
|
17
|
+
<div
|
|
18
|
+
v-for="(country, i) in countries"
|
|
19
|
+
v-bind:key="country"
|
|
20
|
+
:class="`cursor-pointer w-12 ${i === 0 ? '' : 'my-2'}`"
|
|
21
|
+
@click="set(country)">
|
|
22
|
+
<div class="flex justify-center items-center smooth-hover">
|
|
23
|
+
<span class="text-sm">{{ country }}</span>
|
|
24
|
+
<img class="w-5 ml-3" :src="`/flags/${country?.toLowerCase()}.svg`" />
|
|
25
|
+
</div>
|
|
22
26
|
</div>
|
|
23
27
|
</div>
|
|
24
|
-
</
|
|
28
|
+
</Teleport>
|
|
25
29
|
</div>
|
|
26
30
|
</div>
|
|
27
31
|
</template>
|
|
28
32
|
|
|
29
33
|
<script setup>
|
|
30
34
|
import { ref, watch } from "vue";
|
|
35
|
+
import { useDropdownPosition } from "@/composables/useDropdownPosition";
|
|
36
|
+
import { useClickOutside } from "@/composables/useClickOutside";
|
|
37
|
+
|
|
31
38
|
const countries = ["US", "CA", "DE", "FR", "DK"];
|
|
32
39
|
const open = ref(false);
|
|
40
|
+
const dropdownRef = ref(null);
|
|
33
41
|
|
|
34
42
|
const props = defineProps({
|
|
35
43
|
value: { type: String },
|
|
36
44
|
onClick: { type: Function },
|
|
37
45
|
disabled: { type: Boolean, required: false }
|
|
38
46
|
});
|
|
47
|
+
|
|
39
48
|
const current = ref(props.value || "US");
|
|
40
49
|
|
|
50
|
+
// Use composables for positioning and click outside
|
|
51
|
+
const { menuStyle, updatePosition } = useDropdownPosition(dropdownRef, {
|
|
52
|
+
offset: { x: -17.6, y: 4 },
|
|
53
|
+
minWidth: 80,
|
|
54
|
+
maxHeight: 192,
|
|
55
|
+
zIndex: 99999999999999,
|
|
56
|
+
estimateHeight: () => Math.min(countries.length * 32, 192)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
useClickOutside(dropdownRef, () => {
|
|
60
|
+
if (open.value) {
|
|
61
|
+
open.value = false;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const toggleOpen = () => {
|
|
66
|
+
if (props.disabled) return;
|
|
67
|
+
open.value = !open.value;
|
|
68
|
+
if (open.value) {
|
|
69
|
+
updatePosition();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
41
73
|
const set = (c) => {
|
|
42
74
|
if (props.disabled) return;
|
|
43
75
|
current.value = c;
|
|
@@ -57,19 +89,49 @@ watch(
|
|
|
57
89
|
}
|
|
58
90
|
|
|
59
91
|
.small-dropdown {
|
|
92
|
+
@apply h-10 !important;
|
|
60
93
|
background-clip: border-box !important;
|
|
61
94
|
/* border-radius: 100% !important; */
|
|
62
95
|
padding: 0;
|
|
63
96
|
width: 3em !important;
|
|
64
|
-
/* height: 3em !important; */
|
|
65
97
|
display: flex;
|
|
66
98
|
justify-items: center;
|
|
67
99
|
justify-content: center;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
102
|
+
@media (min-width: 768px) {
|
|
103
|
+
.small-dropdown {
|
|
104
|
+
height: 40px !important; /* Match input-default responsive height */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.dropdown-content-portal {
|
|
109
|
+
@apply bg-dark-400 border border-dark-650 rounded-lg shadow-2xl z-50;
|
|
110
|
+
padding: 0.5rem;
|
|
111
|
+
max-height: 192px !important;
|
|
112
|
+
overflow-y: auto !important;
|
|
113
|
+
overscroll-behavior: contain !important;
|
|
114
|
+
touch-action: pan-y !important;
|
|
115
|
+
-webkit-overflow-scrolling: touch !important;
|
|
116
|
+
scrollbar-width: none;
|
|
117
|
+
-ms-overflow-style: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.dropdown-content-portal::-webkit-scrollbar {
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.dropdown-content-portal > div {
|
|
125
|
+
@apply px-3 py-2 text-sm text-white cursor-pointer;
|
|
126
|
+
border-radius: 6px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.dropdown-content-portal > div:hover {
|
|
130
|
+
/* Removed hover background */
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.dropdown-content-portal > div .flex {
|
|
134
|
+
@apply items-center justify-center;
|
|
135
|
+
gap: 0.75rem;
|
|
73
136
|
}
|
|
74
137
|
</style>
|
|
75
|
-
, watch
|
|
@@ -1,37 +1,36 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Table>
|
|
3
|
-
<Header class="
|
|
4
|
-
<div class="col-span-3 lg:col-span-2
|
|
3
|
+
<Header class="grid-cols-7 text-center lg:grid-cols-8">
|
|
4
|
+
<div class="col-span-3 flex lg:col-span-2">
|
|
5
5
|
<Checkbox
|
|
6
6
|
class="mr-3"
|
|
7
7
|
:toggled="ui.mainCheckbox.profiles"
|
|
8
8
|
@valueUpdate="ui.toggleMainCheckbox('profiles')"
|
|
9
|
-
|
|
9
|
+
:isHeader="true" />
|
|
10
10
|
<div class="mx-auto flex items-center" @click="ui.toggleSort('eventId')">
|
|
11
|
-
<
|
|
12
|
-
<h4 class="hidden
|
|
13
|
-
<!-- <DownIcon v-if="ui.sortData.sortBy === 'eventId'" class="ml-1" /> -->
|
|
11
|
+
<ProfileIcon class="mr-0 h-4 w-4 md:mr-3" />
|
|
12
|
+
<h4 class="hidden md:flex">Profile Name</h4>
|
|
14
13
|
</div>
|
|
15
14
|
</div>
|
|
16
|
-
<div class="
|
|
17
|
-
<CartIcon class="mr-0
|
|
18
|
-
<h4 class="hidden
|
|
15
|
+
<div class="col-span-1 flex items-center justify-center lg:col-span-2" v-once>
|
|
16
|
+
<CartIcon class="mr-0 h-4 w-4 md:mr-3" />
|
|
17
|
+
<h4 class="hidden md:flex">Card Number</h4>
|
|
19
18
|
</div>
|
|
20
19
|
<div class="col-span-1 flex items-center justify-center" v-once>
|
|
21
|
-
<TimerIcon class="mr-0
|
|
22
|
-
<h4 class="hidden
|
|
20
|
+
<TimerIcon class="mr-0 h-4 w-4 md:mr-3" />
|
|
21
|
+
<h4 class="hidden md:flex">Expiration</h4>
|
|
23
22
|
</div>
|
|
24
23
|
<div class="col-span-1 flex items-center justify-center" v-once>
|
|
25
|
-
<
|
|
26
|
-
<h4 class="hidden
|
|
24
|
+
<CheckmarkIcon class="mr-0 h-4 w-4 md:mr-3" />
|
|
25
|
+
<h4 class="hidden md:flex">Enabled</h4>
|
|
27
26
|
</div>
|
|
28
|
-
<div class="col-span-1 hidden
|
|
29
|
-
<TicketIcon class="mr-0 lg:mr-3" />
|
|
27
|
+
<div class="col-span-1 hidden items-center justify-center lg:flex" v-once>
|
|
28
|
+
<TicketIcon class="mr-0 h-4 w-4 lg:mr-3" />
|
|
30
29
|
<h4 class="hidden lg:flex">Tags</h4>
|
|
31
30
|
</div>
|
|
32
31
|
<div class="col-span-1 flex items-center justify-center" v-once>
|
|
33
|
-
<ClickIcon class="mr-0
|
|
34
|
-
<h4 class="hidden
|
|
32
|
+
<ClickIcon class="mr-0 h-4 w-4 md:mr-3" />
|
|
33
|
+
<h4 class="hidden md:flex">Actions</h4>
|
|
35
34
|
</div>
|
|
36
35
|
</Header>
|
|
37
36
|
<div v-if="toRender.length != 0">
|
|
@@ -39,26 +38,27 @@
|
|
|
39
38
|
:items="toRender"
|
|
40
39
|
:item-size="64"
|
|
41
40
|
key-field="index"
|
|
42
|
-
class="scroller
|
|
43
|
-
|
|
41
|
+
class="scroller vue-recycle-scroller ready direction-vertical hidden-scrollbars stop-pan flex flex-col divide-y divide-dark-650 overflow-y-auto overflow-x-hidden"
|
|
42
|
+
:style="{ maxHeight: dynamicTableHeight }">
|
|
44
43
|
<template #default="props">
|
|
45
|
-
<div class="
|
|
44
|
+
<div class="profile" :key="`profile-${props.item.id || props.item.index}`">
|
|
46
45
|
<Profile
|
|
47
46
|
@click="i[props.item.index]++"
|
|
48
|
-
:class="
|
|
49
|
-
:
|
|
50
|
-
/>
|
|
47
|
+
:class="props.item.index % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
|
|
48
|
+
:profile="props.item" />
|
|
51
49
|
</div>
|
|
52
50
|
</template>
|
|
53
51
|
</RecycleScroller>
|
|
54
52
|
</div>
|
|
55
|
-
<div v-else class="flex
|
|
56
|
-
|
|
53
|
+
<div v-else class="empty-state flex flex-col items-center justify-center bg-dark-400 py-8 text-center">
|
|
54
|
+
<ProfileIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
|
|
55
|
+
<p class="text-sm text-light-400">No profiles found</p>
|
|
56
|
+
<p class="mt-1 text-xs text-light-500">Create profiles to get started</p>
|
|
57
57
|
</div>
|
|
58
58
|
</Table>
|
|
59
59
|
</template>
|
|
60
60
|
<style lang="scss" scoped>
|
|
61
|
-
.
|
|
61
|
+
.profile {
|
|
62
62
|
height: 64px;
|
|
63
63
|
}
|
|
64
64
|
h4 {
|
|
@@ -73,25 +73,82 @@ h4 {
|
|
|
73
73
|
max-height: calc(100vh - 20rem);
|
|
74
74
|
overflow: hidden;
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
.empty-state {
|
|
78
|
+
color: #969696;
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
}
|
|
76
82
|
</style>
|
|
77
83
|
<script setup>
|
|
78
84
|
import { Table, Header } from "@/components/Table";
|
|
79
|
-
import { CartIcon, TicketIcon, ClickIcon, TimerIcon } from "@/components/icons";
|
|
85
|
+
import { CartIcon, TicketIcon, ClickIcon, TimerIcon, ProfileIcon, CheckmarkIcon } from "@/components/icons";
|
|
80
86
|
import Profile from "./Profile.vue";
|
|
81
87
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
82
88
|
import { useUIStore } from "@/stores/ui";
|
|
83
|
-
import { computed, ref } from "vue";
|
|
89
|
+
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
84
90
|
|
|
85
91
|
const props = defineProps({
|
|
86
|
-
|
|
92
|
+
profiles: { type: Object }
|
|
87
93
|
});
|
|
88
|
-
const i = ref({});
|
|
89
|
-
props.tasks.forEach((t) => (i.value[t._id] = 0));
|
|
90
94
|
|
|
91
95
|
const ui = useUIStore();
|
|
92
96
|
|
|
97
|
+
const i = ref({});
|
|
93
98
|
const toRender = computed(() => {
|
|
94
99
|
let c = 0;
|
|
95
|
-
|
|
100
|
+
const rendered = props.profiles.map((t) => ({ ...t, index: c++ }));
|
|
101
|
+
|
|
102
|
+
// Initialize reactive refs for click tracking
|
|
103
|
+
rendered.forEach((t) => {
|
|
104
|
+
if (t.id && !(t.id in i.value)) {
|
|
105
|
+
i.value[t.id] = 0;
|
|
106
|
+
}
|
|
107
|
+
if (!(t.index in i.value)) {
|
|
108
|
+
i.value[t.index] = 0;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return rendered;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Dynamic height calculation for perfect item fitting
|
|
116
|
+
const windowHeight = ref(window.innerHeight);
|
|
117
|
+
const windowWidth = ref(window.innerWidth);
|
|
118
|
+
|
|
119
|
+
const updateDimensions = () => {
|
|
120
|
+
windowHeight.value = window.innerHeight;
|
|
121
|
+
windowWidth.value = window.innerWidth;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
onMounted(() => {
|
|
125
|
+
window.addEventListener("resize", updateDimensions);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
onUnmounted(() => {
|
|
129
|
+
window.removeEventListener("resize", updateDimensions);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const dynamicTableHeight = computed(() => {
|
|
133
|
+
// Calculate available space for profiles table with conservative buffer
|
|
134
|
+
const headerHeight = 60; // Header + navbar
|
|
135
|
+
const titleHeight = 50; // Profiles title and controls
|
|
136
|
+
const searchHeight = 50; // Search and filter controls
|
|
137
|
+
const margins = windowWidth.value >= 1024 ? 40 : 25;
|
|
138
|
+
const bufferSpace = 50; // Conservative buffer to prevent partial items
|
|
139
|
+
|
|
140
|
+
const totalUsedSpace = headerHeight + titleHeight + searchHeight + margins + bufferSpace;
|
|
141
|
+
const availableHeight = windowHeight.value - totalUsedSpace;
|
|
142
|
+
|
|
143
|
+
// Profile row height is always 64px
|
|
144
|
+
const rowHeight = 64;
|
|
145
|
+
const minRowsToShow = 2;
|
|
146
|
+
const minHeight = minRowsToShow * rowHeight;
|
|
147
|
+
|
|
148
|
+
// Calculate exact number of complete rows that fit with conservative approach
|
|
149
|
+
const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
|
|
150
|
+
const exactHeight = maxCompleteRows * rowHeight;
|
|
151
|
+
|
|
152
|
+
return exactHeight + "px";
|
|
96
153
|
});
|
|
97
154
|
</script>
|
|
@@ -1,16 +1,77 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<span class="
|
|
2
|
+
<div class="tag-pill">
|
|
3
|
+
<span class="tag-text">{{ formatText(props.text) }}</span>
|
|
4
4
|
</div>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
|
-
<style scoped>
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
<style lang="scss" scoped>
|
|
8
|
+
.tag-pill {
|
|
9
|
+
@apply inline-flex items-center justify-center rounded-md transition-all duration-200;
|
|
10
|
+
background: linear-gradient(145deg, oklch(0.26 0 0), oklch(0.22 0 0));
|
|
11
|
+
border: 1px solid oklch(0.31 0 0);
|
|
12
|
+
padding: 0.1875rem 0.5rem;
|
|
13
|
+
min-width: 2rem;
|
|
14
|
+
max-width: 4.5rem;
|
|
15
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
16
|
+
|
|
17
|
+
&:hover {
|
|
18
|
+
background: linear-gradient(145deg, oklch(0.28 0 0), oklch(0.26 0 0));
|
|
19
|
+
border-color: oklch(0.33 0 0);
|
|
20
|
+
transform: translateY(-0.5px);
|
|
21
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.tag-text {
|
|
26
|
+
@apply text-white font-semibold truncate;
|
|
27
|
+
font-size: 0.6875rem;
|
|
28
|
+
line-height: 1.1;
|
|
29
|
+
letter-spacing: 0.025em;
|
|
30
|
+
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Ultra responsive design
|
|
34
|
+
@media (max-width: 1024px) {
|
|
35
|
+
.tag-pill {
|
|
36
|
+
padding: 0.1rem 0.3rem;
|
|
37
|
+
max-width: 3.5rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.tag-text {
|
|
41
|
+
font-size: 0.575rem;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (max-width: 768px) {
|
|
46
|
+
.tag-pill {
|
|
47
|
+
padding: 0.075rem 0.25rem;
|
|
48
|
+
max-width: 3rem;
|
|
49
|
+
min-width: 1.25rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.tag-text {
|
|
53
|
+
font-size: 0.55rem;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@media (max-width: 480px) {
|
|
58
|
+
.tag-pill {
|
|
59
|
+
padding: 0.05rem 0.2rem;
|
|
60
|
+
max-width: 2.5rem;
|
|
61
|
+
min-width: 1rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.tag-text {
|
|
65
|
+
font-size: 0.5rem;
|
|
66
|
+
}
|
|
11
67
|
}
|
|
12
68
|
</style>
|
|
13
69
|
|
|
14
70
|
<script setup>
|
|
15
71
|
const props = defineProps({ text: { type: String } });
|
|
72
|
+
|
|
73
|
+
const formatText = (text) => {
|
|
74
|
+
if (!text) return "";
|
|
75
|
+
return text.toUpperCase();
|
|
76
|
+
};
|
|
16
77
|
</script>
|