@necrolab/dashboard 0.5.29 → 0.5.31
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 +1 -1
- package/public/reconnect-logo.png +0 -0
- package/src/components/Editors/Account/AccountView.vue +17 -1
- package/src/components/Editors/Profile/ProfileView.vue +17 -1
- package/src/components/Tasks/TaskView.vue +26 -10
- package/src/composables/useDynamicTableHeight.js +23 -6
- package/src/views/Accounts.vue +1 -1
- package/src/views/Profiles.vue +1 -1
- package/src/views/Tasks.vue +29 -1
package/package.json
CHANGED
|
Binary file
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
</Header>
|
|
32
32
|
<RecycleScroller
|
|
33
|
-
v-if="props.accounts.length !== 0"
|
|
33
|
+
v-if="props.accounts.length !== 0 && useVirtualScroller"
|
|
34
34
|
class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
|
|
35
35
|
:style="{ height: dynamicTableHeight, maxHeight: dynamicTableHeight }"
|
|
36
36
|
:items="virtualAccounts"
|
|
@@ -46,6 +46,20 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
</template>
|
|
48
48
|
</RecycleScroller>
|
|
49
|
+
<div
|
|
50
|
+
v-else-if="props.accounts.length !== 0"
|
|
51
|
+
class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
|
|
52
|
+
:style="{ maxHeight: dynamicTableHeight }">
|
|
53
|
+
<div
|
|
54
|
+
v-for="(item, index) in virtualAccounts"
|
|
55
|
+
:key="item.virtualKey"
|
|
56
|
+
class="min-h-16 flex-shrink-0 hover:bg-dark-550">
|
|
57
|
+
<Account
|
|
58
|
+
:class="getRowClass(index)"
|
|
59
|
+
:account="item.account"
|
|
60
|
+
:privacy="props.privacy" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
49
63
|
<EmptyState v-else :icon="MailIcon" message="No accounts found" subtitle="Create accounts to get started" />
|
|
50
64
|
</div>
|
|
51
65
|
</template>
|
|
@@ -89,6 +103,8 @@ const virtualAccounts = computed(() =>
|
|
|
89
103
|
virtualKey: String(account.id ?? account.email ?? "account")
|
|
90
104
|
}))
|
|
91
105
|
);
|
|
106
|
+
const ACCOUNT_VIRTUALIZE_THRESHOLD = 80;
|
|
107
|
+
const useVirtualScroller = computed(() => props.accounts.length > ACCOUNT_VIRTUALIZE_THRESHOLD);
|
|
92
108
|
|
|
93
109
|
const handleVirtualWheel = (event) => {
|
|
94
110
|
const target = event.currentTarget;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
</div>
|
|
35
35
|
</Header>
|
|
36
36
|
<RecycleScroller
|
|
37
|
-
v-if="props.profiles.length !== 0"
|
|
37
|
+
v-if="props.profiles.length !== 0 && useVirtualScroller"
|
|
38
38
|
class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
|
|
39
39
|
:style="{ height: dynamicTableHeight, maxHeight: dynamicTableHeight }"
|
|
40
40
|
:items="virtualProfiles"
|
|
@@ -50,6 +50,20 @@
|
|
|
50
50
|
</div>
|
|
51
51
|
</template>
|
|
52
52
|
</RecycleScroller>
|
|
53
|
+
<div
|
|
54
|
+
v-else-if="props.profiles.length !== 0"
|
|
55
|
+
class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
|
|
56
|
+
:style="{ maxHeight: dynamicTableHeight }">
|
|
57
|
+
<div
|
|
58
|
+
v-for="(item, index) in virtualProfiles"
|
|
59
|
+
:key="item.virtualKey"
|
|
60
|
+
class="min-h-16 flex-shrink-0 hover:bg-dark-550">
|
|
61
|
+
<Profile
|
|
62
|
+
:class="getRowClass(index)"
|
|
63
|
+
:profile="item.profile"
|
|
64
|
+
:privacy="props.privacy" />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
53
67
|
<EmptyState v-else :icon="ProfileIcon" message="No profiles found" subtitle="Create profiles to get started" />
|
|
54
68
|
</div>
|
|
55
69
|
</template>
|
|
@@ -88,6 +102,8 @@ const virtualProfiles = computed(() =>
|
|
|
88
102
|
virtualKey: String(profile.id ?? `${profile.profileName ?? "profile"}-${profile.cardNumber ?? ""}`)
|
|
89
103
|
}))
|
|
90
104
|
);
|
|
105
|
+
const PROFILE_VIRTUALIZE_THRESHOLD = 80;
|
|
106
|
+
const useVirtualScroller = computed(() => props.profiles.length > PROFILE_VIRTUALIZE_THRESHOLD);
|
|
91
107
|
|
|
92
108
|
const handleVirtualWheel = (event) => {
|
|
93
109
|
const target = event.currentTarget;
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
</Header>
|
|
37
37
|
<DynamicScroller
|
|
38
|
-
v-if="virtualTaskItems.length"
|
|
38
|
+
v-if="virtualTaskItems.length && useVirtualScroller"
|
|
39
39
|
class="hidden-scrollbars touch-pan-y min-h-0 overflow-y-auto overflow-x-hidden scrollable"
|
|
40
|
-
:style="{ height:
|
|
40
|
+
:style="{ height: maxTableHeight, maxHeight: maxTableHeight }"
|
|
41
41
|
:items="virtualTaskItems"
|
|
42
42
|
:min-item-size="virtualMinItemSize"
|
|
43
43
|
:buffer="virtualBuffer"
|
|
@@ -66,10 +66,25 @@
|
|
|
66
66
|
</DynamicScrollerItem>
|
|
67
67
|
</template>
|
|
68
68
|
</DynamicScroller>
|
|
69
|
+
<div
|
|
70
|
+
v-else-if="virtualTaskItems.length"
|
|
71
|
+
class="hidden-scrollbars touch-pan-y min-h-0 overflow-y-auto overflow-x-hidden scrollable"
|
|
72
|
+
:style="{ maxHeight: maxTableHeight }">
|
|
73
|
+
<div
|
|
74
|
+
v-for="(item, index) in virtualTaskItems"
|
|
75
|
+
:key="item.taskId"
|
|
76
|
+
class="shrink-0 border-b border-dark-650 min-h-14.5 md:min-h-17.25 has-[.event-details]:min-h-18.75 mobile-portrait:min-h-12.5 transition-colors duration-150 ease-in-out hover:!bg-dark-550">
|
|
77
|
+
<Task
|
|
78
|
+
v-if="props.tasks[item.taskId]"
|
|
79
|
+
:task="props.tasks[item.taskId]"
|
|
80
|
+
:preferEventName="props.preferEventName"
|
|
81
|
+
:class="getRowClass(index)" />
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
69
84
|
<div
|
|
70
85
|
v-else
|
|
71
86
|
class="empty-state flex flex-col items-center justify-center py-8 text-center bg-dark-400 text-light-500 text-sm font-medium"
|
|
72
|
-
:style="{ minHeight:
|
|
87
|
+
:style="{ minHeight: emptyStateHeight }">
|
|
73
88
|
<div
|
|
74
89
|
v-if="
|
|
75
90
|
!ui.queueStats.queued && !ui.queueStats.sleeping && ui.queueStats.nextQueuePasses.length === 0
|
|
@@ -237,6 +252,9 @@ onUnmounted(() => {
|
|
|
237
252
|
|
|
238
253
|
const virtualMinItemSize = computed(() => (windowWidth.value <= 768 ? 58 : 69));
|
|
239
254
|
const virtualBuffer = computed(() => virtualMinItemSize.value * 8);
|
|
255
|
+
const TASK_VIRTUALIZE_THRESHOLD = 80;
|
|
256
|
+
const useVirtualScroller = computed(() => virtualTaskItems.value.length > TASK_VIRTUALIZE_THRESHOLD);
|
|
257
|
+
const emptyStateHeight = computed(() => `${virtualMinItemSize.value * 2}px`);
|
|
240
258
|
|
|
241
259
|
const handleVirtualWheel = (event) => {
|
|
242
260
|
const target = event.currentTarget;
|
|
@@ -244,7 +262,7 @@ const handleVirtualWheel = (event) => {
|
|
|
244
262
|
target.scrollTop += event.deltaY;
|
|
245
263
|
};
|
|
246
264
|
|
|
247
|
-
const
|
|
265
|
+
const maxTableHeight = computed(() => {
|
|
248
266
|
// Detect PWA mode (standalone display)
|
|
249
267
|
const isPWA = window.matchMedia('(display-mode: standalone)').matches;
|
|
250
268
|
|
|
@@ -280,12 +298,10 @@ const dynamicTableHeight = computed(() => {
|
|
|
280
298
|
|
|
281
299
|
// Calculate row height based on screen size
|
|
282
300
|
const rowHeight = windowWidth.value <= 768 ? 58 : 69; // Mobile vs desktop row height
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight) + 1;
|
|
288
|
-
const exactHeight = maxCompleteRows * rowHeight;
|
|
301
|
+
const safeAvailableHeight = Math.max(availableHeight, rowHeight * 2);
|
|
302
|
+
const desktopBonusRows = windowWidth.value >= 1024 ? 1 : 0;
|
|
303
|
+
const maxRowsThatFit = Math.max(1, Math.floor(safeAvailableHeight / rowHeight) + desktopBonusRows);
|
|
304
|
+
const exactHeight = maxRowsThatFit * rowHeight;
|
|
289
305
|
|
|
290
306
|
return exactHeight + "px";
|
|
291
307
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
2
|
import { useWindowDimensions } from "./useWindowDimensions";
|
|
3
3
|
|
|
4
|
-
export function useDynamicTableHeight(options = {}) {
|
|
4
|
+
export function useDynamicTableHeight(options = {}, itemCount = null) {
|
|
5
5
|
const { windowHeight, windowWidth } = useWindowDimensions();
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
topReservedSpace = options.TOP_RESERVED_SPACE ?? 243,
|
|
9
9
|
bottomBuffer = options.BOTTOM_BUFFER ?? 16,
|
|
10
10
|
rowHeight = options.ROW_HEIGHT ?? 64,
|
|
11
|
-
minRowsToShow = options.MIN_ROWS_TO_SHOW ?? 2
|
|
11
|
+
minRowsToShow = options.MIN_ROWS_TO_SHOW ?? 2,
|
|
12
|
+
minRowsWhenEmpty = options.MIN_ROWS_WHEN_EMPTY ?? minRowsToShow
|
|
12
13
|
} = options;
|
|
13
14
|
|
|
14
15
|
const dynamicTableHeight = computed(() => {
|
|
@@ -22,9 +23,25 @@ export function useDynamicTableHeight(options = {}) {
|
|
|
22
23
|
const extraBuffer = isPWA && isMobile ? (isIPhonePortrait ? 24 : 40) : 0;
|
|
23
24
|
|
|
24
25
|
const availableHeight = windowHeight.value - topReservedSpace - bottomBuffer - extraBuffer;
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const safeAvailableHeight = Math.max(availableHeight, rowHeight);
|
|
27
|
+
const maxRowsThatFit = Math.max(1, Math.floor(safeAvailableHeight / rowHeight));
|
|
28
|
+
|
|
29
|
+
if (itemCount === null || itemCount === undefined) {
|
|
30
|
+
const minRows = Math.max(1, minRowsToShow);
|
|
31
|
+
const exactHeight = Math.max(minRows, maxRowsThatFit) * rowHeight;
|
|
32
|
+
return exactHeight + "px";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const count = Number(unref(itemCount) || 0);
|
|
36
|
+
let targetRows = Math.max(1, minRowsWhenEmpty);
|
|
37
|
+
|
|
38
|
+
if (count > 0) {
|
|
39
|
+
targetRows = Math.max(1, Math.min(count, maxRowsThatFit));
|
|
40
|
+
} else {
|
|
41
|
+
targetRows = Math.min(maxRowsThatFit, Math.max(1, minRowsWhenEmpty));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const exactHeight = targetRows * rowHeight;
|
|
28
45
|
|
|
29
46
|
return exactHeight + "px";
|
|
30
47
|
});
|
package/src/views/Accounts.vue
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="page-header-card">
|
|
5
5
|
<MailIcon />
|
|
6
6
|
<h4>Accounts</h4>
|
|
7
|
-
<span class="pl-1.5 text-sm font-medium text-light-400">{{ ui.accounts.length }}</span>
|
|
7
|
+
<span class="pl-1.5 text-sm font-medium text-light-400">{{ ui.search.accounts.results.length }}</span>
|
|
8
8
|
</div>
|
|
9
9
|
<ul class="mobile-icons">
|
|
10
10
|
<li>
|
package/src/views/Profiles.vue
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="page-header-card">
|
|
5
5
|
<GroupIcon />
|
|
6
6
|
<h4>Profiles</h4>
|
|
7
|
-
<span class="pl-1.5 text-sm font-medium text-light-400">{{ ui.profiles.length }}</span>
|
|
7
|
+
<span class="pl-1.5 text-sm font-medium text-light-400">{{ ui.search.profiles.results.length }}</span>
|
|
8
8
|
</div>
|
|
9
9
|
<ul class="mobile-icons">
|
|
10
10
|
<li>
|
package/src/views/Tasks.vue
CHANGED
|
@@ -243,7 +243,35 @@ const QuickSettings = defineAsyncComponent(() => import("@/components/Tasks/Quic
|
|
|
243
243
|
|
|
244
244
|
const ui = useUIStore();
|
|
245
245
|
const activeModal = computed(() => ui.activeModal);
|
|
246
|
-
|
|
246
|
+
|
|
247
|
+
// Count of visible tasks after filters (country, event, All/Checkout) - synced with table
|
|
248
|
+
const taskCount = computed(() => {
|
|
249
|
+
const siteIdEdgeCases = {
|
|
250
|
+
LN_US: ["TM_US"],
|
|
251
|
+
TM_CA: ["TM_US"],
|
|
252
|
+
TM_IE: ["TM_UK"],
|
|
253
|
+
TM_NZ: ["TM_AU"]
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
let count = 0;
|
|
257
|
+
for (const id of ui.taskIdOrder) {
|
|
258
|
+
const task = ui.tasks[id];
|
|
259
|
+
if (!task || task.hidden) continue;
|
|
260
|
+
|
|
261
|
+
// Filter by country
|
|
262
|
+
if (task.siteId !== ui.currentCountry.siteId && !siteIdEdgeCases[task.siteId]?.includes(ui.currentCountry.siteId)) continue;
|
|
263
|
+
|
|
264
|
+
// Filter by event
|
|
265
|
+
if (ui.currentEvent && task.eventId !== ui.currentEvent) continue;
|
|
266
|
+
|
|
267
|
+
// Filter by All/Checkout
|
|
268
|
+
if (ui.taskFilter === "Checkout" && !task.checkoutUrl) continue;
|
|
269
|
+
|
|
270
|
+
count++;
|
|
271
|
+
}
|
|
272
|
+
return count;
|
|
273
|
+
});
|
|
274
|
+
|
|
247
275
|
ui.refreshQueueStats();
|
|
248
276
|
|
|
249
277
|
const taskSearchQuery = ref("");
|