@necrolab/dashboard 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/api.js +2 -3
- package/eslint.config.js +46 -0
- package/index.html +2 -1
- package/package.json +5 -2
- package/src/App.vue +140 -170
- package/src/assets/css/base/mixins.scss +72 -0
- package/src/assets/css/base/reset.scss +0 -2
- package/src/assets/css/base/scroll.scss +43 -36
- package/src/assets/css/base/typography.scss +9 -10
- package/src/assets/css/base/variables.scss +43 -0
- package/src/assets/css/components/accessibility.scss +37 -0
- package/src/assets/css/components/buttons.scss +58 -15
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +119 -0
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -19
- package/src/assets/css/components/tables.scss +5 -7
- package/src/assets/css/components/toasts.scss +7 -7
- package/src/assets/css/components/utilities.scss +220 -0
- package/src/assets/css/main.scss +72 -75
- package/src/components/Auth/LoginForm.vue +5 -84
- package/src/components/Editors/Account/Account.vue +8 -10
- package/src/components/Editors/Account/AccountCreator.vue +28 -59
- package/src/components/Editors/Account/AccountView.vue +38 -86
- package/src/components/Editors/Account/CreateAccount.vue +8 -50
- package/src/components/Editors/Profile/CreateProfile.vue +74 -131
- package/src/components/Editors/Profile/Profile.vue +15 -17
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +46 -96
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +62 -75
- package/src/components/Filter/FilterPreview.vue +161 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +61 -12
- package/src/components/Tasks/CheckStock.vue +7 -16
- package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
- package/src/components/Tasks/Controls/MobileControls.vue +5 -20
- package/src/components/Tasks/CreateTaskAXS.vue +20 -118
- package/src/components/Tasks/CreateTaskTM.vue +33 -189
- package/src/components/Tasks/EventDetailRow.vue +21 -0
- package/src/components/Tasks/MassEdit.vue +6 -16
- package/src/components/Tasks/QuickSettings.vue +140 -216
- package/src/components/Tasks/ScrapeVenue.vue +4 -13
- package/src/components/Tasks/Stats.vue +19 -38
- package/src/components/Tasks/Task.vue +65 -268
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +43 -63
- package/src/components/Tasks/Utilities.vue +10 -42
- package/src/components/Tasks/ViewTask.vue +23 -107
- package/src/components/icons/Close.vue +2 -8
- package/src/components/icons/Gear.vue +8 -8
- package/src/components/icons/Hash.vue +5 -0
- package/src/components/icons/Key.vue +2 -8
- package/src/components/icons/Pencil.vue +2 -8
- package/src/components/icons/Profile.vue +2 -8
- package/src/components/icons/Sell.vue +2 -8
- package/src/components/icons/Spinner.vue +4 -7
- package/src/components/icons/SquareCheck.vue +2 -8
- package/src/components/icons/SquareUncheck.vue +2 -8
- package/src/components/icons/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -1
- package/src/components/ui/ActionButtonGroup.vue +113 -52
- package/src/components/ui/BalanceIndicator.vue +60 -0
- package/src/components/ui/EmptyState.vue +24 -0
- package/src/components/ui/EnableDisableToggle.vue +23 -0
- package/src/components/ui/FormField.vue +48 -48
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +78 -37
- package/src/components/ui/Navbar.vue +60 -41
- package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
- package/src/components/ui/ReconnectIndicator.vue +111 -124
- package/src/components/ui/SectionCard.vue +6 -14
- package/src/components/ui/Splash.vue +2 -10
- package/src/components/ui/StatusBadge.vue +26 -28
- package/src/components/ui/TaskToggle.vue +54 -0
- package/src/components/ui/controls/CountryChooser.vue +27 -64
- package/src/components/ui/controls/EyeToggle.vue +1 -1
- package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
- package/src/components/ui/controls/atomic/Dropdown.vue +102 -95
- package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -94
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useColorMapping.js +15 -0
- package/src/composables/useCopyToClipboard.js +1 -1
- package/src/composables/useDateFormatting.js +21 -0
- package/src/composables/useDeviceDetection.js +14 -0
- package/src/composables/useDropdownPosition.js +5 -6
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +60 -0
- package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
- package/src/libs/utils/eventUrl.js +40 -0
- package/src/libs/utils/string.js +28 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +88 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -4
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +59 -36
- package/src/views/Accounts.vue +17 -31
- package/src/views/Console.vue +76 -176
- package/src/views/Editor.vue +217 -383
- package/src/views/FilterBuilder.vue +190 -373
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +12 -22
- package/src/views/Tasks.vue +51 -38
- package/tailwind.config.js +82 -71
- package/workbox-config.cjs +47 -5
- package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2416
- package/exit +0 -209
- package/run +0 -177
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
|
@@ -2,63 +2,56 @@
|
|
|
2
2
|
<div class="flex items-center gap-1 font-bold text-white lg:gap-3" v-if="ui.queueStats.show" :key="key">
|
|
3
3
|
<div
|
|
4
4
|
v-if="ui.queueStats.total"
|
|
5
|
-
class="
|
|
6
|
-
<h2 class="
|
|
5
|
+
class="stat-badge">
|
|
6
|
+
<h2 class="stat-header">
|
|
7
7
|
<img width="14px" src="@/assets/img/wildcard.svg" />
|
|
8
8
|
<span class="hidden md:inline">Total</span>
|
|
9
9
|
</h2>
|
|
10
|
-
<span class="
|
|
10
|
+
<span class="stat-value">
|
|
11
11
|
{{ ui.queueStats.total }}
|
|
12
12
|
</span>
|
|
13
13
|
</div>
|
|
14
14
|
<div
|
|
15
15
|
v-if="ui.queueStats.queued"
|
|
16
|
-
class="
|
|
17
|
-
<h2 class="
|
|
16
|
+
class="stat-badge">
|
|
17
|
+
<h2 class="stat-header">
|
|
18
18
|
<SkiIcon />
|
|
19
19
|
<span class="hidden md:inline">Queued</span>
|
|
20
20
|
</h2>
|
|
21
|
-
<span class="
|
|
21
|
+
<span class="stat-value">
|
|
22
22
|
{{ ui.queueStats.queued }}
|
|
23
23
|
</span>
|
|
24
24
|
</div>
|
|
25
25
|
<div
|
|
26
26
|
v-if="ui.queueStats.sleeping"
|
|
27
|
-
class="
|
|
28
|
-
<h2 class="
|
|
27
|
+
class="stat-badge">
|
|
28
|
+
<h2 class="stat-header">
|
|
29
29
|
<TimerIcon />
|
|
30
30
|
<span class="hidden md:inline">Sleeping</span>
|
|
31
31
|
</h2>
|
|
32
|
-
<span class="
|
|
32
|
+
<span class="stat-value">
|
|
33
33
|
{{ ui.queueStats.sleeping }}
|
|
34
34
|
</span>
|
|
35
35
|
</div>
|
|
36
36
|
<div
|
|
37
37
|
v-if="ui.queueStats.nextQueuePasses.length > 0"
|
|
38
|
-
class="
|
|
39
|
-
<h2 class="
|
|
38
|
+
class="mb-2 flex h-8 min-w-0 items-center justify-between gap-2 rounded-xl p-2 text-sm lg:mb-5 lg:gap-3 lg:p-3 bg-dark-400 border-2 border-dark-550">
|
|
39
|
+
<h2 class="stat-header">
|
|
40
40
|
<CartIcon />
|
|
41
41
|
<span class="hidden sm:block">Next Passes</span>
|
|
42
42
|
<span class="block sm:hidden">Pass</span>
|
|
43
43
|
</h2>
|
|
44
|
-
<span class="flex items-center truncate text-right text-xs font-
|
|
44
|
+
<span class="flex items-center truncate text-right text-xs font-bold text-light-300">
|
|
45
45
|
{{ ui.queueStats.nextQueuePasses.slice(0, queuePassAmount).join(", ") }}
|
|
46
46
|
</span>
|
|
47
47
|
</div>
|
|
48
|
-
<!-- <div
|
|
49
|
-
v-if="ui.queueStats.carts"
|
|
50
|
-
class="bg-dark-500 mb-2 text-sm rounded-lg lg:p-2 p-1 lg:gap-3 gap-2 flex justify-between"
|
|
51
|
-
>
|
|
52
|
-
<h2 class="font-bold text-sm flex items-center gap-1"><CartIcon />Carts</h2>
|
|
53
|
-
<span class="text-light-400 text-sm font-black flex justify-center">{{ ui.queueStats.sleeping }} </span>
|
|
54
|
-
</div> -->
|
|
55
48
|
</div>
|
|
56
49
|
</template>
|
|
57
50
|
|
|
58
51
|
<script setup>
|
|
59
52
|
import { SkiIcon, TimerIcon, CartIcon } from "@/components/icons";
|
|
60
53
|
import { useUIStore } from "@/stores/ui";
|
|
61
|
-
import { ref } from "vue";
|
|
54
|
+
import { ref, onUnmounted } from "vue";
|
|
62
55
|
|
|
63
56
|
const ui = useUIStore();
|
|
64
57
|
|
|
@@ -71,22 +64,10 @@ const getQueuePassAmount = (width) => {
|
|
|
71
64
|
let key = ref(0);
|
|
72
65
|
let queuePassAmount = ref(getQueuePassAmount(window.innerWidth));
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<style lang="scss" scoped>
|
|
77
|
-
.stats-card {
|
|
78
|
-
@apply rounded;
|
|
79
|
-
background: oklch(0.2046 0 0);
|
|
80
|
-
border: 2px solid oklch(0.2809 0 0);
|
|
81
|
-
|
|
82
|
-
h2 {
|
|
83
|
-
font-weight: 600;
|
|
84
|
-
color: oklch(0.90 0 0);
|
|
85
|
-
}
|
|
67
|
+
const handleResize = () => (queuePassAmount.value = getQueuePassAmount(window.innerWidth));
|
|
68
|
+
window.addEventListener("resize", handleResize);
|
|
86
69
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
</style>
|
|
70
|
+
onUnmounted(() => {
|
|
71
|
+
window.removeEventListener("resize", handleResize);
|
|
72
|
+
});
|
|
73
|
+
</script>
|
|
@@ -5,68 +5,65 @@
|
|
|
5
5
|
@dblclick="handleDoubleClick"
|
|
6
6
|
@touchstart="handleTouchStart"
|
|
7
7
|
@touchend="handleTouchEnd">
|
|
8
|
-
<div class="col-span-1 flex items-
|
|
8
|
+
<div class="col-span-1 flex items-center justify-start lg:col-span-2 py-2">
|
|
9
9
|
<Checkbox
|
|
10
10
|
class="ml-2 mr-4 flex-shrink-0"
|
|
11
11
|
:toggled="props.task.selected"
|
|
12
12
|
@valueUpdate="ui.toggleTaskSelected(props.task.taskId)" />
|
|
13
13
|
<div
|
|
14
14
|
v-if="props.preferEventName && props.task.eventName"
|
|
15
|
-
class="
|
|
15
|
+
class="hidden lg:flex flex-col gap-0.5 justify-center cursor-pointer max-w-full min-w-0"
|
|
16
16
|
@click="copy(props.task.eventId, 'Copied event ID')"
|
|
17
17
|
:title="`Event ID: ${props.task.eventId}`">
|
|
18
|
-
<div class="
|
|
18
|
+
<div class="max-w-45 truncate text-xs+ font-semibold leading-tight text-white">
|
|
19
19
|
{{ props.task.eventName }}
|
|
20
20
|
</div>
|
|
21
|
-
<
|
|
22
|
-
<
|
|
21
|
+
<EventDetailRow :content="[props.task.venueName, props.task.eventCity].filter(Boolean).join(', ')" truncate>
|
|
22
|
+
<template #icon>
|
|
23
23
|
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
24
24
|
<circle cx="12" cy="10" r="3"></circle>
|
|
25
|
-
</
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<svg class="event-icon mt-[1px]" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
25
|
+
</template>
|
|
26
|
+
</EventDetailRow>
|
|
27
|
+
<EventDetailRow :content="formatEventDate(props.task.eventLocalDate)">
|
|
28
|
+
<template #icon>
|
|
30
29
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
|
31
30
|
<line x1="16" y1="2" x2="16" y2="6"></line>
|
|
32
31
|
<line x1="8" y1="2" x2="8" y2="6"></line>
|
|
33
32
|
<line x1="3" y1="10" x2="21" y2="10"></line>
|
|
34
|
-
</
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<svg class="event-icon mt-[1px]" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
33
|
+
</template>
|
|
34
|
+
</EventDetailRow>
|
|
35
|
+
<div v-if="props.task.email" class="flex items-start gap-1 text-3xs leading-tight-sm min-h-2.75">
|
|
36
|
+
<svg class="icon-sm" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
39
37
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
40
38
|
<polyline points="22,6 12,13 2,6"></polyline>
|
|
41
39
|
</svg>
|
|
42
|
-
<span class="truncate text-light-500">{{ props.task.email }}</span>
|
|
40
|
+
<span class="truncate text-light-500 text-3xs leading-tight-sm">{{ props.task.email }}</span>
|
|
43
41
|
</div>
|
|
44
42
|
</div>
|
|
45
43
|
<div
|
|
46
44
|
v-else
|
|
47
|
-
class="
|
|
48
|
-
@click="copy(props.task.eventId, 'Copied event ID')">
|
|
49
|
-
<div class="
|
|
50
|
-
<svg class="
|
|
45
|
+
class="hidden lg:flex flex-col gap-0.5 justify-center cursor-pointer max-w-full min-w-0"
|
|
46
|
+
@click="copy(props.task.eventId || props.task.email, props.task.eventId ? 'Copied event ID' : 'Copied email')">
|
|
47
|
+
<div class="flex items-center gap-1 text-xs+ text-white font-semibold max-w-45 truncate leading-tight-md">
|
|
48
|
+
<svg class="icon-sm" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
51
49
|
<path d="M9 11l3 3L22 4"></path>
|
|
52
50
|
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
|
53
51
|
</svg>
|
|
54
|
-
<span>{{ props.task.eventId }}</span>
|
|
52
|
+
<span>{{ props.task.eventId || props.task.email }}</span>
|
|
55
53
|
</div>
|
|
56
|
-
<
|
|
57
|
-
<
|
|
54
|
+
<EventDetailRow v-if="props.task.eventId && props.task.email && props.task.eventId !== props.task.email" :content="props.task.email" truncate>
|
|
55
|
+
<template #icon>
|
|
58
56
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
59
57
|
<polyline points="22,6 12,13 2,6"></polyline>
|
|
60
|
-
</
|
|
61
|
-
|
|
62
|
-
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</EventDetailRow>
|
|
63
60
|
</div>
|
|
64
61
|
</div>
|
|
65
|
-
<div class="col-span-2 overflow-hidden">
|
|
66
|
-
<h4 class="text-
|
|
62
|
+
<div class="col-span-2 overflow-hidden lg:col-span-3 xl:col-span-2">
|
|
63
|
+
<h4 class="text-center text-light-300 text-xs leading-tight lg:text-2xs">
|
|
67
64
|
<span v-if="!props.task.reservedTicketsList">-</span>
|
|
68
65
|
<div v-else class="overflow-hidden">
|
|
69
|
-
<div v-for="(l, index) in props.task.reservedTicketsList.split('\n')" :key="
|
|
66
|
+
<div v-for="(l, index) in props.task.reservedTicketsList.split('\n')" :key="`ticket-${index}`" class="truncate">
|
|
70
67
|
<span
|
|
71
68
|
v-if="!!l.trim()"
|
|
72
69
|
class="text-xs"
|
|
@@ -84,10 +81,10 @@
|
|
|
84
81
|
</span>
|
|
85
82
|
</h4>
|
|
86
83
|
</div>
|
|
87
|
-
<div class="col-span-
|
|
88
|
-
<div class="status-
|
|
84
|
+
<div class="col-span-6 justify-center text-center md:col-span-5 lg:col-span-4 xl:col-span-5">
|
|
85
|
+
<div class="task-status-badge">
|
|
89
86
|
<div
|
|
90
|
-
class="
|
|
87
|
+
class="w-2 h-2 rounded-full flex-shrink-0"
|
|
91
88
|
:class="
|
|
92
89
|
colorToClass(
|
|
93
90
|
props.task.active || props.task.status.toLowerCase() === 'idle'
|
|
@@ -95,43 +92,43 @@
|
|
|
95
92
|
: 'red'
|
|
96
93
|
)
|
|
97
94
|
"></div>
|
|
98
|
-
<span class="
|
|
95
|
+
<span class="truncate text-sm font-medium uppercase text-light-300 tracking-wide max-md:text-xs max-md:max-w-full max-sm:tracking-normal">{{ props.task.status }}</span>
|
|
99
96
|
</div>
|
|
100
97
|
</div>
|
|
101
|
-
<div class="col-span-
|
|
102
|
-
<ActionButtonGroup class="overflow-visible">
|
|
98
|
+
<div class="col-span-1 flex md:col-span-2 lg:col-span-3 overflow-visible max-sm:min-w-0 max-sm:flex-shrink-0 max-sm:items-center max-sm:justify-end max-sm:px-0.5">
|
|
99
|
+
<ActionButtonGroup class="overflow-visible" :allowWrap="true">
|
|
103
100
|
<li>
|
|
104
|
-
<button v-if="task.active" @click="ui.stopTask(task.taskId)">
|
|
101
|
+
<button v-if="task.active" @click="ui.stopTask(task.taskId)" aria-label="Stop task">
|
|
105
102
|
<PauseIcon />
|
|
106
103
|
</button>
|
|
107
|
-
<button v-else @click="ui.startTask(task.taskId)">
|
|
104
|
+
<button v-else @click="ui.startTask(task.taskId)" aria-label="Start task">
|
|
108
105
|
<PlayIcon />
|
|
109
106
|
</button>
|
|
110
107
|
</li>
|
|
111
108
|
<li v-if="task.status?.toLowerCase() == 'waiting' && props.task._timeLeftString !== '00:00'">
|
|
112
|
-
<button @click="ui.continueTask(task.taskId, 'autocheckout')">
|
|
109
|
+
<button @click="ui.continueTask(task.taskId, 'autocheckout')" aria-label="Auto checkout">
|
|
113
110
|
<BagWhiteIcon />
|
|
114
111
|
</button>
|
|
115
112
|
</li>
|
|
116
113
|
<li v-if="task.status?.toLowerCase() == 'waiting'">
|
|
117
|
-
<button @click="ui.continueTask(task.taskId, 'change_reservation')">
|
|
114
|
+
<button @click="ui.continueTask(task.taskId, 'change_reservation')" aria-label="Change reservation">
|
|
118
115
|
<EditIcon />
|
|
119
116
|
</button>
|
|
120
117
|
</li>
|
|
121
118
|
<li>
|
|
122
|
-
<button @click="ui.deleteTask(task.taskId)">
|
|
119
|
+
<button @click="ui.deleteTask(task.taskId)" aria-label="Delete task">
|
|
123
120
|
<TrashIcon />
|
|
124
121
|
</button>
|
|
125
122
|
</li>
|
|
126
123
|
<li @contextmenu.prevent="handleRightClick">
|
|
127
|
-
<button @click="openViewTaskModal">
|
|
124
|
+
<button @click="openViewTaskModal" aria-label="View task details">
|
|
128
125
|
<EyeIcon />
|
|
129
126
|
</button>
|
|
130
127
|
</li>
|
|
131
128
|
</ActionButtonGroup>
|
|
132
129
|
</div>
|
|
133
|
-
<div class="absolute right-5 top-4 col-span-1 hidden items-center justify-center
|
|
134
|
-
<h4 class="
|
|
130
|
+
<div class="absolute right-5 top-4 col-span-1 hidden items-center justify-center xl:flex max-sm:left-16 max-sm:top-1 max-sm:z-10">
|
|
131
|
+
<h4 class="text-center text-xs text-light-500 bg-dark-475/40 border border-dark-500 font-semibold tracking-wide m-0 px-1.5 py-0.5 rounded text-2xs lg:text-2xs">
|
|
135
132
|
{{ props.task.taskId }}
|
|
136
133
|
</h4>
|
|
137
134
|
</div>
|
|
@@ -155,202 +152,40 @@
|
|
|
155
152
|
</transition>
|
|
156
153
|
</Row>
|
|
157
154
|
</template>
|
|
158
|
-
<style lang="scss" scoped>
|
|
159
|
-
h4 {
|
|
160
|
-
@apply text-center;
|
|
161
|
-
color: oklch(0.90 0 0);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.event-details,
|
|
165
|
-
.event-id-details {
|
|
166
|
-
max-width: 100%;
|
|
167
|
-
min-width: 0;
|
|
168
|
-
gap: 2px;
|
|
169
|
-
|
|
170
|
-
.event-name,
|
|
171
|
-
.event-id-row {
|
|
172
|
-
max-width: 180px;
|
|
173
|
-
overflow: hidden;
|
|
174
|
-
text-overflow: ellipsis;
|
|
175
|
-
white-space: nowrap;
|
|
176
|
-
font-size: 11px;
|
|
177
|
-
line-height: 1.3;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.event-icon {
|
|
181
|
-
@apply min-w-[10px] flex-shrink-0;
|
|
182
|
-
min-height: 10px;
|
|
183
|
-
color: oklch(0.60 0 0);
|
|
184
|
-
stroke: oklch(0.60 0 0);
|
|
185
|
-
fill: none;
|
|
186
|
-
|
|
187
|
-
path,
|
|
188
|
-
rect,
|
|
189
|
-
line,
|
|
190
|
-
circle,
|
|
191
|
-
polyline {
|
|
192
|
-
stroke: oklch(0.60 0 0);
|
|
193
|
-
fill: none;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.event-venue,
|
|
198
|
-
.event-date,
|
|
199
|
-
.event-email {
|
|
200
|
-
line-height: 1.2;
|
|
201
|
-
min-height: 11px;
|
|
202
|
-
|
|
203
|
-
span {
|
|
204
|
-
color: oklch(0.60 0 0);
|
|
205
|
-
font-size: 9px;
|
|
206
|
-
line-height: 1.2;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.status-container {
|
|
212
|
-
@apply mx-auto flex w-fit items-center justify-center rounded-lg border border-dark-650 bg-dark-500;
|
|
213
|
-
padding: 6px 12px;
|
|
214
|
-
gap: 6px;
|
|
215
|
-
max-width: 100%;
|
|
216
|
-
pointer-events: none;
|
|
217
|
-
|
|
218
|
-
@media (max-width: 768px) {
|
|
219
|
-
padding: 4px 8px;
|
|
220
|
-
gap: 4px;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.status-text {
|
|
225
|
-
@apply truncate text-sm font-medium uppercase;
|
|
226
|
-
color: oklch(0.90 0 0);
|
|
227
|
-
letter-spacing: 0.025em;
|
|
228
|
-
|
|
229
|
-
@media (max-width: 768px) {
|
|
230
|
-
font-size: 0.65rem;
|
|
231
|
-
max-width: 140px;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/* Mobile specific styling - changed from 480px to 640px for earlier transition */
|
|
236
|
-
@media (max-width: 640px) {
|
|
237
|
-
/* Position adjustment for mobile taskId */
|
|
238
|
-
.block.md\\:hidden {
|
|
239
|
-
@apply left-16 top-1 z-10;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/* Make the actions column more flexible */
|
|
243
|
-
.col-span-2.lg\\:col-span-3 {
|
|
244
|
-
min-width: 0;
|
|
245
|
-
flex-shrink: 0;
|
|
246
|
-
display: flex;
|
|
247
|
-
align-items: center;
|
|
248
|
-
justify-content: center;
|
|
249
|
-
padding: 0 2px;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/* Compact status container */
|
|
253
|
-
.status-container {
|
|
254
|
-
@apply border-0 shadow-sm;
|
|
255
|
-
padding: 2px 4px;
|
|
256
|
-
font-size: 0.7rem;
|
|
257
|
-
max-width: 100%;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.status-text {
|
|
261
|
-
font-size: 0.65rem;
|
|
262
|
-
letter-spacing: 0;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.task-id {
|
|
267
|
-
font-size: 10px;
|
|
268
|
-
font-weight: 600;
|
|
269
|
-
letter-spacing: 0.5px;
|
|
270
|
-
margin: 0;
|
|
271
|
-
color: oklch(0.65 0 0);
|
|
272
|
-
background: rgba(46, 47, 52, 0.4);
|
|
273
|
-
padding: 2px 6px;
|
|
274
|
-
border-radius: 4px;
|
|
275
|
-
border: 1px solid oklch(0.26 0 0);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.task-event-id {
|
|
279
|
-
font-size: 11px;
|
|
280
|
-
font-weight: 500;
|
|
281
|
-
text-align: center;
|
|
282
|
-
color: oklch(0.82 0 0);
|
|
283
|
-
|
|
284
|
-
&:hover {
|
|
285
|
-
color: #ffffff;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
@media (max-width: 1024px) {
|
|
289
|
-
font-size: 10px;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
@media (max-width: 768px) {
|
|
293
|
-
font-size: 9px;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/* Responsive task styling */
|
|
298
|
-
@screen lg {
|
|
299
|
-
h4 {
|
|
300
|
-
font-size: 10px;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.task-id {
|
|
304
|
-
font-size: 8px;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
.task-event-id {
|
|
308
|
-
font-size: 10px;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
</style>
|
|
312
155
|
|
|
313
156
|
<script setup>
|
|
314
157
|
/// <reference path="@/types/index.js" />
|
|
315
158
|
|
|
316
159
|
import { Row } from "@/components/Table";
|
|
317
|
-
import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon, EyeIcon
|
|
160
|
+
import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon, EyeIcon } from "@/components/icons";
|
|
318
161
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
319
162
|
import ActionButtonGroup from "@/components/ui/ActionButtonGroup.vue";
|
|
163
|
+
import EventDetailRow from "@/components/Tasks/EventDetailRow.vue";
|
|
320
164
|
import { useUIStore } from "@/stores/ui";
|
|
321
|
-
import
|
|
322
|
-
import ViewTask from "@/components/Tasks/ViewTask.vue";
|
|
323
|
-
import { computed, ref, onMounted, onUnmounted, nextTick } from "vue";
|
|
165
|
+
import { ref, onUnmounted, nextTick } from "vue";
|
|
324
166
|
import { useRowSelection } from "@/composables/useRowSelection";
|
|
325
167
|
import { useCopyToClipboard } from "@/composables/useCopyToClipboard";
|
|
168
|
+
import { useColorMapping } from "@/composables/useColorMapping";
|
|
169
|
+
import { useDateFormatting } from "@/composables/useDateFormatting";
|
|
170
|
+
import { useTicketPricing } from "@/composables/useTicketPricing";
|
|
326
171
|
|
|
327
172
|
const ui = useUIStore();
|
|
328
173
|
const { copy } = useCopyToClipboard();
|
|
174
|
+
const { colorToClass } = useColorMapping();
|
|
175
|
+
const { formatEventDate } = useDateFormatting();
|
|
176
|
+
const { isTotalPrice } = useTicketPricing();
|
|
329
177
|
|
|
330
178
|
/** @type {{ task: Task }} */
|
|
331
179
|
const props = defineProps({
|
|
332
|
-
task: {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
const date = new Date(dateString);
|
|
341
|
-
const options = {
|
|
342
|
-
month: 'short',
|
|
343
|
-
day: 'numeric',
|
|
344
|
-
year: 'numeric',
|
|
345
|
-
hour: 'numeric',
|
|
346
|
-
minute: '2-digit',
|
|
347
|
-
hour12: true
|
|
348
|
-
};
|
|
349
|
-
return date.toLocaleString('en-US', options).replace(',', '');
|
|
350
|
-
} catch {
|
|
351
|
-
return dateString;
|
|
180
|
+
task: {
|
|
181
|
+
type: Object,
|
|
182
|
+
required: true
|
|
183
|
+
},
|
|
184
|
+
preferEventName: {
|
|
185
|
+
type: Boolean,
|
|
186
|
+
default: false
|
|
352
187
|
}
|
|
353
|
-
};
|
|
188
|
+
});
|
|
354
189
|
|
|
355
190
|
// Context menu positioning
|
|
356
191
|
const contextMenuPosition = ref({});
|
|
@@ -361,20 +196,21 @@ const { handleDoubleClick, handleTouchStart, handleTouchEnd } = useRowSelection(
|
|
|
361
196
|
ui.toggleTaskSelected(props.task.taskId);
|
|
362
197
|
});
|
|
363
198
|
|
|
199
|
+
// Context menu dimensions
|
|
200
|
+
const MENU_WIDTH = 168; // w-42 = 10.5rem = 168px
|
|
201
|
+
const MENU_HEIGHT = 200; // Approximate height
|
|
202
|
+
|
|
364
203
|
// Handle right-click to position context menu
|
|
365
204
|
const handleRightClick = (event) => {
|
|
366
|
-
const menuWidth = 168; // w-42 = 10.5rem = 168px
|
|
367
|
-
const menuHeight = 200; // Approximate height
|
|
368
|
-
|
|
369
205
|
let x = event.clientX;
|
|
370
206
|
let y = event.clientY - 55;
|
|
371
207
|
|
|
372
208
|
// Prevent menu from going off screen
|
|
373
|
-
if (x +
|
|
374
|
-
x = event.clientX -
|
|
209
|
+
if (x + MENU_WIDTH > window.innerWidth) {
|
|
210
|
+
x = event.clientX - MENU_WIDTH; // Show to the left instead
|
|
375
211
|
}
|
|
376
|
-
if (y +
|
|
377
|
-
y = event.clientY -
|
|
212
|
+
if (y + MENU_HEIGHT > window.innerHeight) {
|
|
213
|
+
y = event.clientY - MENU_HEIGHT; // Show above instead
|
|
378
214
|
}
|
|
379
215
|
|
|
380
216
|
contextMenuPosition.value = {
|
|
@@ -410,32 +246,6 @@ const openViewTaskModal = () => {
|
|
|
410
246
|
ui.toggleModal('view-task');
|
|
411
247
|
};
|
|
412
248
|
|
|
413
|
-
const isTotalPrice = (line, index, lines) => {
|
|
414
|
-
const trimmed = line.trim();
|
|
415
|
-
if (!trimmed) return false;
|
|
416
|
-
|
|
417
|
-
// Check if this is the last non-empty line
|
|
418
|
-
const nonEmptyLines = lines.filter(l => l.trim());
|
|
419
|
-
const isLastLine = index === lines.lastIndexOf(nonEmptyLines[nonEmptyLines.length - 1]);
|
|
420
|
-
|
|
421
|
-
if (!isLastLine) return false;
|
|
422
|
-
|
|
423
|
-
// Check if line is a standalone price (not in parentheses)
|
|
424
|
-
// Matches: $345.88, €345.88, USD 345.88, EUR 345.88, etc.
|
|
425
|
-
// Does NOT match: ($86.47) or 2× 301/E ($86.47)
|
|
426
|
-
const totalPricePattern = /^([$€£¥₹₽¢]|[A-Z]{3})\s*[\d,]+\.?\d*$/;
|
|
427
|
-
return totalPricePattern.test(trimmed) && !trimmed.includes('(') && !trimmed.includes(')');
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
const colorMapping = new Map();
|
|
431
|
-
colorMapping.set("green", "bg-green-400");
|
|
432
|
-
colorMapping.set("red", "bg-red-400");
|
|
433
|
-
colorMapping.set("error", "bg-red-400");
|
|
434
|
-
colorMapping.set("success", "bg-green-400");
|
|
435
|
-
const colorToClass = (color) => {
|
|
436
|
-
return colorMapping.get(color) || "bg-white";
|
|
437
|
-
};
|
|
438
|
-
|
|
439
249
|
const openInBrowser = (debug) => {
|
|
440
250
|
if (!props.task.openerLink) return;
|
|
441
251
|
ui.showSuccess(`Opening in browser ${debug ? "(debug)" : ""}`);
|
|
@@ -446,19 +256,6 @@ const openInBrowser = (debug) => {
|
|
|
446
256
|
openInNewTab(out);
|
|
447
257
|
};
|
|
448
258
|
|
|
449
|
-
const formatDate = (date) => {
|
|
450
|
-
if (!date) return "TBA";
|
|
451
|
-
const d = new Date(date);
|
|
452
|
-
const iso = d.toISOString();
|
|
453
|
-
const [year, month, day] = iso.substring(0, 10).split("-");
|
|
454
|
-
const time = iso.substring(11, 16);
|
|
455
|
-
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
456
|
-
const dayName = dayNames[d.getUTCDay()];
|
|
457
|
-
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
458
|
-
const monthName = monthNames[parseInt(month) - 1];
|
|
459
|
-
return `${dayName} ${monthName} ${parseInt(day)} ${year} ${time}`;
|
|
460
|
-
};
|
|
461
|
-
|
|
462
259
|
const openInNewTab = (href) => {
|
|
463
260
|
if (!href) return;
|
|
464
261
|
ui.logger.Info("Opening", href);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex rounded-2xl gap-x-2 max-w-full shadow-3xl mx-auto items-center justify-center bg-dark-
|
|
2
|
+
<div class="flex rounded-2xl gap-x-2 max-w-full shadow-3xl mx-auto items-center justify-center bg-dark-550">
|
|
3
3
|
<img class="ml-3 flex-shrink-0" v-if="logo" :src="logo" />
|
|
4
4
|
<div v-else class="ml-3"></div>
|
|
5
5
|
<span class="font-bold p-2 truncate mr-3">{{ text }}</span>
|
|
@@ -10,8 +10,14 @@
|
|
|
10
10
|
import { ref } from "vue";
|
|
11
11
|
|
|
12
12
|
const props = defineProps({
|
|
13
|
-
image: {
|
|
14
|
-
|
|
13
|
+
image: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: ''
|
|
16
|
+
},
|
|
17
|
+
text: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
}
|
|
15
21
|
});
|
|
16
22
|
|
|
17
23
|
const logo = ref(`/img/${props.image}.svg`);
|