@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.
Files changed (140) hide show
  1. package/.prettierrc +27 -1
  2. package/.vscode/extensions.json +1 -1
  3. package/README.md +64 -2
  4. package/artwork/image.png +0 -0
  5. package/backend/api.js +26 -24
  6. package/backend/auth.js +2 -2
  7. package/backend/batching.js +1 -1
  8. package/backend/endpoints.js +8 -11
  9. package/backend/index.js +2 -2
  10. package/backend/mock-data.js +27 -36
  11. package/backend/mock-src/classes/logger.js +5 -7
  12. package/backend/mock-src/classes/utils.js +3 -2
  13. package/backend/mock-src/ticketmaster.js +4 -4
  14. package/backend/validator.js +2 -2
  15. package/config/configs.json +0 -1
  16. package/dev-server.js +134 -0
  17. package/exit +209 -0
  18. package/index.html +78 -8
  19. package/index.js +1 -1
  20. package/jsconfig.json +16 -0
  21. package/package.json +39 -25
  22. package/postcss.config.js +1 -1
  23. package/postinstall.js +124 -20
  24. package/public/android-chrome-192x192.png +0 -0
  25. package/public/android-chrome-512x512.png +0 -0
  26. package/public/apple-touch-icon.png +0 -0
  27. package/public/favicon-16x16.png +0 -0
  28. package/public/favicon-32x32.png +0 -0
  29. package/public/favicon.ico +0 -0
  30. package/public/img/logo_trans.png +0 -0
  31. package/public/img/necro_logo.png +0 -0
  32. package/public/manifest.json +16 -10
  33. package/run +176 -9
  34. package/src/App.vue +498 -85
  35. package/src/assets/css/base/reset.scss +43 -0
  36. package/src/assets/css/base/scroll.scss +114 -0
  37. package/src/assets/css/base/typography.scss +37 -0
  38. package/src/assets/css/components/buttons.scss +216 -0
  39. package/src/assets/css/components/forms.scss +221 -0
  40. package/src/assets/css/components/modals.scss +13 -0
  41. package/src/assets/css/components/tables.scss +27 -0
  42. package/src/assets/css/components/toasts.scss +100 -0
  43. package/src/assets/css/main.scss +201 -122
  44. package/src/assets/img/background.svg +2 -2
  45. package/src/assets/img/background.svg.backup +11 -0
  46. package/src/assets/img/logo_trans.png +0 -0
  47. package/src/components/Auth/LoginForm.vue +62 -11
  48. package/src/components/Editors/Account/Account.vue +116 -40
  49. package/src/components/Editors/Account/AccountCreator.vue +88 -39
  50. package/src/components/Editors/Account/AccountView.vue +102 -34
  51. package/src/components/Editors/Account/CreateAccount.vue +80 -32
  52. package/src/components/Editors/Profile/CreateProfile.vue +269 -83
  53. package/src/components/Editors/Profile/Profile.vue +132 -47
  54. package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
  55. package/src/components/Editors/Profile/ProfileView.vue +89 -32
  56. package/src/components/Editors/TagLabel.vue +67 -6
  57. package/src/components/Editors/TagToggle.vue +7 -2
  58. package/src/components/Filter/Filter.vue +288 -71
  59. package/src/components/Filter/FilterPreview.vue +202 -31
  60. package/src/components/Filter/PriceSortToggle.vue +76 -6
  61. package/src/components/Table/Header.vue +1 -1
  62. package/src/components/Table/Row.vue +1 -1
  63. package/src/components/Table/Table.vue +19 -2
  64. package/src/components/Tasks/CheckStock.vue +6 -8
  65. package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
  66. package/src/components/Tasks/Controls/MobileControls.vue +8 -45
  67. package/src/components/Tasks/CreateTaskAXS.vue +80 -72
  68. package/src/components/Tasks/CreateTaskTM.vue +95 -141
  69. package/src/components/Tasks/MassEdit.vue +4 -6
  70. package/src/components/Tasks/QuickSettings.vue +199 -30
  71. package/src/components/Tasks/ScrapeVenue.vue +5 -6
  72. package/src/components/Tasks/Stats.vue +50 -24
  73. package/src/components/Tasks/Task.vue +384 -179
  74. package/src/components/Tasks/TaskLabel.vue +2 -2
  75. package/src/components/Tasks/TaskView.vue +136 -48
  76. package/src/components/Tasks/Utilities.vue +25 -10
  77. package/src/components/Tasks/ViewTask.vue +321 -0
  78. package/src/components/icons/Bag.vue +1 -1
  79. package/src/components/icons/Check.vue +5 -0
  80. package/src/components/icons/Close.vue +21 -0
  81. package/src/components/icons/CloseX.vue +5 -0
  82. package/src/components/icons/Eye.vue +6 -0
  83. package/src/components/icons/Key.vue +21 -0
  84. package/src/components/icons/Loyalty.vue +1 -1
  85. package/src/components/icons/Mail.vue +2 -2
  86. package/src/components/icons/Pencil.vue +21 -0
  87. package/src/components/icons/Play.vue +2 -2
  88. package/src/components/icons/Profile.vue +18 -0
  89. package/src/components/icons/Reload.vue +4 -5
  90. package/src/components/icons/Sandclock.vue +2 -2
  91. package/src/components/icons/Sell.vue +21 -0
  92. package/src/components/icons/Spinner.vue +42 -0
  93. package/src/components/icons/SquareCheck.vue +18 -0
  94. package/src/components/icons/SquareUncheck.vue +18 -0
  95. package/src/components/icons/Stadium.vue +1 -1
  96. package/src/components/icons/Wildcard.vue +18 -0
  97. package/src/components/icons/index.js +26 -1
  98. package/src/components/ui/Modal.vue +107 -13
  99. package/src/components/ui/Navbar.vue +175 -40
  100. package/src/components/ui/ReconnectIndicator.vue +351 -55
  101. package/src/components/ui/Splash.vue +5 -35
  102. package/src/components/ui/controls/CountryChooser.vue +200 -62
  103. package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
  104. package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
  105. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  106. package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
  107. package/src/components/ui/controls/atomic/Switch.vue +53 -25
  108. package/src/composables/useClickOutside.js +21 -0
  109. package/src/composables/useDropdownPosition.js +174 -0
  110. package/src/libs/Filter.js +60 -24
  111. package/src/registerServiceWorker.js +1 -1
  112. package/src/stores/connection.js +4 -4
  113. package/src/stores/sampleData.js +172 -199
  114. package/src/stores/ui.js +55 -20
  115. package/src/stores/utils.js +30 -4
  116. package/src/types/index.js +41 -0
  117. package/src/utils/debug.js +1 -0
  118. package/src/views/Accounts.vue +116 -50
  119. package/src/views/Console.vue +394 -79
  120. package/src/views/Editor.vue +1176 -123
  121. package/src/views/FilterBuilder.vue +528 -250
  122. package/src/views/Login.vue +76 -14
  123. package/src/views/Profiles.vue +119 -34
  124. package/src/views/Tasks.vue +266 -98
  125. package/static/offline.html +192 -50
  126. package/switch-branch.sh +41 -0
  127. package/tailwind.config.js +119 -27
  128. package/vite.config.js +73 -16
  129. package/workbox-config.cjs +63 -0
  130. package/ICONS.md +0 -21
  131. package/public/img/background.svg +0 -14
  132. package/public/img/logo.png +0 -0
  133. package/public/img/logo_icon.png +0 -0
  134. package/public/img/logo_icon_2.png +0 -0
  135. package/src/assets/css/_input.scss +0 -143
  136. package/src/assets/img/logo.png +0 -0
  137. package/src/assets/img/logo_icon.png +0 -0
  138. package/src/assets/img/logo_icon_2.png +0 -0
  139. package/vue.config.js +0 -32
  140. package/workbox-config.js +0 -7
