@necrolab/dashboard 0.4.38 → 0.4.40
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 +7 -1
- package/.prettierrc +14 -1
- package/backend/api.js +25 -16
- package/backend/auth.js +2 -2
- package/backend/batching.js +1 -1
- package/backend/endpoints.js +5 -5
- package/backend/index.js +2 -2
- package/backend/mock-data.js +27 -28
- package/backend/mock-src/classes/logger.js +5 -7
- package/backend/mock-src/classes/utils.js +3 -2
- package/backend/mock-src/ticketmaster.js +2 -2
- package/backend/validator.js +2 -2
- package/dev-server.js +136 -0
- package/index.html +1 -1
- package/index.js +1 -1
- package/package.json +8 -6
- package/postcss.config.js +1 -1
- package/postinstall.js +30 -16
- 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/manifest.json +4 -4
- package/src/App.vue +471 -49
- package/src/assets/css/_input.scss +37 -37
- package/src/assets/css/main.scss +177 -30
- package/src/assets/img/logo_icon-old.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/components/Auth/LoginForm.vue +12 -5
- package/src/components/Editors/Account/Account.vue +19 -19
- package/src/components/Editors/Account/AccountCreator.vue +53 -24
- package/src/components/Editors/Account/AccountView.vue +79 -17
- package/src/components/Editors/Account/CreateAccount.vue +47 -28
- package/src/components/Editors/Profile/Profile.vue +24 -24
- package/src/components/Editors/Profile/ProfileView.vue +67 -16
- package/src/components/Editors/TagLabel.vue +6 -7
- package/src/components/Filter/FilterPreview.vue +0 -4
- package/src/components/Table/Table.vue +15 -0
- package/src/components/Tasks/Controls/DesktopControls.vue +1 -1
- package/src/components/Tasks/CreateTaskAXS.vue +15 -15
- package/src/components/Tasks/CreateTaskTM.vue +5 -4
- package/src/components/Tasks/Stats.vue +22 -16
- package/src/components/Tasks/Task.vue +100 -81
- package/src/components/Tasks/TaskView.vue +25 -23
- package/src/components/Tasks/Utilities.vue +1 -1
- package/src/components/icons/Mail.vue +2 -2
- package/src/components/ui/Modal.vue +84 -15
- package/src/components/ui/Navbar.vue +118 -39
- package/src/components/ui/controls/atomic/Dropdown.vue +23 -3
- package/src/components/ui/controls/atomic/MultiDropdown.vue +43 -23
- package/src/stores/sampleData.js +89 -64
- package/src/stores/ui.js +30 -4
- package/src/views/Accounts.vue +2 -2
- package/src/views/Console.vue +276 -41
- package/src/views/Editor.vue +175 -28
- package/src/views/FilterBuilder.vue +45 -49
- package/src/views/Login.vue +134 -12
- package/src/views/Profiles.vue +8 -8
- package/src/views/Tasks.vue +51 -2
- package/tailwind.config.js +2 -2
- package/vite.config.js +34 -1
- package/vue.config.js +1 -1
- package/{workbox-config.js → workbox-config.cjs} +1 -4
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Row
|
|
3
|
-
class="relative text-white grid-cols-12 gap-2"
|
|
4
|
-
@click="ui.setOpenContextMenu('')"
|
|
5
|
-
@click.right.prevent="ui.setOpenContextMenu('')"
|
|
6
|
-
>
|
|
2
|
+
<Row class="relative text-white grid-cols-10 gap-2 ipadlg:grid-cols-12" @click="ui.setOpenContextMenu('')">
|
|
7
3
|
<div class="block md:hidden absolute left-1 top-1">
|
|
8
4
|
<h4 class="text-xs task-id text-white font-bold">
|
|
9
5
|
{{ props.task.taskId }}
|
|
@@ -13,18 +9,13 @@
|
|
|
13
9
|
<Checkbox
|
|
14
10
|
class="ml-2 mr-4 flex-shrink-0"
|
|
15
11
|
:toggled="props.task.selected"
|
|
16
|
-
@valueUpdate="ui.toggleTaskSelected(props.task.taskId)"
|
|
17
|
-
/>
|
|
12
|
+
@valueUpdate="ui.toggleTaskSelected(props.task.taskId)" />
|
|
18
13
|
<h4
|
|
19
14
|
class="task-event-id mx-auto hidden lg:block text-white cursor-pointer hover:text-light-300"
|
|
20
|
-
@click="copy(props.task.eventId)"
|
|
21
|
-
>
|
|
15
|
+
@click="copy(props.task.eventId)">
|
|
22
16
|
{{ props.task.eventId }}
|
|
23
17
|
</h4>
|
|
24
18
|
</div>
|
|
25
|
-
<div class="col-span-2 hidden lg:block">
|
|
26
|
-
<h4 class="text-white">{{ props.task.quantity }}</h4>
|
|
27
|
-
</div>
|
|
28
19
|
<div class="col-span-2">
|
|
29
20
|
<h4 class="text-white">
|
|
30
21
|
<span v-if="!props.task.reservedTicketsList">-</span>
|
|
@@ -38,13 +29,12 @@
|
|
|
38
29
|
:class="{
|
|
39
30
|
'text-red-400':
|
|
40
31
|
props.task._timeLeftString === '00:00' || props.task._timeLeftString === 'No Cartholds'
|
|
41
|
-
}"
|
|
42
|
-
>
|
|
32
|
+
}">
|
|
43
33
|
{{ props.task._timeLeftString !== "00:00" ? props.task._timeLeftString : "Expired" }}
|
|
44
34
|
</span>
|
|
45
35
|
</h4>
|
|
46
36
|
</div>
|
|
47
|
-
<div class="col-span-
|
|
37
|
+
<div class="col-span-5 md:col-span-4 lg:col-span-5 ipadlg:col-span-6 text-center justify-center">
|
|
48
38
|
<div class="status-container">
|
|
49
39
|
<div
|
|
50
40
|
class="status-indicator"
|
|
@@ -54,12 +44,11 @@
|
|
|
54
44
|
? props.task.statusColor
|
|
55
45
|
: 'red'
|
|
56
46
|
)
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
<span class="status-text">{{ truncate(props.task.status, statusTruncateLength) }}</span>
|
|
47
|
+
"></div>
|
|
48
|
+
<span class="status-text">{{ props.task.status }}</span>
|
|
60
49
|
</div>
|
|
61
50
|
</div>
|
|
62
|
-
<div class="col-span-2 flex">
|
|
51
|
+
<div class="col-span-2 ipadlg:col-span-3 flex">
|
|
63
52
|
<ul class="task-buttons">
|
|
64
53
|
<li>
|
|
65
54
|
<button v-if="task.active" @click="ui.stopTask(task.taskId)">
|
|
@@ -84,7 +73,7 @@
|
|
|
84
73
|
<TrashIcon />
|
|
85
74
|
</button>
|
|
86
75
|
</li>
|
|
87
|
-
<li @
|
|
76
|
+
<li @contextmenu.prevent="handleRightClick">
|
|
88
77
|
<button @click="props.task.isExpanded = !props.task.isExpanded">
|
|
89
78
|
<span>{{ props.task.isExpanded ? "−" : "+" }}</span>
|
|
90
79
|
</button>
|
|
@@ -98,30 +87,26 @@
|
|
|
98
87
|
</div>
|
|
99
88
|
<transition name="fade">
|
|
100
89
|
<div
|
|
101
|
-
class="col-span-12 flex flex-wrap gap-x-4 gap-y-4 lg:gap-x-10 pt-8 pb-2 xl:justify-around will-change-auto"
|
|
102
|
-
v-if="props.task.isExpanded"
|
|
103
|
-
>
|
|
90
|
+
class="col-span-10 ipadlg:col-span-12 flex flex-wrap gap-x-4 gap-y-4 lg:gap-x-10 pt-8 pb-2 xl:justify-around will-change-auto"
|
|
91
|
+
v-if="props.task.isExpanded">
|
|
104
92
|
<!-- Details -->
|
|
105
93
|
<TaskLabel
|
|
106
94
|
class="md:hidden"
|
|
107
95
|
image="stadium_w"
|
|
108
96
|
:text="props.task.eventId"
|
|
109
|
-
@click="copy(props.task.eventId)"
|
|
110
|
-
/>
|
|
97
|
+
@click="copy(props.task.eventId)" />
|
|
111
98
|
|
|
112
99
|
<TaskLabel class="md:hidden" image="bag_w" :text="props.task.quantity" />
|
|
113
100
|
<TaskLabel
|
|
114
101
|
v-if="props.task.email"
|
|
115
102
|
image="mail"
|
|
116
103
|
:text="props.task.email"
|
|
117
|
-
@click="copy(props.task.email)"
|
|
118
|
-
/>
|
|
104
|
+
@click="copy(props.task.email)" />
|
|
119
105
|
<TaskLabel
|
|
120
106
|
v-if="props.task.password"
|
|
121
107
|
image="key"
|
|
122
108
|
:text="props.task.password"
|
|
123
|
-
@click="copy(props.task.password)"
|
|
124
|
-
/>
|
|
109
|
+
@click="copy(props.task.password)" />
|
|
125
110
|
<TaskLabel v-if="!props.task.email && !props.task.password" image="mail" text="No account chosen yet" />
|
|
126
111
|
<TaskLabel v-if="props.task.profileName" image="profile" :text="props.task.profileName" />
|
|
127
112
|
<TaskLabel image="timer" :text="props.task.smartTimer ? 'On' : 'Off'" />
|
|
@@ -136,40 +121,37 @@
|
|
|
136
121
|
v-if="props.task.presaleCode"
|
|
137
122
|
@click="copy(props.task.presaleCode)"
|
|
138
123
|
image="pencil"
|
|
139
|
-
:text="props.task.presaleCode"
|
|
140
|
-
/>
|
|
124
|
+
:text="props.task.presaleCode" />
|
|
141
125
|
|
|
142
126
|
<TaskLabel v-if="props.task.eventName" image="stadium_w" :text="props.task.eventName" />
|
|
143
127
|
<TaskLabel v-if="props.task.eventVenue" image="stadium_w" :text="props.task.eventVenue" />
|
|
144
128
|
<TaskLabel
|
|
145
129
|
v-if="props.task.eventLocalDate"
|
|
146
130
|
image="stadium_w"
|
|
147
|
-
:text="formatDate(props.task.eventLocalDate)"
|
|
148
|
-
/>
|
|
131
|
+
:text="formatDate(props.task.eventLocalDate)" />
|
|
149
132
|
<TaskLabel image="sandclock" :text="props.task.agedAccount ? 'On' : 'Off'" />
|
|
150
133
|
</div>
|
|
151
134
|
</transition>
|
|
152
135
|
|
|
153
136
|
<!-- Context menu -->
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
</div>
|
|
137
|
+
<transition name="fade">
|
|
138
|
+
<div
|
|
139
|
+
v-if="ui.openContextMenu === task.taskId"
|
|
140
|
+
ref="contextMenuRef"
|
|
141
|
+
class="fixed bg-dark-500 text-white w-42 grid grid-cols-1 p-1 gap-1 rounded-lg shadow-xl border border-dark-650"
|
|
142
|
+
style="z-index: 9999"
|
|
143
|
+
:style="contextMenuPosition">
|
|
144
|
+
<button class="btn-primary" @click="openInNewTab(`${ui.currentCountry.url}/event/${task.eventId}`)">
|
|
145
|
+
Open Event
|
|
146
|
+
</button>
|
|
147
|
+
<button v-if="task.openerLink" class="btn-primary" @click="openInBrowser(false)">
|
|
148
|
+
Open in browser (proxy)
|
|
149
|
+
</button>
|
|
150
|
+
<button v-if="task.openerLink" class="btn-primary" @click="openInBrowser(true)">
|
|
151
|
+
Open in browser (debug)
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
</transition>
|
|
173
155
|
</Row>
|
|
174
156
|
</template>
|
|
175
157
|
<style lang="scss" scoped>
|
|
@@ -183,6 +165,7 @@ h4 {
|
|
|
183
165
|
padding: 6px 12px;
|
|
184
166
|
gap: 6px;
|
|
185
167
|
transition: all 0.15s ease;
|
|
168
|
+
max-width: 100%;
|
|
186
169
|
|
|
187
170
|
&:hover {
|
|
188
171
|
@apply bg-dark-550 border-dark-700;
|
|
@@ -300,19 +283,23 @@ h4 {
|
|
|
300
283
|
gap: 0;
|
|
301
284
|
border-radius: 7px;
|
|
302
285
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
286
|
+
max-width: calc(100vw - 260px); /* Prevent overflow on small screens */
|
|
287
|
+
overflow: hidden;
|
|
303
288
|
|
|
304
289
|
button {
|
|
305
|
-
width:
|
|
306
|
-
height:
|
|
290
|
+
width: 20px;
|
|
291
|
+
height: 20px;
|
|
307
292
|
border-radius: 5px;
|
|
293
|
+
min-width: 20px;
|
|
294
|
+
flex-shrink: 0;
|
|
308
295
|
}
|
|
309
296
|
svg,
|
|
310
297
|
img {
|
|
311
|
-
width:
|
|
312
|
-
height:
|
|
298
|
+
width: 10px;
|
|
299
|
+
height: 10px;
|
|
313
300
|
}
|
|
314
301
|
span {
|
|
315
|
-
font-size:
|
|
302
|
+
font-size: 9px;
|
|
316
303
|
}
|
|
317
304
|
}
|
|
318
305
|
}
|
|
@@ -340,12 +327,13 @@ h4 {
|
|
|
340
327
|
.task-buttons {
|
|
341
328
|
padding: 1px;
|
|
342
329
|
gap: 0;
|
|
343
|
-
border-radius:
|
|
330
|
+
border-radius: 5px;
|
|
344
331
|
|
|
345
332
|
button {
|
|
346
|
-
width:
|
|
347
|
-
height:
|
|
348
|
-
border-radius:
|
|
333
|
+
width: 15px;
|
|
334
|
+
height: 15px;
|
|
335
|
+
border-radius: 2px;
|
|
336
|
+
min-width: 15px;
|
|
349
337
|
|
|
350
338
|
&:hover {
|
|
351
339
|
transform: scale(1.1);
|
|
@@ -354,11 +342,11 @@ h4 {
|
|
|
354
342
|
|
|
355
343
|
svg,
|
|
356
344
|
img {
|
|
357
|
-
width:
|
|
358
|
-
height:
|
|
345
|
+
width: 8px;
|
|
346
|
+
height: 8px;
|
|
359
347
|
}
|
|
360
348
|
span {
|
|
361
|
-
font-size:
|
|
349
|
+
font-size: 8px;
|
|
362
350
|
}
|
|
363
351
|
}
|
|
364
352
|
}
|
|
@@ -372,7 +360,7 @@ import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/compon
|
|
|
372
360
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
373
361
|
import { useUIStore } from "@/stores/ui";
|
|
374
362
|
import TaskLabel from "@/components/Tasks/TaskLabel.vue";
|
|
375
|
-
import { ref } from "vue";
|
|
363
|
+
import { computed, ref, onMounted, onUnmounted, nextTick } from "vue";
|
|
376
364
|
|
|
377
365
|
const ui = useUIStore();
|
|
378
366
|
|
|
@@ -381,6 +369,53 @@ const props = defineProps({
|
|
|
381
369
|
task: { type: Object }
|
|
382
370
|
});
|
|
383
371
|
|
|
372
|
+
// Context menu positioning
|
|
373
|
+
const contextMenuPosition = ref({});
|
|
374
|
+
const contextMenuRef = ref(null);
|
|
375
|
+
|
|
376
|
+
// Handle right-click to position context menu
|
|
377
|
+
const handleRightClick = (event) => {
|
|
378
|
+
const menuWidth = 168; // w-42 = 10.5rem = 168px
|
|
379
|
+
const menuHeight = 200; // Approximate height
|
|
380
|
+
|
|
381
|
+
let x = event.clientX;
|
|
382
|
+
let y = event.clientY - 55;
|
|
383
|
+
|
|
384
|
+
// Prevent menu from going off screen
|
|
385
|
+
if (x + menuWidth > window.innerWidth) {
|
|
386
|
+
x = event.clientX - menuWidth; // Show to the left instead
|
|
387
|
+
}
|
|
388
|
+
if (y + menuHeight > window.innerHeight) {
|
|
389
|
+
y = event.clientY - menuHeight; // Show above instead
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
contextMenuPosition.value = {
|
|
393
|
+
left: `${x}px`,
|
|
394
|
+
top: `${y}px`
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Open the context menu for this task
|
|
398
|
+
ui.setOpenContextMenu(props.task.taskId);
|
|
399
|
+
|
|
400
|
+
// Add click outside listener after menu opens
|
|
401
|
+
nextTick(() => {
|
|
402
|
+
document.addEventListener("click", handleClickOutside);
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// Handle clicking outside the context menu
|
|
407
|
+
const handleClickOutside = (event) => {
|
|
408
|
+
if (contextMenuRef.value && !contextMenuRef.value.contains(event.target)) {
|
|
409
|
+
ui.setOpenContextMenu("");
|
|
410
|
+
document.removeEventListener("click", handleClickOutside);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Cleanup on unmount
|
|
415
|
+
onUnmounted(() => {
|
|
416
|
+
document.removeEventListener("click", handleClickOutside);
|
|
417
|
+
});
|
|
418
|
+
|
|
384
419
|
const copy = (txt) => {
|
|
385
420
|
if (!txt) return;
|
|
386
421
|
navigator.clipboard.writeText(txt);
|
|
@@ -396,11 +431,6 @@ const colorToClass = (color) => {
|
|
|
396
431
|
return colorMapping.get(color) || "bg-white";
|
|
397
432
|
};
|
|
398
433
|
|
|
399
|
-
const truncate = (text, after) => {
|
|
400
|
-
if (text?.length <= after || after === -1) return text;
|
|
401
|
-
return text?.substring(0, after) + "...";
|
|
402
|
-
};
|
|
403
|
-
|
|
404
434
|
const openInBrowser = (debug) => {
|
|
405
435
|
if (!props.task.openerLink) return;
|
|
406
436
|
ui.showSuccess(`Opening in browser ${debug ? "(debug)" : ""}`);
|
|
@@ -433,15 +463,4 @@ const openInNewTab = (href) => {
|
|
|
433
463
|
href: href
|
|
434
464
|
}).click();
|
|
435
465
|
};
|
|
436
|
-
|
|
437
|
-
const getMaxStatusLength = (width) => {
|
|
438
|
-
if (width > 1279) return -1;
|
|
439
|
-
if (width > 767) return 25;
|
|
440
|
-
if (width > 639) return 30;
|
|
441
|
-
if (width > 540) return 18;
|
|
442
|
-
return 13;
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
let statusTruncateLength = ref(getMaxStatusLength(window.innerWidth));
|
|
446
|
-
window.addEventListener("resize", () => (statusTruncateLength.value = getMaxStatusLength(window.innerWidth)));
|
|
447
466
|
</script>
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Table>
|
|
3
|
-
<Header class="text-center grid-cols-
|
|
3
|
+
<Header class="text-center grid-cols-10 gap-2 ipadlg:grid-cols-12">
|
|
4
4
|
<div class="col-span-1 lg:col-span-2 flex items-center justify-start">
|
|
5
5
|
<Checkbox
|
|
6
6
|
class="ml-2 mr-4"
|
|
7
7
|
:toggled="ui.mainCheckbox.tasks"
|
|
8
8
|
@valueUpdate="ui.toggleMainCheckbox('tasks')"
|
|
9
|
-
:isHeader="true"
|
|
10
|
-
/>
|
|
9
|
+
:isHeader="true" />
|
|
11
10
|
<div class="mx-auto hidden lg:flex items-center" @click="ui.toggleSort('eventId')">
|
|
12
11
|
<EventIcon class="ipadlg:mr-3" />
|
|
13
12
|
<h4 class="hidden ipadlg:flex">Event</h4>
|
|
@@ -15,22 +14,19 @@
|
|
|
15
14
|
<UpIcon v-if="ui.sortData.sortBy === 'eventId' && ui.sortData.reversed" class="ml-1" />
|
|
16
15
|
</div>
|
|
17
16
|
</div>
|
|
18
|
-
<div class="col-span-2 items-center justify-center hidden lg:flex" v-once>
|
|
19
|
-
<CartIcon class="mr-0 ipadlg:mr-3" />
|
|
20
|
-
|
|
21
|
-
<h4 class="hidden ipadlg:flex">Quantity</h4>
|
|
22
|
-
</div>
|
|
23
17
|
<div class="col-span-2 flex-center" v-once>
|
|
24
18
|
<TicketIcon class="mr-0 ipadlg:mr-3" />
|
|
25
19
|
<h4 class="hidden ipadlg:flex">Tickets</h4>
|
|
26
20
|
</div>
|
|
27
|
-
<div
|
|
21
|
+
<div
|
|
22
|
+
class="col-span-5 md:col-span-4 lg:col-span-5 ipadlg:col-span-6 flex-center"
|
|
23
|
+
@click="ui.toggleSort('status')">
|
|
28
24
|
<StatusIcon class="mr-0 ipadlg:mr-3" />
|
|
29
25
|
<h4 class="hidden ipadlg:flex">Status</h4>
|
|
30
26
|
<DownIcon v-if="ui.sortData.sortBy === 'status' && !ui.sortData.reversed" class="ml-1" />
|
|
31
27
|
<UpIcon v-if="ui.sortData.sortBy === 'status' && ui.sortData.reversed" class="ml-1" />
|
|
32
28
|
</div>
|
|
33
|
-
<div class="col-span-2 flex-center" v-once>
|
|
29
|
+
<div class="col-span-2 ipadlg:col-span-3 flex-center" v-once>
|
|
34
30
|
<ClickIcon class="mr-0 ipadlg:mr-3" />
|
|
35
31
|
<h4 class="hidden ipadlg:flex">Actions</h4>
|
|
36
32
|
</div>
|
|
@@ -42,18 +38,25 @@
|
|
|
42
38
|
</Header>
|
|
43
39
|
<div
|
|
44
40
|
class="flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
|
|
45
|
-
:style="{ maxHeight: dynamicTableHeight }"
|
|
46
|
-
|
|
47
|
-
<div
|
|
48
|
-
v-for="(task, i) in getTasksInOrder()"
|
|
49
|
-
:key="task.taskId"
|
|
50
|
-
class="task-row-container"
|
|
51
|
-
>
|
|
41
|
+
:style="{ maxHeight: dynamicTableHeight }">
|
|
42
|
+
<div v-for="(task, i) in getTasksInOrder()" :key="task.taskId" class="task-row-container">
|
|
52
43
|
<Task :task="task" :style="{ backgroundColor: i % 2 == 1 ? '#1a1b1e' : '#242529' }" />
|
|
53
44
|
</div>
|
|
54
|
-
<div
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
<div
|
|
46
|
+
v-if="getTasksInOrder().length === 0"
|
|
47
|
+
class="flex flex-col items-center justify-center py-8 empty-state text-center">
|
|
48
|
+
<div v-if="ui.queueStats.total === 0">
|
|
49
|
+
<TasksIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50 mx-auto" />
|
|
50
|
+
<p class="text-light-400 text-sm">No tasks yet</p>
|
|
51
|
+
<p class="text-light-500 text-xs mt-1">Create tasks to get started</p>
|
|
52
|
+
</div>
|
|
53
|
+
<div v-else>
|
|
54
|
+
<TasksIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50 mx-auto" />
|
|
55
|
+
<p class="text-light-400 text-sm">
|
|
56
|
+
{{ ui.queueStats.total }} hidden task{{ ui.queueStats.total === 1 ? "" : "s" }}
|
|
57
|
+
</p>
|
|
58
|
+
<p class="text-light-500 text-xs mt-1">Adjust filters to see tasks</p>
|
|
59
|
+
</div>
|
|
57
60
|
</div>
|
|
58
61
|
</div>
|
|
59
62
|
</Table>
|
|
@@ -81,7 +84,6 @@ h4 {
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
|
|
85
87
|
.empty-state {
|
|
86
88
|
@apply bg-dark-400;
|
|
87
89
|
color: #969696;
|
|
@@ -92,7 +94,7 @@ h4 {
|
|
|
92
94
|
<script setup>
|
|
93
95
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
94
96
|
import { Table, Header } from "@/components/Table";
|
|
95
|
-
import { EventIcon,
|
|
97
|
+
import { EventIcon, TicketIcon, StatusIcon, ClickIcon, DownIcon, UpIcon, TasksIcon } from "@/components/icons";
|
|
96
98
|
import Task from "./Task.vue";
|
|
97
99
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
98
100
|
import { useUIStore } from "@/stores/ui";
|
|
@@ -173,7 +175,7 @@ const dynamicTableHeight = computed(() => {
|
|
|
173
175
|
const minHeight = minRowsToShow * rowHeight;
|
|
174
176
|
|
|
175
177
|
// Calculate how many complete rows can fit
|
|
176
|
-
const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
|
|
178
|
+
const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight) + 1;
|
|
177
179
|
const exactHeight = maxCompleteRows * rowHeight;
|
|
178
180
|
|
|
179
181
|
return exactHeight + "px";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="grid grid-cols-1 gap-3 lg:grid-cols-1" v-
|
|
2
|
+
<div class="grid grid-cols-1 gap-3 lg:grid-cols-1" v-if="ui.currentModule == 'TM'">
|
|
3
3
|
<div class="lg:justify-self-end">
|
|
4
4
|
<h4 class="hidden lg:block text-white opacity-40 uppercase font-medium">Utils</h4>
|
|
5
5
|
<div class="flex gap-3 justify-between lg:justify-start">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg width="
|
|
2
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
3
|
<path
|
|
4
|
-
d="
|
|
4
|
+
d="M3.54165 15.2083C3.19217 15.2083 2.893098 15.084 2.644438 14.8353C2.395355 14.5863 2.270813 14.287 2.270813 13.9375V6.3125C2.270813 5.96302 2.395355 5.66395 2.644438 5.41529C2.893098 5.16621 3.19217 5.04166 3.54165 5.04166H16.7083C17.0578 5.04166 17.3571 5.16621 17.6062 5.41529C17.8548 5.66395 17.9791 5.96302 17.9791 6.3125V13.9375C17.9791 14.287 17.8548 14.5863 17.6062 14.8353C17.3571 15.084 17.0578 15.2083 16.7083 15.2083H3.54165ZM10.125 10.7604L3.54165 7.58333V13.9375H16.7083V7.58333L10.125 10.7604ZM10.125 9.48958L16.7083 6.3125H3.54165L10.125 9.48958ZM3.54165 7.58333V6.3125V13.9375V7.58333Z"
|
|
5
5
|
fill="white"
|
|
6
6
|
/>
|
|
7
7
|
</svg>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="modal-mask pt-14 ipadlg:py-1 overflow-y-scroll scrollable">
|
|
3
|
-
<div class="component-modal" ref="target">
|
|
2
|
+
<div class="modal-mask pt-14 ipadlg:py-1 overflow-y-scroll scrollable" @touchmove.prevent>
|
|
3
|
+
<div class="component-modal" ref="target" @touchmove.stop>
|
|
4
4
|
<div class="modal-header">
|
|
5
5
|
<slot name="header" />
|
|
6
6
|
<button @click="ui.toggleModal()" class="btn-icon border-none hover:bg-dark-400">
|
|
@@ -16,12 +16,40 @@
|
|
|
16
16
|
<script setup>
|
|
17
17
|
import { useUIStore } from "@/stores/ui";
|
|
18
18
|
import { onClickOutside } from "@vueuse/core";
|
|
19
|
-
import { ref } from "vue";
|
|
19
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
20
20
|
import { CloseIcon } from "@/components/icons";
|
|
21
21
|
|
|
22
22
|
const ui = useUIStore();
|
|
23
23
|
const target = ref(null);
|
|
24
24
|
|
|
25
|
+
// Store original body styles
|
|
26
|
+
let originalOverflow = "";
|
|
27
|
+
let originalPosition = "";
|
|
28
|
+
let originalTop = "";
|
|
29
|
+
let scrollY = 0;
|
|
30
|
+
|
|
31
|
+
onMounted(() => {
|
|
32
|
+
// Lock body scroll
|
|
33
|
+
scrollY = window.scrollY;
|
|
34
|
+
originalOverflow = document.body.style.overflow;
|
|
35
|
+
originalPosition = document.body.style.position;
|
|
36
|
+
originalTop = document.body.style.top;
|
|
37
|
+
|
|
38
|
+
document.body.style.overflow = "hidden";
|
|
39
|
+
document.body.style.position = "fixed";
|
|
40
|
+
document.body.style.top = `-${scrollY}px`;
|
|
41
|
+
document.body.style.width = "100%";
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
onUnmounted(() => {
|
|
45
|
+
// Restore body scroll
|
|
46
|
+
document.body.style.overflow = originalOverflow;
|
|
47
|
+
document.body.style.position = originalPosition;
|
|
48
|
+
document.body.style.top = originalTop;
|
|
49
|
+
document.body.style.width = "";
|
|
50
|
+
window.scrollTo(0, scrollY);
|
|
51
|
+
});
|
|
52
|
+
|
|
25
53
|
onClickOutside(target, (event) => {
|
|
26
54
|
if (event.target.classList.contains("modal-mask")) ui.toggleModal();
|
|
27
55
|
});
|
|
@@ -32,21 +60,24 @@ onClickOutside(target, (event) => {
|
|
|
32
60
|
z-index: 9998;
|
|
33
61
|
background-color: rgba(17, 17, 17, 0.85);
|
|
34
62
|
backdrop-filter: blur(4px);
|
|
63
|
+
align-items: center; // Keep centered on desktop
|
|
64
|
+
justify-content: center;
|
|
65
|
+
padding: 2rem;
|
|
35
66
|
}
|
|
36
67
|
|
|
37
68
|
.component-modal {
|
|
38
|
-
margin: auto;
|
|
39
69
|
width: 640px;
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
// Remove max-height on desktop to show full modal
|
|
71
|
+
@apply bg-dark-300 px-5 py-5 rounded-lg flex flex-col;
|
|
72
|
+
|
|
42
73
|
.modal-header {
|
|
43
74
|
@apply flex text-white font-bold;
|
|
44
|
-
|
|
75
|
+
|
|
45
76
|
button {
|
|
46
77
|
@apply ml-auto;
|
|
47
78
|
}
|
|
48
79
|
}
|
|
49
|
-
|
|
80
|
+
|
|
50
81
|
.modal-body {
|
|
51
82
|
@apply flex flex-col;
|
|
52
83
|
}
|
|
@@ -54,19 +85,57 @@ onClickOutside(target, (event) => {
|
|
|
54
85
|
|
|
55
86
|
@media (max-width: 810px) {
|
|
56
87
|
.modal-mask {
|
|
57
|
-
|
|
58
|
-
|
|
88
|
+
align-items: flex-start; // Start from top on mobile
|
|
89
|
+
justify-content: center;
|
|
90
|
+
padding: 1rem;
|
|
91
|
+
padding-top: 2rem;
|
|
59
92
|
}
|
|
60
|
-
|
|
93
|
+
|
|
61
94
|
.component-modal {
|
|
62
95
|
width: calc(100vw - 2rem);
|
|
63
|
-
max-height: calc(100vh -
|
|
64
|
-
overflow-y: auto;
|
|
65
|
-
|
|
96
|
+
max-height: calc(100vh - 8rem); // Only limit height on mobile
|
|
97
|
+
overflow-y: auto; // Only add scrolling on mobile
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.modal-body {
|
|
101
|
+
padding-bottom: 3rem; // Increased padding for create button accessibility
|
|
102
|
+
overflow-y: auto; // Allow scrolling on mobile
|
|
103
|
+
flex: 1;
|
|
104
|
+
min-height: 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// iPhone landscape mode specific adjustments
|
|
109
|
+
@media only screen and (max-device-width: 430px) and (orientation: landscape) {
|
|
110
|
+
.modal-mask {
|
|
111
|
+
top: 0;
|
|
112
|
+
left: 0;
|
|
113
|
+
right: 0;
|
|
114
|
+
bottom: 0;
|
|
115
|
+
width: 100vw;
|
|
116
|
+
height: 100vh;
|
|
117
|
+
padding: 0;
|
|
118
|
+
padding-top: calc(env(safe-area-inset-top, 0px) + 4rem); // Navbar + safe area
|
|
119
|
+
padding-bottom: env(safe-area-inset-bottom, 0.5rem);
|
|
120
|
+
padding-left: env(safe-area-inset-left, 0.5rem);
|
|
121
|
+
padding-right: env(safe-area-inset-right, 0.5rem);
|
|
122
|
+
align-items: flex-start;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.component-modal {
|
|
127
|
+
width: 100%;
|
|
128
|
+
max-width: none;
|
|
129
|
+
max-height: calc(100vh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 5rem);
|
|
130
|
+
min-height: auto;
|
|
131
|
+
margin: 0;
|
|
66
132
|
}
|
|
67
133
|
|
|
68
134
|
.modal-body {
|
|
69
|
-
padding-bottom:
|
|
135
|
+
padding-bottom: 4rem; // Ensure create button is accessible
|
|
136
|
+
max-height: calc(100vh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 8rem);
|
|
137
|
+
overflow-y: auto;
|
|
138
|
+
-webkit-overflow-scrolling: touch;
|
|
70
139
|
}
|
|
71
140
|
}
|
|
72
141
|
</style>
|