@necrolab/dashboard 0.5.22 → 0.5.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
@@ -8,13 +8,13 @@
8
8
  "bot": "node dev-server.js",
9
9
  "expose": "node dev-server.js",
10
10
  "postinstall": "node postinstall.js",
11
- "updaterenderer": "npm i @necrolab/tm-renderer@latest",
12
11
  "preview": "vite preview",
13
- "lint": "npx eslint src/ --fix"
12
+ "lint": "npx eslint src/ --fix",
13
+ "release": "npm version patch && npm i && npm publish",
14
+ "release:minor": "npm version minor && npm i && npm publish"
14
15
  },
15
16
  "dependencies": {
16
17
  "@msgpack/msgpack": "^3.0.0-beta2",
17
- "@necrolab/tm-renderer": "^0.1.12",
18
18
  "@vitejs/plugin-vue": "^5.2.1",
19
19
  "@vueuse/core": "^11.3.0",
20
20
  "autoprefixer": "^10.4.21",
@@ -30,7 +30,7 @@
30
30
  </h4>
31
31
  </div>
32
32
 
33
- <div class="col-span-1 flex">
33
+ <div class="col-span-1 flex items-center justify-center">
34
34
  <ActionButtonGroup>
35
35
  <li>
36
36
  <button @click="edit">
@@ -8,8 +8,17 @@
8
8
 
9
9
  <div>
10
10
  <div class="my-3 grid grid-cols-12 gap-3">
11
+ <!-- Country chooser -->
12
+ <FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-4 md:col-span-2" noWrapper>
13
+ <ProfileCountryChooser
14
+ class="h-10"
15
+ :value="formProfile.country"
16
+ :onClick="chooseCountry"
17
+ :disabled="true" />
18
+ </FormField>
19
+
11
20
  <!-- Profile tag -->
12
- <FormField label="Profile Tag" :icon="TagIcon" z-index="0" class="col-span-12 md:col-span-4" noWrapper>
21
+ <FormField label="Profile Tag" :icon="TagIcon" z-index="0" class="col-span-8 md:col-span-4" noWrapper>
13
22
  <Dropdown
14
23
  :class="`input-default dropdown w-full`"
15
24
  :default="ui.profile.tags[0]"
@@ -19,7 +28,7 @@
19
28
  </FormField>
20
29
 
21
30
  <!-- Card Number -->
22
- <FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-8">
31
+ <FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-6">
23
32
  <input
24
33
  ref="cardNumberInput"
25
34
  placeholder="Enter card number"
@@ -30,15 +39,6 @@
30
39
  @focus="formatCardNumberDisplay" />
31
40
  </FormField>
32
41
 
33
- <!-- Country chooser -->
34
- <FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-12 md:col-span-2" noWrapper>
35
- <ProfileCountryChooser
36
- class="h-10"
37
- :value="formProfile.country"
38
- :onClick="chooseCountry"
39
- :disabled="true" />
40
- </FormField>
41
-
42
42
  <!-- Exp Year -->
43
43
  <FormField label="Expiry Year" :icon="TimerIcon" :error="errors.includes('expYear')" z-index="0" class="col-span-6 md:col-span-5" noWrapper>
44
44
  <Dropdown
@@ -10,6 +10,10 @@
10
10
  <div class="grid grid-cols-12 items-center py-3 px-2 sm:px-4 gap-2 sm:gap-3">
11
11
  <div class="col-span-9 sm:col-span-10">
12
12
  <div class="flex-gap-2 items-center sm:gap-3 cursor-pointer flex-1" @click="handleFilterClick(filter.id)">
13
+ <div
14
+ class="filter-color-dot flex-shrink-0"
15
+ :style="{ backgroundColor: color }">
16
+ </div>
13
17
  <div class="filter-type-badge flex-shrink-0">
14
18
  <component :is="getFilterIcon()" class="w-3 h-3 sm:w-4 sm:h-4" />
15
19
  </div>
@@ -169,6 +173,10 @@ const props = defineProps({
169
173
  filterBuilder: {
170
174
  type: Object,
171
175
  required: true
176
+ },
177
+ color: {
178
+ type: String,
179
+ default: "#7bc999"
172
180
  }
173
181
  });
174
182
 
@@ -307,6 +315,11 @@ props.filterBuilder.onUpdate(() => {
307
315
  @apply border-t border-t-dark-625/20;
308
316
  }
309
317
 
318
+ .filter-color-dot {
319
+ @apply w-2.5 h-2.5 rounded-full flex-shrink-0;
320
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
321
+ }
322
+
310
323
  .filter-type-badge {
311
324
  @apply w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-dark-400 flex items-center justify-center flex-shrink-0;
312
325
  }
@@ -1,24 +1,26 @@
1
1
  <template>
2
2
  <Row
3
- class="relative grid-cols-10 gap-2 text-white lg:grid-cols-12 min-h-full"
3
+ class="relative min-h-full grid-cols-10 gap-2 text-white lg:grid-cols-12"
4
4
  @click="ui.setOpenContextMenu('')"
5
5
  @dblclick="handleDoubleClick"
6
6
  @touchstart="handleTouchStart"
7
7
  @touchend="handleTouchEnd">
8
- <div class="col-span-1 flex items-center justify-start lg:col-span-2 py-2">
8
+ <div class="col-span-1 flex items-center justify-start py-2 lg:col-span-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="hidden lg:flex flex-col gap-0.5 justify-center cursor-pointer max-w-full min-w-0"
15
+ class="hidden min-w-0 max-w-full cursor-pointer flex-col justify-center gap-0.5 lg:flex"
16
16
  @click="copy(props.task.eventId, 'Copied event ID')"
17
17
  :title="`Event ID: ${props.task.eventId}`">
18
- <div class="max-w-45 truncate text-xs+ font-semibold leading-tight text-white">
18
+ <div class="max-w-45 text-xs+ truncate font-semibold leading-tight text-white">
19
19
  {{ props.task.eventName }}
20
20
  </div>
21
- <EventDetailRow :content="[props.task.venueName, props.task.eventCity].filter(Boolean).join(', ')" truncate>
21
+ <EventDetailRow
22
+ :content="[props.task.venueName, props.task.eventCity].filter(Boolean).join(', ')"
23
+ truncate>
22
24
  <template #icon>
23
25
  <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
24
26
  <circle cx="12" cy="10" r="3"></circle>
@@ -32,26 +34,46 @@
32
34
  <line x1="3" y1="10" x2="21" y2="10"></line>
33
35
  </template>
34
36
  </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">
37
+ <div v-if="props.task.email" class="text-3xs leading-tight-sm min-h-2.75 flex items-start gap-1">
38
+ <svg
39
+ class="icon-sm"
40
+ width="10"
41
+ height="10"
42
+ fill="none"
43
+ stroke="currentColor"
44
+ stroke-width="2"
45
+ viewBox="0 0 24 24">
37
46
  <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>
38
47
  <polyline points="22,6 12,13 2,6"></polyline>
39
48
  </svg>
40
- <span class="truncate text-light-500 text-3xs leading-tight-sm">{{ props.task.email }}</span>
49
+ <span class="text-3xs leading-tight-sm truncate text-light-500">{{ props.task.email }}</span>
41
50
  </div>
42
51
  </div>
43
52
  <div
44
53
  v-else
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-gap-1 items-center 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">
54
+ class="hidden min-w-0 max-w-full cursor-pointer flex-col justify-center gap-0.5 lg:flex"
55
+ @click="
56
+ copy(
57
+ props.task.eventId || props.task.email,
58
+ props.task.eventId ? 'Copied event ID' : 'Copied email'
59
+ )
60
+ ">
61
+ <div
62
+ class="flex-gap-1 text-xs+ max-w-45 leading-tight-md items-center truncate font-semibold text-white">
63
+ <svg
64
+ class="icon-sm"
65
+ width="11"
66
+ height="11"
67
+ fill="none"
68
+ stroke="currentColor"
69
+ stroke-width="2.5"
70
+ viewBox="0 0 24 24">
49
71
  <path d="M9 11l3 3L22 4"></path>
50
72
  <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
51
73
  </svg>
52
74
  <span>{{ props.task.eventId || props.task.email }}</span>
53
75
  </div>
54
- <EventDetailRow v-if="props.task.eventId && props.task.email && props.task.eventId !== props.task.email" :content="props.task.email" truncate>
76
+ <EventDetailRow v-if="!props.task.eventId" :content="props.task.email" truncate>
55
77
  <template #icon>
56
78
  <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>
57
79
  <polyline points="22,6 12,13 2,6"></polyline>
@@ -60,19 +82,29 @@
60
82
  </div>
61
83
  </div>
62
84
  <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">
85
+ <h4 class="lg:text-2xs text-center text-xs leading-tight text-light-300">
64
86
  <span v-if="!props.task.reservedTicketsList">-</span>
65
87
  <div v-else class="overflow-hidden">
66
- <div v-for="(l, index) in props.task.reservedTicketsList.split('\n')" :key="`ticket-${index}`" class="truncate">
88
+ <div
89
+ v-for="(l, index) in props.task.reservedTicketsList.split('\n')"
90
+ :key="`ticket-${index}`"
91
+ class="truncate">
67
92
  <span
68
93
  v-if="!!l.trim()"
69
94
  class="text-xs"
70
- :class="{ 'text-green-400 font-bold': isTotalPrice(l, index, props.task.reservedTicketsList.split('\n')) }"
71
- >{{ l.trim() }}</span>
95
+ :class="{
96
+ 'font-bold text-green-400': isTotalPrice(
97
+ l,
98
+ index,
99
+ props.task.reservedTicketsList.split(' ')
100
+ )
101
+ }">
102
+ {{ l.trim() }}
103
+ </span>
72
104
  </div>
73
105
  </div>
74
106
  <span
75
- class="block mt-1 font-bold text-xs"
107
+ class="mt-1 block text-xs font-bold"
76
108
  :class="{
77
109
  'text-red-400':
78
110
  props.task._timeLeftString === '00:00' || props.task._timeLeftString === 'No Cartholds'
@@ -84,7 +116,7 @@
84
116
  <div class="col-span-6 justify-center text-center md:col-span-5 lg:col-span-4 xl:col-span-5">
85
117
  <div class="task-status-badge">
86
118
  <div
87
- class="w-2 h-2 rounded-full flex-shrink-0"
119
+ class="h-2 w-2 flex-shrink-0 rounded-full"
88
120
  :class="
89
121
  colorToClass(
90
122
  props.task.active || props.task.status.toLowerCase() === 'idle'
@@ -92,10 +124,14 @@
92
124
  : 'red'
93
125
  )
94
126
  "></div>
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>
127
+ <span
128
+ class="max-md:text-xs max-md:max-w-full max-sm:tracking-normal truncate text-sm font-medium uppercase tracking-wide text-light-300">
129
+ {{ props.task.status }}
130
+ </span>
96
131
  </div>
97
132
  </div>
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">
133
+ <div
134
+ class="max-sm:min-w-0 max-sm:flex-shrink-0 max-sm:items-center max-sm:justify-end max-sm:px-0.5 col-span-1 flex overflow-visible md:col-span-2 lg:col-span-3">
99
135
  <ActionButtonGroup class="overflow-visible" :allowWrap="true">
100
136
  <li>
101
137
  <button v-if="task.active" @click="ui.stopTask(task.taskId)" aria-label="Stop task">
@@ -127,8 +163,10 @@
127
163
  </li>
128
164
  </ActionButtonGroup>
129
165
  </div>
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">
166
+ <div
167
+ class="max-sm:left-16 max-sm:top-1 max-sm:z-10 absolute right-5 top-4 col-span-1 hidden items-center justify-center xl:flex">
168
+ <h4
169
+ class="bg-dark-475/40 text-2xs lg:text-2xs m-0 rounded border border-dark-500 px-1.5 py-0.5 text-center text-xs font-semibold tracking-wide text-light-500">
132
170
  {{ props.task.taskId }}
133
171
  </h4>
134
172
  </div>
@@ -137,7 +175,7 @@
137
175
  <div
138
176
  v-if="ui.openContextMenu === task.taskId"
139
177
  ref="contextMenuRef"
140
- 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"
178
+ class="w-42 context-menu fixed z-max grid grid-cols-1 gap-1 rounded-lg border border-dark-650 bg-dark-500 p-1 text-white shadow-xl"
141
179
  :style="contextMenuPosition">
142
180
  <button class="btn-primary" @click="openInNewTab(`${ui.currentCountry.url}/event/${task.eventId}`)">
143
181
  Open Event
@@ -237,10 +275,9 @@ onUnmounted(() => {
237
275
  document.removeEventListener("click", handleClickOutside);
238
276
  });
239
277
 
240
-
241
278
  const openViewTaskModal = () => {
242
279
  ui.selectedTaskForView = props.task;
243
- ui.toggleModal('view-task');
280
+ ui.toggleModal("view-task");
244
281
  };
245
282
 
246
283
  const openInBrowser = (debug) => {
@@ -25,7 +25,7 @@ const disabledClasses = "bg-red-500/20 border-red-500/30";
25
25
  <template>
26
26
  <div
27
27
  :class="[
28
- 'flex-center rounded-full border-2 transition-all duration-200',
28
+ 'flex items-center justify-center rounded-full border-2 transition-all duration-200',
29
29
  enabled ? enabledClasses : disabledClasses,
30
30
  sizeClasses[size]
31
31
  ]">
@@ -19,7 +19,9 @@ export function useFilterCSS(filterBuilder, svg) {
19
19
  document.head.appendChild(styleElement);
20
20
  }
21
21
 
22
- const combinedCSS = filterBuilder.value.cssClasses + filterBuilder.value.temporaryCSS;
22
+ // Only use cssClasses now (permanent filter colors)
23
+ // temporaryCSS is no longer used - hover uses DOM classes instead
24
+ const combinedCSS = filterBuilder.value.cssClasses;
23
25
  if (styleElement.textContent !== combinedCSS) {
24
26
  styleElement.textContent = combinedCSS;
25
27
  }
@@ -35,17 +37,9 @@ export function useFilterCSS(filterBuilder, svg) {
35
37
  });
36
38
  };
37
39
 
38
- const originalHighlight = filterBuilder.value.highlight.bind(filterBuilder.value);
39
- filterBuilder.value.highlight = (...args) => {
40
- originalHighlight(...args);
41
- nextTick(() => injectStyles());
42
- };
43
-
44
- const originalClearHighlight = filterBuilder.value.clearHighlight.bind(filterBuilder.value);
45
- filterBuilder.value.clearHighlight = () => {
46
- originalClearHighlight();
47
- nextTick(() => injectStyles());
48
- };
40
+ // NOTE: highlight() and clearHighlight() now use DOM class manipulation
41
+ // They don't modify CSS, so we don't wrap them to inject styles
42
+ // This eliminates the flicker caused by style element updates
49
43
  };
50
44
 
51
45
  const cleanupStyles = () => {