@necrolab/dashboard 0.4.220 → 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 -79
- 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,244 +1,465 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Row
|
|
3
|
-
class="
|
|
4
|
-
@click="ui.setOpenContextMenu('')"
|
|
5
|
-
@click.right.prevent="ui.setOpenContextMenu('')"
|
|
6
|
-
>
|
|
7
|
-
<div class="block md:hidden absolute left-1 top-0">
|
|
8
|
-
<h4 class="text-xs task-id text-white">
|
|
9
|
-
{{ props.task.taskId }}
|
|
10
|
-
</h4>
|
|
11
|
-
</div>
|
|
12
|
-
<div class="col-span-1 lg:col-span-2 flex">
|
|
2
|
+
<Row class="relative grid-cols-10 gap-2 text-white lg:grid-cols-12" @click="ui.setOpenContextMenu('')">
|
|
3
|
+
<div class="col-span-1 flex items-center justify-start lg:col-span-2">
|
|
13
4
|
<Checkbox
|
|
14
|
-
class="ml-
|
|
5
|
+
class="ml-2 mr-4 flex-shrink-0"
|
|
15
6
|
:toggled="props.task.selected"
|
|
16
|
-
@valueUpdate="ui.toggleTaskSelected(props.task.taskId)"
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
@valueUpdate="ui.toggleTaskSelected(props.task.taskId)" />
|
|
8
|
+
<h4
|
|
9
|
+
class="task-event-id mx-auto hidden cursor-pointer text-white hover:text-light-300 lg:block"
|
|
10
|
+
@click="copy(props.task.eventId)">
|
|
19
11
|
{{ props.task.eventId }}
|
|
20
12
|
</h4>
|
|
21
13
|
</div>
|
|
22
|
-
<div class="col-span-2 hidden
|
|
23
|
-
<h4 class="text-white
|
|
24
|
-
</div>
|
|
25
|
-
<div class="col-span-2">
|
|
26
|
-
<h4 class="text-white">
|
|
14
|
+
<div class="col-span-2 overflow-hidden">
|
|
15
|
+
<h4 class="text-white text-xs leading-tight">
|
|
27
16
|
<span v-if="!props.task.reservedTicketsList">-</span>
|
|
28
|
-
<div v-else>
|
|
29
|
-
<div v-for="l in props.task.reservedTicketsList.split('
|
|
30
|
-
<span
|
|
17
|
+
<div v-else class="overflow-hidden">
|
|
18
|
+
<div v-for="(l, index) in props.task.reservedTicketsList.split('\n')" :key="l" class="truncate">
|
|
19
|
+
<span
|
|
20
|
+
v-if="!!l.trim()"
|
|
21
|
+
class="text-xs"
|
|
22
|
+
:class="{ 'text-green-400 font-bold': isTotalPrice(l, index, props.task.reservedTicketsList.split('\n')) }"
|
|
23
|
+
>{{ l.trim() }}</span>
|
|
31
24
|
</div>
|
|
32
25
|
</div>
|
|
33
26
|
<span
|
|
34
|
-
class="
|
|
35
|
-
:class="
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
>
|
|
27
|
+
class="block mt-1 font-bold text-xs"
|
|
28
|
+
:class="{
|
|
29
|
+
'text-red-400':
|
|
30
|
+
props.task._timeLeftString === '00:00' || props.task._timeLeftString === 'No Cartholds'
|
|
31
|
+
}">
|
|
32
|
+
{{ props.task._timeLeftString !== "00:00" ? props.task._timeLeftString : "Expired" }}
|
|
33
|
+
</span>
|
|
42
34
|
</h4>
|
|
43
35
|
</div>
|
|
44
|
-
<div class="col-span-
|
|
45
|
-
<div class="
|
|
46
|
-
<!-- Status circle -->
|
|
36
|
+
<div class="col-span-5 justify-center text-center md:col-span-4 lg:col-span-5">
|
|
37
|
+
<div class="status-container">
|
|
47
38
|
<div
|
|
48
|
-
class="
|
|
49
|
-
:class="
|
|
39
|
+
class="status-indicator"
|
|
40
|
+
:class="
|
|
50
41
|
colorToClass(
|
|
51
42
|
props.task.active || props.task.status.toLowerCase() === 'idle'
|
|
52
43
|
? props.task.statusColor
|
|
53
44
|
: 'red'
|
|
54
45
|
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<!-- Actual status -->
|
|
58
|
-
<span class="font-bold text-sm p-1 md:mr-3 mr-2 truncate uppercase">{{
|
|
59
|
-
truncate(props.task.status, statusTruncateLength)
|
|
60
|
-
}}</span>
|
|
46
|
+
"></div>
|
|
47
|
+
<span class="status-text">{{ props.task.status }}</span>
|
|
61
48
|
</div>
|
|
62
49
|
</div>
|
|
63
|
-
<div class="col-span-2 flex">
|
|
64
|
-
<ul class="task-buttons
|
|
50
|
+
<div class="col-span-2 flex lg:col-span-3 overflow-visible">
|
|
51
|
+
<ul class="task-buttons overflow-visible">
|
|
65
52
|
<li>
|
|
66
|
-
<button
|
|
53
|
+
<button v-if="task.active" @click="ui.stopTask(task.taskId)">
|
|
67
54
|
<PauseIcon />
|
|
68
55
|
</button>
|
|
69
|
-
<button
|
|
56
|
+
<button v-else @click="ui.startTask(task.taskId)">
|
|
70
57
|
<PlayIcon />
|
|
71
58
|
</button>
|
|
72
59
|
</li>
|
|
73
|
-
<li v-if="task.status?.toLowerCase()
|
|
74
|
-
<button
|
|
60
|
+
<li v-if="task.status?.toLowerCase() == 'waiting' && props.task._timeLeftString !== '00:00'">
|
|
61
|
+
<button @click="ui.continueTask(task.taskId, 'autocheckout')">
|
|
75
62
|
<BagWhiteIcon />
|
|
76
63
|
</button>
|
|
77
64
|
</li>
|
|
78
|
-
<li v-if="task.status?.toLowerCase()
|
|
79
|
-
<button
|
|
65
|
+
<li v-if="task.status?.toLowerCase() == 'waiting'">
|
|
66
|
+
<button @click="ui.continueTask(task.taskId, 'change_reservation')">
|
|
80
67
|
<EditIcon />
|
|
81
68
|
</button>
|
|
82
69
|
</li>
|
|
83
70
|
<li>
|
|
84
|
-
<button
|
|
71
|
+
<button @click="ui.deleteTask(task.taskId)">
|
|
85
72
|
<TrashIcon />
|
|
86
73
|
</button>
|
|
87
74
|
</li>
|
|
88
|
-
<li
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
>
|
|
92
|
-
<!-- eslint-disable-next-line vue/no-mutating-props -->
|
|
93
|
-
<button class="p-1 mt-1" @click="props.task.isExpanded = !props.task.isExpanded">
|
|
94
|
-
<span>{{ props.task.isExpanded ? "-" : "+" }}</span>
|
|
75
|
+
<li @contextmenu.prevent="handleRightClick">
|
|
76
|
+
<button @click="openViewTaskModal">
|
|
77
|
+
<EyeIcon />
|
|
95
78
|
</button>
|
|
96
79
|
</li>
|
|
97
80
|
</ul>
|
|
98
81
|
</div>
|
|
99
|
-
<div class="
|
|
100
|
-
<h4 class="text-center text-xs
|
|
82
|
+
<div class="absolute right-5 top-4 col-span-1 hidden items-center justify-center md:block lg:flex">
|
|
83
|
+
<h4 class="task-id text-center text-xs text-white">
|
|
101
84
|
{{ props.task.taskId }}
|
|
102
85
|
</h4>
|
|
103
86
|
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Context menu -->
|
|
104
89
|
<transition name="fade">
|
|
105
90
|
<div
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<TaskLabel
|
|
120
|
-
class="md:hidden"
|
|
121
|
-
image="stadium_w"
|
|
122
|
-
:text="props.task.eventId"
|
|
123
|
-
@click="copy(props.task.eventId)"
|
|
124
|
-
/>
|
|
125
|
-
|
|
126
|
-
<TaskLabel class="md:hidden" image="bag_w" :text="props.task.quantity" />
|
|
127
|
-
<TaskLabel
|
|
128
|
-
v-if="props.task.email"
|
|
129
|
-
image="mail"
|
|
130
|
-
:text="props.task.email"
|
|
131
|
-
@click="copy(props.task.email)"
|
|
132
|
-
/>
|
|
133
|
-
<TaskLabel
|
|
134
|
-
v-if="props.task.password"
|
|
135
|
-
image="key"
|
|
136
|
-
:text="props.task.password"
|
|
137
|
-
@click="copy(props.task.password)"
|
|
138
|
-
/>
|
|
139
|
-
<TaskLabel v-if="!props.task.email && !props.task.password" image="mail" text="No account chosen yet" />
|
|
140
|
-
<TaskLabel v-if="props.task.profileName" image="profile" :text="props.task.profileName" />
|
|
141
|
-
<TaskLabel image="camera" :text="props.task.proxy" @click="copy(props.task.proxy)" />
|
|
142
|
-
<TaskLabel image="timer" :text="props.task.smartTimer ? 'On' : 'Off'" />
|
|
143
|
-
<TaskLabel image="groups" :text="props.task.loginAfterCart ? 'On' : 'Off'" />
|
|
144
|
-
<TaskLabel image="hand" :text="props.task.manual ? 'On' : 'Off'" />
|
|
145
|
-
<TaskLabel image="savings" :text="props.task.doNotPay ? 'On' : 'Off'" />
|
|
146
|
-
<TaskLabel image="loyalty" :text="props.task.presaleMode ? 'On' : 'Off'" />
|
|
147
|
-
<TaskLabel image="ski" :text="props.task.quickQueue ? 'On' : 'Off'" />
|
|
148
|
-
<TaskLabel image="scanner" :text="props.task.accountTag" />
|
|
149
|
-
<TaskLabel image="sell" :text="props.task.profileTags.join(', ')" />
|
|
150
|
-
<TaskLabel
|
|
151
|
-
v-if="props.task.presaleCode"
|
|
152
|
-
@click="copy(props.task.presaleCode)"
|
|
153
|
-
image="pencil"
|
|
154
|
-
:text="props.task.presaleCode"
|
|
155
|
-
/>
|
|
156
|
-
|
|
157
|
-
<TaskLabel v-if="props.task.eventName" image="stadium_w" :text="props.task.eventName" />
|
|
158
|
-
<TaskLabel v-if="props.task.eventVenue" image="stadium_w" :text="props.task.eventVenue" />
|
|
159
|
-
<TaskLabel
|
|
160
|
-
v-if="props.task.eventLocalDate"
|
|
161
|
-
image="stadium_w"
|
|
162
|
-
:text="formatDate(props.task.eventLocalDate)"
|
|
163
|
-
/>
|
|
164
|
-
<TaskLabel image="sandclock" :text="props.task.agedAccount ? 'On' : 'Off'" />
|
|
91
|
+
v-if="ui.openContextMenu === task.taskId"
|
|
92
|
+
ref="contextMenuRef"
|
|
93
|
+
class="w-42 context-menu z-max fixed grid grid-cols-1 gap-1 rounded-lg border border-dark-650 bg-dark-500 p-1 text-white shadow-xl"
|
|
94
|
+
:style="contextMenuPosition">
|
|
95
|
+
<button class="btn-primary" @click="openInNewTab(`${ui.currentCountry.url}/event/${task.eventId}`)">
|
|
96
|
+
Open Event
|
|
97
|
+
</button>
|
|
98
|
+
<button v-if="task.openerLink" class="btn-primary" @click="openInBrowser(false)">
|
|
99
|
+
Open in browser (proxy)
|
|
100
|
+
</button>
|
|
101
|
+
<button v-if="task.openerLink" class="btn-primary" @click="openInBrowser(true)">
|
|
102
|
+
Open in browser (debug)
|
|
103
|
+
</button>
|
|
165
104
|
</div>
|
|
166
105
|
</transition>
|
|
167
|
-
|
|
168
|
-
<!-- Context menu -->
|
|
169
|
-
|
|
170
|
-
<div class="absolute -bottom-1.5 right-5">
|
|
171
|
-
<transition name="fade">
|
|
172
|
-
<div
|
|
173
|
-
v-if="ui.openContextMenu === task.taskId"
|
|
174
|
-
class="bg-light-300 text-white w-42 grid grid-cols-1 p-1 gap-1 z-50 rounded-lg shadow-xl relative"
|
|
175
|
-
>
|
|
176
|
-
<!-- <span class="text-light-400">{{ task.taskId }}</span> -->
|
|
177
|
-
<button
|
|
178
|
-
class="bg-dark-500 smooth-hover p-1 rounded-lg"
|
|
179
|
-
@click="openInNewTab(`${ui.currentCountry.url}/event/${task.eventId}`)"
|
|
180
|
-
>
|
|
181
|
-
Open Event
|
|
182
|
-
</button>
|
|
183
|
-
<button
|
|
184
|
-
v-if="task.openerLink"
|
|
185
|
-
class="bg-dark-500 smooth-hover p-1 rounded-lg"
|
|
186
|
-
@click="openInBrowser(false)"
|
|
187
|
-
>
|
|
188
|
-
Open in browser (proxy)
|
|
189
|
-
</button>
|
|
190
|
-
<button
|
|
191
|
-
v-if="task.openerLink"
|
|
192
|
-
class="bg-dark-500 smooth-hover p-1 rounded-lg"
|
|
193
|
-
@click="openInBrowser(true)"
|
|
194
|
-
>
|
|
195
|
-
Open in browser (debug)
|
|
196
|
-
</button>
|
|
197
|
-
</div>
|
|
198
|
-
</transition>
|
|
199
|
-
</div>
|
|
200
106
|
</Row>
|
|
201
107
|
</template>
|
|
202
108
|
<style lang="scss" scoped>
|
|
203
109
|
h4 {
|
|
204
110
|
@apply text-center;
|
|
111
|
+
color: oklch(0.90 0 0);
|
|
205
112
|
}
|
|
206
113
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
114
|
+
.status-container {
|
|
115
|
+
@apply mx-auto flex w-fit items-center justify-center rounded-lg border border-dark-650 bg-dark-500;
|
|
116
|
+
padding: 6px 12px;
|
|
117
|
+
gap: 6px;
|
|
118
|
+
max-width: 100%;
|
|
119
|
+
pointer-events: none;
|
|
120
|
+
|
|
121
|
+
@media (max-width: 768px) {
|
|
122
|
+
padding: 4px 8px;
|
|
123
|
+
gap: 4px;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.status-text {
|
|
128
|
+
@apply truncate text-sm font-medium uppercase;
|
|
129
|
+
color: oklch(0.90 0 0);
|
|
130
|
+
letter-spacing: 0.025em;
|
|
131
|
+
|
|
132
|
+
@media (max-width: 768px) {
|
|
133
|
+
font-size: 0.65rem;
|
|
134
|
+
max-width: 140px;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.task-buttons {
|
|
139
|
+
@apply mx-auto flex items-center justify-center rounded;
|
|
140
|
+
background: oklch(0.2046 0 0);
|
|
141
|
+
border: 2px solid oklch(0.2809 0 0);
|
|
142
|
+
padding: 2px;
|
|
143
|
+
gap: 1px;
|
|
144
|
+
flex-shrink: 0;
|
|
145
|
+
overflow: visible;
|
|
146
|
+
|
|
147
|
+
button {
|
|
148
|
+
@apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
|
|
149
|
+
background: transparent;
|
|
150
|
+
width: 22px;
|
|
151
|
+
height: 22px;
|
|
152
|
+
color: oklch(0.90 0 0);
|
|
153
|
+
border-radius: 4px;
|
|
154
|
+
|
|
155
|
+
&:hover {
|
|
156
|
+
background: oklch(0.72 0.15 145 / 0.15);
|
|
157
|
+
color: oklch(1 0 0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&:active {
|
|
161
|
+
background: oklch(0.72 0.15 145 / 0.25);
|
|
162
|
+
}
|
|
210
163
|
}
|
|
164
|
+
|
|
165
|
+
svg,
|
|
166
|
+
img {
|
|
167
|
+
width: 12px;
|
|
168
|
+
height: 12px;
|
|
169
|
+
position: relative;
|
|
170
|
+
z-index: 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
svg path {
|
|
174
|
+
fill: currentColor;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
span {
|
|
178
|
+
@apply relative z-[1] text-xs font-medium;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Tablet sizing - medium buttons */
|
|
183
|
+
@media (min-width: 768px) and (max-width: 1023px) {
|
|
184
|
+
.task-buttons {
|
|
185
|
+
padding: 2px;
|
|
186
|
+
gap: 1px;
|
|
187
|
+
border-radius: 6px;
|
|
188
|
+
|
|
189
|
+
button {
|
|
190
|
+
width: 26px;
|
|
191
|
+
height: 26px;
|
|
192
|
+
border-radius: 5px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
svg,
|
|
196
|
+
img {
|
|
197
|
+
width: 14px;
|
|
198
|
+
height: 14px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
span {
|
|
202
|
+
font-size: 0.75rem;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Desktop sizing - large buttons */
|
|
208
|
+
@media (min-width: 1024px) {
|
|
211
209
|
.task-buttons {
|
|
212
|
-
|
|
210
|
+
padding: 3px;
|
|
211
|
+
gap: 2px;
|
|
212
|
+
border-radius: 8px;
|
|
213
|
+
|
|
214
|
+
button {
|
|
215
|
+
width: 28px;
|
|
216
|
+
height: 28px;
|
|
217
|
+
border-radius: 6px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
svg,
|
|
221
|
+
img {
|
|
222
|
+
width: 16px;
|
|
223
|
+
height: 16px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
span {
|
|
227
|
+
font-size: 0.875rem;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Mobile specific styling - changed from 480px to 640px for earlier transition */
|
|
233
|
+
@media (max-width: 640px) {
|
|
234
|
+
/* Position adjustment for mobile taskId */
|
|
235
|
+
.block.md\\:hidden {
|
|
236
|
+
left: 4rem !important;
|
|
237
|
+
top: 0.25rem !important;
|
|
238
|
+
z-index: 1 !important;
|
|
213
239
|
}
|
|
240
|
+
|
|
241
|
+
/* Improved button layout for mobile */
|
|
242
|
+
.task-buttons {
|
|
243
|
+
padding: 1px;
|
|
244
|
+
gap: 1px;
|
|
245
|
+
border-radius: 4px;
|
|
246
|
+
border: 2px solid oklch(0.2809 0 0) !important;
|
|
247
|
+
max-width: 100%;
|
|
248
|
+
min-height: 28px;
|
|
249
|
+
height: auto;
|
|
250
|
+
flex-wrap: wrap;
|
|
251
|
+
justify-content: center;
|
|
252
|
+
align-items: center;
|
|
253
|
+
background: oklch(0.2046 0 0);
|
|
254
|
+
|
|
255
|
+
button {
|
|
256
|
+
width: 20px;
|
|
257
|
+
height: 20px;
|
|
258
|
+
border-radius: 3px;
|
|
259
|
+
min-width: 20px;
|
|
260
|
+
border: none !important;
|
|
261
|
+
flex-shrink: 0;
|
|
262
|
+
margin: 0.5px;
|
|
263
|
+
background: transparent;
|
|
264
|
+
|
|
265
|
+
&:hover {
|
|
266
|
+
background: oklch(0.72 0.15 145 / 0.15);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
&:active {
|
|
270
|
+
background: oklch(0.72 0.15 145 / 0.25);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
svg,
|
|
275
|
+
img {
|
|
276
|
+
width: 10px;
|
|
277
|
+
height: 10px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
span {
|
|
281
|
+
font-size: 0.7rem;
|
|
282
|
+
line-height: 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Improved list item layout */
|
|
286
|
+
li {
|
|
287
|
+
display: flex;
|
|
288
|
+
margin: 0;
|
|
289
|
+
padding: 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* Make the actions column more flexible */
|
|
294
|
+
.col-span-2.lg\\:col-span-3 {
|
|
295
|
+
min-width: 0;
|
|
296
|
+
flex-shrink: 0;
|
|
297
|
+
display: flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
justify-content: center;
|
|
300
|
+
padding: 0 2px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Compact status container */
|
|
304
|
+
.status-container {
|
|
305
|
+
border: none !important;
|
|
306
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
|
|
307
|
+
padding: 2px 4px;
|
|
308
|
+
font-size: 0.7rem;
|
|
309
|
+
max-width: 100%;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.status-text {
|
|
313
|
+
font-size: 0.65rem;
|
|
314
|
+
letter-spacing: 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Reduce row height to accommodate smaller elements */
|
|
318
|
+
.task-row-container {
|
|
319
|
+
min-height: 45px !important;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.task-id {
|
|
324
|
+
font-size: 10px;
|
|
325
|
+
font-weight: 600;
|
|
326
|
+
letter-spacing: 0.5px;
|
|
327
|
+
margin: 0;
|
|
328
|
+
color: oklch(0.65 0 0);
|
|
329
|
+
background: rgba(46, 47, 52, 0.4);
|
|
330
|
+
padding: 2px 6px;
|
|
331
|
+
border-radius: 4px;
|
|
332
|
+
border: 1px solid oklch(0.26 0 0);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.task-event-id {
|
|
336
|
+
font-size: 11px;
|
|
337
|
+
font-weight: 500;
|
|
338
|
+
text-align: center;
|
|
339
|
+
color: oklch(0.82 0 0);
|
|
340
|
+
|
|
341
|
+
&:hover {
|
|
342
|
+
color: #ffffff;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@media (max-width: 1024px) {
|
|
346
|
+
font-size: 10px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
@media (max-width: 768px) {
|
|
350
|
+
font-size: 9px;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* Responsive task styling */
|
|
355
|
+
@screen lg {
|
|
356
|
+
h4 {
|
|
357
|
+
font-size: 10px;
|
|
358
|
+
}
|
|
359
|
+
|
|
214
360
|
.task-id {
|
|
215
|
-
font-size:
|
|
361
|
+
font-size: 8px;
|
|
216
362
|
}
|
|
217
|
-
|
|
218
|
-
|
|
363
|
+
|
|
364
|
+
.task-event-id {
|
|
365
|
+
font-size: 10px;
|
|
219
366
|
}
|
|
220
367
|
}
|
|
221
368
|
</style>
|
|
369
|
+
|
|
222
370
|
<script setup>
|
|
371
|
+
/// <reference path="@/types/index.js" />
|
|
372
|
+
|
|
223
373
|
import { Row } from "@/components/Table";
|
|
224
|
-
import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/components/icons";
|
|
374
|
+
import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon, EyeIcon } from "@/components/icons";
|
|
225
375
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
226
376
|
import { useUIStore } from "@/stores/ui";
|
|
227
377
|
import TaskLabel from "@/components/Tasks/TaskLabel.vue";
|
|
228
|
-
import
|
|
378
|
+
import ViewTask from "@/components/Tasks/ViewTask.vue";
|
|
379
|
+
import { computed, ref, onMounted, onUnmounted, nextTick } from "vue";
|
|
229
380
|
|
|
230
381
|
const ui = useUIStore();
|
|
231
382
|
|
|
383
|
+
/** @type {{ task: Task }} */
|
|
232
384
|
const props = defineProps({
|
|
233
385
|
task: { type: Object }
|
|
234
386
|
});
|
|
235
387
|
|
|
388
|
+
// Context menu positioning
|
|
389
|
+
const contextMenuPosition = ref({});
|
|
390
|
+
const contextMenuRef = ref(null);
|
|
391
|
+
|
|
392
|
+
// Handle right-click to position context menu
|
|
393
|
+
const handleRightClick = (event) => {
|
|
394
|
+
const menuWidth = 168; // w-42 = 10.5rem = 168px
|
|
395
|
+
const menuHeight = 200; // Approximate height
|
|
396
|
+
|
|
397
|
+
let x = event.clientX;
|
|
398
|
+
let y = event.clientY - 55;
|
|
399
|
+
|
|
400
|
+
// Prevent menu from going off screen
|
|
401
|
+
if (x + menuWidth > window.innerWidth) {
|
|
402
|
+
x = event.clientX - menuWidth; // Show to the left instead
|
|
403
|
+
}
|
|
404
|
+
if (y + menuHeight > window.innerHeight) {
|
|
405
|
+
y = event.clientY - menuHeight; // Show above instead
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
contextMenuPosition.value = {
|
|
409
|
+
left: `${x}px`,
|
|
410
|
+
top: `${y}px`
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// Open the context menu for this task
|
|
414
|
+
ui.setOpenContextMenu(props.task.taskId);
|
|
415
|
+
|
|
416
|
+
// Add click outside listener after menu opens
|
|
417
|
+
nextTick(() => {
|
|
418
|
+
document.addEventListener("click", handleClickOutside);
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// Handle clicking outside the context menu
|
|
423
|
+
const handleClickOutside = (event) => {
|
|
424
|
+
if (contextMenuRef.value && !contextMenuRef.value.contains(event.target)) {
|
|
425
|
+
ui.setOpenContextMenu("");
|
|
426
|
+
document.removeEventListener("click", handleClickOutside);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Cleanup on unmount
|
|
431
|
+
onUnmounted(() => {
|
|
432
|
+
document.removeEventListener("click", handleClickOutside);
|
|
433
|
+
});
|
|
434
|
+
|
|
236
435
|
const copy = (txt) => {
|
|
237
436
|
if (!txt) return;
|
|
238
437
|
navigator.clipboard.writeText(txt);
|
|
239
438
|
ui.showSuccess("Copied text");
|
|
240
439
|
};
|
|
241
440
|
|
|
441
|
+
const openViewTaskModal = () => {
|
|
442
|
+
ui.selectedTaskForView = props.task;
|
|
443
|
+
ui.toggleModal('view-task');
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const isTotalPrice = (line, index, lines) => {
|
|
447
|
+
const trimmed = line.trim();
|
|
448
|
+
if (!trimmed) return false;
|
|
449
|
+
|
|
450
|
+
// Check if this is the last non-empty line
|
|
451
|
+
const nonEmptyLines = lines.filter(l => l.trim());
|
|
452
|
+
const isLastLine = index === lines.lastIndexOf(nonEmptyLines[nonEmptyLines.length - 1]);
|
|
453
|
+
|
|
454
|
+
if (!isLastLine) return false;
|
|
455
|
+
|
|
456
|
+
// Check if line is a standalone price (not in parentheses)
|
|
457
|
+
// Matches: $345.88, €345.88, £345.88, ¥345.88, etc.
|
|
458
|
+
// Does NOT match: ($86.47) or 2× 301/E ($86.47)
|
|
459
|
+
const totalPricePattern = /^[$€£¥₹₽¢]\s*[\d,]+\.?\d*$/;
|
|
460
|
+
return totalPricePattern.test(trimmed) && !trimmed.includes('(') && !trimmed.includes(')');
|
|
461
|
+
};
|
|
462
|
+
|
|
242
463
|
const colorMapping = new Map();
|
|
243
464
|
colorMapping.set("green", "bg-green-400");
|
|
244
465
|
colorMapping.set("red", "bg-red-400");
|
|
@@ -248,23 +469,18 @@ const colorToClass = (color) => {
|
|
|
248
469
|
return colorMapping.get(color) || "bg-white";
|
|
249
470
|
};
|
|
250
471
|
|
|
251
|
-
const truncate = (text, after) => {
|
|
252
|
-
if (text?.length <= after || after === -1) return text;
|
|
253
|
-
return text?.substring(0, after) + "...";
|
|
254
|
-
};
|
|
255
|
-
|
|
256
472
|
const openInBrowser = (debug) => {
|
|
257
473
|
if (!props.task.openerLink) return;
|
|
258
474
|
ui.showSuccess(`Opening in browser ${debug ? "(debug)" : ""}`);
|
|
259
475
|
const input = props.task.openerLink;
|
|
260
|
-
const data = JSON.parse(atob(input.split("://")
|
|
476
|
+
const data = JSON.parse(atob(input.split("://").pop()));
|
|
261
477
|
data.config.debug = debug;
|
|
262
478
|
const out = "necro://" + btoa(JSON.stringify(data));
|
|
263
479
|
openInNewTab(out);
|
|
264
480
|
};
|
|
265
481
|
|
|
266
482
|
const formatDate = (date) => {
|
|
267
|
-
if (!date) return "
|
|
483
|
+
if (!date) return "TBA";
|
|
268
484
|
const d = new Date(date);
|
|
269
485
|
const iso = d.toISOString();
|
|
270
486
|
const [year, month, day] = iso.substring(0, 10).split("-");
|
|
@@ -285,15 +501,4 @@ const openInNewTab = (href) => {
|
|
|
285
501
|
href: href
|
|
286
502
|
}).click();
|
|
287
503
|
};
|
|
288
|
-
|
|
289
|
-
const getMaxStatusLength = (width) => {
|
|
290
|
-
if (width > 1279) return -1;
|
|
291
|
-
if (width > 767) return 25;
|
|
292
|
-
if (width > 639) return 30;
|
|
293
|
-
if (width > 540) return 18;
|
|
294
|
-
return 13;
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
let statusTruncateLength = ref(getMaxStatusLength(window.innerWidth));
|
|
298
|
-
window.addEventListener("resize", () => (statusTruncateLength.value = getMaxStatusLength(window.innerWidth)));
|
|
299
504
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex rounded-2xl gap-x-2 w-
|
|
3
|
-
<img class="ml-3" v-if="logo" :src="logo" />
|
|
2
|
+
<div class="flex rounded-2xl gap-x-2 max-w-full shadow-3xl mx-auto items-center justify-center bg-dark-600">
|
|
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>
|
|
6
6
|
</div>
|