@@ -1,244 +1,465 @@
1
1
  <template>
2
- <Row
3
- class="relative text-white"
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-0 mr-4"
5
+ class="ml-2 mr-4 flex-shrink-0"
15
6
  :toggled="props.task.selected"
16
- @valueUpdate="ui.toggleTaskSelected(props.task.taskId)"
17
- />
18
- <h4 class="task-id-alt mx-auto hidden md:block text-white" @click="copy(props.task.eventId)">
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 md:block">
23
- <h4 class="text-white">{{ props.task.quantity }}</h4>
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(' | ')" :key="l">
30
- <span v-if="!!l.trim()">{{ l.trim() }}</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="ml-1 font-bold"
35
- :class="[
36
- props.task._timeLeftString == '00:00' || props.task._timeLeftString == 'No Cartholds'
37
- ? 'text-red-400'
38
- : ''
39
- ]"
40
- >{{ props.task._timeLeftString !== "00:00" ? props.task._timeLeftString : "Expired" }}</span
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-6 md:col-span-4 lg:col-span-3 text-center justify-center">
45
- <div class="flex md:gap-x-2 rounded-2xl w-fit shadow-3xl mx-auto items-center justify-center bg-dark-600">
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="w-2 h-2 rounded-full mx-1 md:ml-3 ml-2"
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
- ></div>
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 bg-dark-600 px-1 lg:px-2 rounded-full shadow-3xl items-center">
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 class="p-1" v-if="task.active" @click="ui.stopTask(task.taskId)">
53
+ <button v-if="task.active" @click="ui.stopTask(task.taskId)">
67
54
  <PauseIcon />
68
55
  </button>
69
- <button class="p-1" v-else @click="ui.startTask(task.taskId)">
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() === 'waiting' && props.task._timeLeftString !== '00:00'">
74
- <button class="p-1" @click="ui.continueTask(task.taskId, 'autocheckout')">
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() === 'waiting'">
79
- <button class="p-1" @click="ui.continueTask(task.taskId, 'change_reservation')">
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 class="p-1" @click="ui.deleteTask(task.taskId)">
71
+ <button @click="ui.deleteTask(task.taskId)">
85
72
  <TrashIcon />
86
73
  </button>
87
74
  </li>
88
- <li
89
- class="text-xl -mt-2"
90
- @click.right.prevent="window.setTimeout(() => ui.setOpenContextMenu(task.taskId), 10)"
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="hidden md:block col-span-1 absolute right-5 top-4 lg:flex items-center justify-center">
100
- <h4 class="text-center text-xs task-id text-white">
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
- 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"
107
- v-if="props.task.isExpanded"
108
- >
109
- <!-- <div class="flex gap-x-2 " @click="copy(props.task.eventId)">
110
- <StadiumWhiteIcon />
111
- <h4 class="text-white">{{ props.task.eventId }}</h4>
112
- </div>
113
- <div class="flex gap-x-2 md:hidden">
114
- <BagWhiteIcon />
115
- <h4 class="text-white">{{ props.task.quantity }}</h4>
116
- </div> -->
117
-
118
- <!-- Details -->
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
- @media (max-width: 1024px) {
208
- h4 {
209
- font-size: 10px !important;
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
- @apply gap-x-3;
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: 6px !important;
361
+ font-size: 8px;
216
362
  }
217
- .task-id-alt {
218
- font-size: 7px !important;
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 { ref } from "vue";
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("://")[1]));
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-fit shadow-3xl mx-auto items-center justify-center bg-dark-600">
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>