@necrolab/dashboard 0.5.16 → 0.5.17

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 (66) hide show
  1. package/package.json +1 -1
  2. package/src/App.vue +14 -480
  3. package/src/assets/css/components/buttons.scss +12 -68
  4. package/src/assets/css/components/headers.scss +1 -1
  5. package/src/assets/css/components/utilities.scss +91 -16
  6. package/src/assets/css/main.scss +22 -95
  7. package/src/components/Auth/LoginForm.vue +2 -2
  8. package/src/components/Console/ConsoleToolbar.vue +123 -0
  9. package/src/components/Editors/Account/Account.vue +4 -2
  10. package/src/components/Editors/Account/AccountView.vue +12 -37
  11. package/src/components/Editors/Account/CreateAccount.vue +3 -11
  12. package/src/components/Editors/AdminFileEditor.vue +179 -0
  13. package/src/components/Editors/Profile/CreateProfile.vue +4 -20
  14. package/src/components/Editors/Profile/Profile.vue +5 -4
  15. package/src/components/Editors/Profile/ProfileView.vue +13 -38
  16. package/src/components/Editors/ProxyFileEditor.vue +86 -0
  17. package/src/components/Filter/Filter.vue +6 -6
  18. package/src/components/Filter/FilterPreview.vue +4 -12
  19. package/src/components/Filter/PriceSortToggle.vue +1 -1
  20. package/src/components/Tasks/QuickSettings.vue +5 -5
  21. package/src/components/Tasks/Stats.vue +1 -1
  22. package/src/components/Tasks/Task.vue +5 -8
  23. package/src/components/Tasks/TaskView.vue +2 -1
  24. package/src/components/Tasks/ViewTask.vue +2 -2
  25. package/src/components/icons/index.js +0 -4
  26. package/src/components/ui/ActionButtonGroup.vue +2 -2
  27. package/src/components/ui/BalanceIndicator.vue +3 -3
  28. package/src/components/ui/EnableDisableToggle.vue +2 -2
  29. package/src/components/ui/FormField.vue +2 -2
  30. package/src/components/ui/IconLabel.vue +2 -2
  31. package/src/components/ui/InfoRow.vue +4 -4
  32. package/src/components/ui/Modal.vue +83 -9
  33. package/src/components/ui/Navbar.vue +3 -3
  34. package/src/components/ui/ReadonlyFieldsSection.vue +1 -1
  35. package/src/components/ui/StatusBadge.vue +1 -1
  36. package/src/components/ui/controls/CountryChooser.vue +5 -5
  37. package/src/components/ui/controls/atomic/MultiDropdown.vue +1 -1
  38. package/src/composables/useCodeEditor.js +117 -0
  39. package/src/composables/useDropdownPosition.js +0 -2
  40. package/src/composables/useEnableDisable.js +6 -0
  41. package/src/composables/useFilterCSS.js +71 -0
  42. package/src/composables/useFormValidation.js +92 -0
  43. package/src/composables/useGetAllTags.js +9 -0
  44. package/src/composables/useIOSViewportHandling.js +76 -0
  45. package/src/composables/useNotchHandling.js +306 -0
  46. package/src/composables/useTableRender.js +23 -0
  47. package/src/composables/useZoomPrevention.js +96 -0
  48. package/src/constants/tableLayout.js +14 -0
  49. package/src/libs/utils/array.js +1 -3
  50. package/src/libs/utils/dataGeneration.js +1 -1
  51. package/src/libs/utils/string.js +1 -26
  52. package/src/libs/utils/validation.js +2 -26
  53. package/src/stores/connection.js +0 -25
  54. package/src/stores/ui.js +21 -35
  55. package/src/utils/tableHelpers.js +1 -0
  56. package/src/views/Accounts.vue +9 -17
  57. package/src/views/Console.vue +15 -92
  58. package/src/views/Editor.vue +39 -938
  59. package/src/views/FilterBuilder.vue +9 -97
  60. package/src/views/Profiles.vue +9 -17
  61. package/src/views/Tasks.vue +4 -4
  62. package/src/assets/img/background.svg.backup +0 -11
  63. package/src/components/icons/SquareCheck.vue +0 -12
  64. package/src/components/icons/SquareUncheck.vue +0 -12
  65. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  66. /package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +0 -0
@@ -78,7 +78,8 @@
78
78
  /* Console main container with responsive heights */
79
79
  .console-main {
80
80
  @apply relative overflow-x-auto overflow-y-auto rounded border-2 border-dark-550 bg-dark-400 p-2 font-mono text-white;
81
- height: calc(100vh - 18rem);
81
+ height: calc(100vh - 20rem);
82
+ max-height: calc(100vh - 20rem);
82
83
  scroll-padding: 0.5rem;
83
84
  -webkit-overflow-scrolling: touch;
84
85
  overscroll-behavior: contain;
@@ -86,17 +87,19 @@
86
87
  touch-action: pan-y pan-up pan-down;
87
88
 
88
89
  @screen md {
89
- height: calc(100vh - 16rem);
90
+ height: calc(100vh - 18rem);
91
+ max-height: calc(100vh - 18rem);
90
92
  }
91
93
 
92
94
  @screen lg {
93
- height: calc(100vh - 14rem);
95
+ height: calc(100vh - 16rem);
96
+ max-height: calc(100vh - 16rem);
94
97
  padding: 1.25rem;
95
98
  }
96
99
 
97
100
  @screen mobile-portrait {
98
- height: calc(100vh - 19.5rem);
99
- max-height: 50vh;
101
+ height: calc(100vh - 22rem);
102
+ max-height: calc(100vh - 22rem);
100
103
  overflow: auto;
101
104
  padding: 0.25rem;
102
105
  font-size: 0.75rem;
@@ -104,12 +107,13 @@
104
107
  }
105
108
 
106
109
  @media (orientation: landscape) and (max-width: 1023px) {
107
- height: calc(100vh - 10.5rem);
110
+ height: calc(100vh - 12rem);
111
+ max-height: calc(100vh - 12rem);
108
112
  }
109
113
 
110
114
  @media (max-width: 767px) and (orientation: landscape) {
111
115
  height: auto;
112
- max-height: 60vh;
116
+ max-height: 65vh;
113
117
  }
114
118
  }
115
119
 
@@ -202,19 +206,90 @@
202
206
  @apply h-full w-full bg-transparent text-sm text-white outline-none;
203
207
  }
204
208
 
205
- /* High-frequency Tailwind pattern utilities */
206
- .flex-center {
207
- @apply flex items-center justify-center;
209
+ .icon-md {
210
+ @apply h-4 w-4;
208
211
  }
209
212
 
210
- .text-secondary {
211
- @apply text-xs text-light-500;
213
+ /* Flex with gap variants - consolidates 51+ repeated patterns */
214
+ .flex-gap-1 {
215
+ @apply flex gap-1;
212
216
  }
213
217
 
214
- .text-tertiary {
215
- @apply text-xs text-light-300;
218
+ .flex-gap-2 {
219
+ @apply flex gap-2;
216
220
  }
217
221
 
218
- .icon-md {
219
- @apply h-4 w-4;
222
+ .flex-gap-3 {
223
+ @apply flex gap-3;
224
+ }
225
+
226
+ /* Standard transition - used throughout the app */
227
+ .transition-standard {
228
+ @apply transition-all duration-150;
229
+ }
230
+
231
+ /* Badge pattern for version/stat chips - QuickSettings pattern */
232
+ .badge-version {
233
+ @apply flex items-center gap-x-1.5 px-2.5 py-1.5 rounded-lg border bg-dark-400 border-dark-550;
234
+ }
235
+
236
+ /* Icon button container - 8x8 flex centered button pattern */
237
+ .icon-button {
238
+ @apply flex h-8 w-8 flex-shrink-0 items-center justify-center rounded;
239
+ }
240
+
241
+ /* Icon size utilities for common patterns */
242
+ .icon-sm {
243
+ @apply h-3 w-3;
244
+ }
245
+
246
+ .icon-lg {
247
+ @apply h-5 w-5;
248
+ }
249
+
250
+ .icon-xl {
251
+ @apply h-6 w-6;
252
+ }
253
+
254
+ /* Square size utilities */
255
+ .size-8 {
256
+ @apply h-8 w-8;
257
+ }
258
+
259
+ .size-10 {
260
+ @apply h-10 w-10;
261
+ }
262
+
263
+ /* Common button pattern - dark with border */
264
+ .btn-dark-bordered {
265
+ @apply rounded-md border border-dark-650 bg-dark-400;
266
+ }
267
+
268
+ /* Flex layout utilities */
269
+ .flex-between {
270
+ @apply flex items-center justify-between;
271
+ }
272
+
273
+ /* Text utilities */
274
+ .text-button {
275
+ @apply text-white text-xs font-medium;
276
+ }
277
+
278
+ /* Padding combinations */
279
+ .pad-3-2 {
280
+ @apply px-3 py-2;
281
+ }
282
+
283
+ /* Square size utilities */
284
+ .square-8 {
285
+ @apply h-8 w-8;
286
+ }
287
+
288
+ /* Border + rounded combinations */
289
+ .dark-border-rounded {
290
+ @apply rounded border border-dark-650;
291
+ }
292
+
293
+ .dark-border-rounded-md {
294
+ @apply rounded-md border border-dark-650;
220
295
  }
@@ -105,17 +105,6 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
105
105
  COMPONENT UTILITIES
106
106
  ========================================================================== */
107
107
 
108
- .smooth-hover {
109
- @apply transition-all duration-200;
110
- @include scale-hover;
111
- }
112
-
113
- .status-indicator {
114
- @apply h-2 w-2 flex-shrink-0 rounded-full;
115
- min-width: 4px;
116
- min-height: 4px;
117
- }
118
-
119
108
  .mobile-icons {
120
109
  @apply ml-auto flex items-center gap-x-2 lg:hidden;
121
110
 
@@ -134,6 +123,17 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
134
123
  }
135
124
  }
136
125
 
126
+ .smooth-hover {
127
+ @apply transition-all duration-200;
128
+ @include scale-hover;
129
+ }
130
+
131
+ .status-indicator {
132
+ @apply h-2 w-2 flex-shrink-0 rounded-full;
133
+ min-width: 4px;
134
+ min-height: 4px;
135
+ }
136
+
137
137
  .loading-spinner {
138
138
  @apply h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent;
139
139
  }
@@ -150,90 +150,6 @@ button.bg-red-400 {
150
150
  }
151
151
  }
152
152
 
153
- /* ==========================================================================
154
- LABEL & ICON STYLING
155
- ========================================================================== */
156
-
157
- .label-override {
158
- @apply mb-2 flex items-center text-xs;
159
- color: var(--color-text-muted);
160
- margin-top: 1rem;
161
-
162
- svg {
163
- @apply ml-2;
164
- color: var(--color-text-muted) !important;
165
- width: 16px;
166
- height: 16px;
167
- fill: var(--color-text-muted) !important;
168
- }
169
- }
170
-
171
- .task-switches {
172
- h4 {
173
- color: var(--color-text-primary);
174
- font-size: 0.8125rem;
175
- font-weight: 500;
176
- @apply mx-auto mb-2 flex items-center gap-x-2 text-center;
177
- }
178
-
179
- .switch-wrapper {
180
- @apply gap-y-2;
181
- }
182
-
183
- svg {
184
- width: 15px !important;
185
- height: 15px !important;
186
- color: var(--color-text-primary) !important;
187
- margin-left: 0.25rem !important;
188
- }
189
- }
190
-
191
- /* ==========================================================================
192
- DYNAMIC HEIGHTS & RESPONSIVE DESIGN
193
- ========================================================================== */
194
-
195
- .max-h-big {
196
- max-height: calc(100vh - 12rem);
197
- min-height: 8rem;
198
- overflow: hidden;
199
-
200
- @screen xl {
201
- max-height: calc(100vh - 11rem);
202
- }
203
- }
204
-
205
- /* ==========================================================================
206
- TRANSITIONS & ANIMATIONS
207
- ========================================================================== */
208
-
209
- .dropdown-fade-enter-active {
210
- @apply transition-all duration-300;
211
- }
212
-
213
- .dropdown-fade-leave-active {
214
- @apply transition-all duration-200;
215
- }
216
-
217
- .dropdown-fade-enter-from {
218
- @apply opacity-0;
219
- transform: translateY(-8px) scale(0.95);
220
- }
221
-
222
- .dropdown-fade-leave-to {
223
- @apply opacity-0;
224
- transform: translateY(-4px) scale(0.98);
225
- }
226
-
227
- .dropdown-fade-enter-to,
228
- .dropdown-fade-leave-from {
229
- @apply opacity-100;
230
- transform: translateY(0) scale(1);
231
- }
232
-
233
- .will-change-auto {
234
- will-change: auto;
235
- }
236
-
237
153
  /* ==========================================================================
238
154
  iOS OPTIMIZATIONS
239
155
  ========================================================================== */
@@ -271,3 +187,14 @@ button.bg-red-400 {
271
187
  min-height: 5px;
272
188
  }
273
189
  }
190
+
191
+ /* Fade transition for modals and components */
192
+ .fade-enter-active,
193
+ .fade-leave-active {
194
+ transition: opacity 0.15s ease;
195
+ }
196
+
197
+ .fade-enter-from,
198
+ .fade-leave-to {
199
+ opacity: 0;
200
+ }
@@ -2,7 +2,7 @@
2
2
  <div class="form-section">
3
3
  <!-- Username -->
4
4
  <div class="form-field-labeled mb-3">
5
- <div class="flex items-center gap-2">
5
+ <div class="flex-gap-2 items-center">
6
6
  <ProfileIcon class="w-4 h-4" />
7
7
  <span class="text-light-300 font-medium text-sm">Username</span>
8
8
  </div>
@@ -12,7 +12,7 @@
12
12
  </div>
13
13
  <!-- Password -->
14
14
  <div class="form-field-labeled mb-4">
15
- <div class="flex items-center gap-2">
15
+ <div class="flex-gap-2 items-center">
16
16
  <KeyIcon class="w-4 h-4" />
17
17
  <span class="text-light-300 font-medium text-sm">Password</span>
18
18
  </div>
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <div>
3
+ <div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
4
+ <div class="flex flex-col gap-3 md:flex-1 md:flex-row md:items-center">
5
+ <div class="w-full md:w-64">
6
+ <Dropdown
7
+ class="console-dropdown input-default w-full border-2 border-dark-550 bg-dark-500"
8
+ rightAmount="right-2"
9
+ default="All logs"
10
+ :allowDefault="true"
11
+ :value="currentTaskLog"
12
+ :onClick="(f) => $emit('update:currentTaskLog', f.split(' ')[0])"
13
+ :options="Object.entries(taskLogMapping).map(([k, v]) => `${k} (${v.length})`).sort((a, b) => a.localeCompare(b))" />
14
+ </div>
15
+ <div class="flex flex-1 items-center gap-2">
16
+ <div class="input-default flex flex-1 items-center md:max-w-64">
17
+ <input
18
+ :value="searchQuery"
19
+ @input="$emit('update:searchQuery', $event.target.value)"
20
+ type="text"
21
+ placeholder="Search logs..."
22
+ aria-label="Search logs"
23
+ class="transparent-input" />
24
+ <span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
25
+ </div>
26
+ <button
27
+ class="console-scroll-btn md:hidden"
28
+ @mousedown="$emit('scroll', 'up')"
29
+ @mouseup="$emit('scroll-stop')"
30
+ @mouseleave="$emit('scroll-stop')"
31
+ @touchstart="$emit('scroll', 'up')"
32
+ @touchend="$emit('scroll-stop')"
33
+ aria-label="Scroll up">
34
+ <UpIcon class="pointer-events-none h-5 w-5" />
35
+ </button>
36
+ <button
37
+ class="console-scroll-btn md:hidden"
38
+ @mousedown="$emit('scroll', 'down')"
39
+ @mouseup="$emit('scroll-stop')"
40
+ @mouseleave="$emit('scroll-stop')"
41
+ @touchstart="$emit('scroll', 'down')"
42
+ @touchend="$emit('scroll-stop')">
43
+ <DownIcon class="pointer-events-none h-5 w-5" />
44
+ </button>
45
+ </div>
46
+ </div>
47
+ <div class="flex hidden items-center gap-3 md:flex">
48
+ <div class="hidden items-center gap-3 md:flex">
49
+ <button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
50
+ <h3 class="text-sm text-white">Hide Monitors</h3>
51
+ <Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
52
+ </button>
53
+ <button class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
54
+ <h3 class="text-sm text-white">Auto</h3>
55
+ <Switch class="scale-75" :model-value="autoscrollToggled" @update:model-value="$emit('update:autoscrollToggled', $event); $emit('autoscroll-toggle')" />
56
+ <div
57
+ v-if="userScrolledUp && autoscrollToggled"
58
+ class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
59
+ title="Autoscroll paused - scroll to bottom to resume"></div>
60
+ </button>
61
+ </div>
62
+ <button
63
+ class="hidden size-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
64
+ @mousedown="$emit('scroll', 'up')"
65
+ @mouseup="$emit('scroll-stop')"
66
+ @mouseleave="$emit('scroll-stop')"
67
+ @touchstart="$emit('scroll', 'up')"
68
+ @touchend="$emit('scroll-stop')">
69
+ <UpIcon class="pointer-events-none h-5 w-5" />
70
+ </button>
71
+ <button
72
+ class="hidden size-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
73
+ @mousedown="$emit('scroll', 'down')"
74
+ @mouseup="$emit('scroll-stop')"
75
+ @mouseleave="$emit('scroll-stop')"
76
+ @touchstart="$emit('scroll', 'down')"
77
+ @touchend="$emit('scroll-stop')">
78
+ <DownIcon class="pointer-events-none h-5 w-5" />
79
+ </button>
80
+ </div>
81
+ </div>
82
+ <div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
83
+ <button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
84
+ <h3 class="text-sm text-white">Hide Monitors</h3>
85
+ <Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
86
+ </button>
87
+ <button class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
88
+ <h3 class="text-sm text-white">Auto</h3>
89
+ <Switch class="scale-75" :model-value="autoscrollToggled" @update:model-value="$emit('update:autoscrollToggled', $event); $emit('autoscroll-toggle')" />
90
+ <div
91
+ v-if="userScrolledUp && autoscrollToggled"
92
+ class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
93
+ title="Autoscroll paused - scroll to bottom to resume"></div>
94
+ </button>
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script setup>
100
+ import { DownIcon, UpIcon } from '@/components/icons';
101
+ import Switch from '@/components/ui/controls/atomic/Switch.vue';
102
+ import Dropdown from '@/components/ui/controls/atomic/Dropdown.vue';
103
+
104
+ defineProps({
105
+ currentTaskLog: String,
106
+ taskLogMapping: Object,
107
+ searchQuery: String,
108
+ filteredLogs: Boolean,
109
+ autoscrollToggled: Boolean,
110
+ userScrolledUp: Boolean,
111
+ filteredCount: String
112
+ });
113
+
114
+ defineEmits([
115
+ 'update:currentTaskLog',
116
+ 'update:searchQuery',
117
+ 'update:filteredLogs',
118
+ 'update:autoscrollToggled',
119
+ 'scroll',
120
+ 'scroll-stop',
121
+ 'autoscroll-toggle'
122
+ ]);
123
+ </script>
@@ -39,12 +39,12 @@
39
39
  </li>
40
40
  <li v-if="props.account.enabled">
41
41
  <button @click="disable">
42
- <img class="h-4 w-4" src="/img/controls/disable.svg" />
42
+ <img class="icon-md" src="/img/controls/disable.svg" />
43
43
  </button>
44
44
  </li>
45
45
  <li v-else>
46
46
  <button @click="enable">
47
- <img class="h-4 w-4" src="/img/controls/enable.svg" />
47
+ <img class="icon-md" src="/img/controls/enable.svg" />
48
48
  </button>
49
49
  </li>
50
50
  </ActionButtonGroup>
@@ -61,6 +61,8 @@ import { useUIStore } from "@/stores/ui";
61
61
  import TagLabel from "@/components/Editors/TagLabel.vue";
62
62
  import { useRowSelection } from "@/composables/useRowSelection";
63
63
  import { useCopyToClipboard } from "@/composables/useCopyToClipboard";
64
+ import { computed } from "vue";
65
+ import { useEnableDisable } from "@/composables/useEnableDisable";
64
66
 
65
67
  const ui = useUIStore();
66
68
  const { copy } = useCopyToClipboard();
@@ -8,24 +8,24 @@
8
8
  @valueUpdate="ui.toggleMainCheckbox('accounts')"
9
9
  :isHeader="true" />
10
10
  <div class="mx-auto flex cursor-pointer items-center" @click="ui.toggleSort('eventId')">
11
- <MailIcon class="mr-0 h-4 w-4 md:mr-3" />
11
+ <MailIcon class="mr-0 icon-md md:mr-3" />
12
12
  <h4 class="hidden text-white md:flex">Email</h4>
13
13
  </div>
14
14
  </div>
15
15
  <div class="col-span-2 hidden items-center justify-center md:flex" v-once>
16
- <KeyIcon class="mr-0 h-4 w-4 md:mr-3" />
16
+ <KeyIcon class="mr-0 icon-md md:mr-3" />
17
17
  <h4 class="hidden text-white md:flex">Password</h4>
18
18
  </div>
19
19
  <div class="grid-cell-center" v-once>
20
- <CheckmarkIcon class="mr-0 h-4 w-4 md:mr-3" />
20
+ <CheckmarkIcon class="mr-0 icon-md md:mr-3" />
21
21
  <h4 class="hidden text-white md:flex">Enabled</h4>
22
22
  </div>
23
23
  <div class="col-span-1 hidden items-center justify-center lg:flex" v-once>
24
- <TicketIcon class="mr-0 h-4 w-4 md:mr-3" />
24
+ <TicketIcon class="mr-0 icon-md md:mr-3" />
25
25
  <h4 class="hidden text-white md:flex">Tags</h4>
26
26
  </div>
27
27
  <div class="grid-cell-center" v-once>
28
- <ClickIcon class="mr-0 h-4 w-4 md:mr-3" />
28
+ <ClickIcon class="mr-0 icon-md md:mr-3" />
29
29
  <h4 class="hidden text-white md:flex">Actions</h4>
30
30
  </div>
31
31
  </Header>
@@ -38,7 +38,7 @@
38
38
  :key="account.id || account.index"
39
39
  class="min-h-16 flex-shrink-0 hover:bg-dark-550">
40
40
  <Account
41
- :class="i % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
41
+ :class="getRowClass(i)"
42
42
  :account="account" />
43
43
  </div>
44
44
  </div>
@@ -59,7 +59,9 @@ import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
59
59
  import EmptyState from "@/components/ui/EmptyState.vue";
60
60
  import { useUIStore } from "@/stores/ui";
61
61
  import { useDynamicTableHeight } from "@/composables/useDynamicTableHeight";
62
- import { computed, ref } from "vue";
62
+ import { computed } from "vue";
63
+ import { useTableRender } from "@/composables/useTableRender";
64
+ import { getRowClass } from "@/utils/tableHelpers";
63
65
 
64
66
  const props = defineProps({
65
67
  accounts: {
@@ -69,36 +71,9 @@ const props = defineProps({
69
71
  });
70
72
  const ui = useUIStore();
71
73
 
72
- const i = ref({});
73
- const toRender = computed(() => {
74
- let c = 0;
75
- const rendered = props.accounts.map((t) => ({ ...t, index: c++ }));
74
+ const { toRender } = useTableRender(computed(() => props.accounts));
76
75
 
77
- // Initialize reactive refs for click tracking
78
- rendered.forEach((t) => {
79
- if (t.id && !(t.id in i.value)) {
80
- i.value[t.id] = 0;
81
- }
82
- if (!(t.index in i.value)) {
83
- i.value[t.index] = 0;
84
- }
85
- });
76
+ import { TABLE_LAYOUT } from "@/constants/tableLayout";
86
77
 
87
- return rendered;
88
- });
89
-
90
- // Layout constants for dynamic table height calculation
91
- const LAYOUT_CONSTANTS = {
92
- TOP_RESERVED_SPACE: 180, // Page header + search controls + gaps (router-wrapper handles navbar, no UTILS section)
93
- BOTTOM_BUFFER: 50, // Margin at bottom to prevent overflow
94
- ROW_HEIGHT: 64, // Account row height in pixels
95
- MIN_ROWS_TO_SHOW: 2 // Minimum number of rows to display
96
- };
97
-
98
- const { dynamicTableHeight } = useDynamicTableHeight({
99
- topReservedSpace: LAYOUT_CONSTANTS.TOP_RESERVED_SPACE,
100
- bottomBuffer: LAYOUT_CONSTANTS.BOTTOM_BUFFER,
101
- rowHeight: LAYOUT_CONSTANTS.ROW_HEIGHT,
102
- minRowsToShow: LAYOUT_CONSTANTS.MIN_ROWS_TO_SHOW
103
- });
78
+ const { dynamicTableHeight } = useDynamicTableHeight(TABLE_LAYOUT.ACCOUNTS);
104
79
  </script>
@@ -72,6 +72,7 @@ import { useUIStore } from "@/stores/ui";
72
72
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
73
73
  import ReadonlyFieldsSection from "@/components/ui/ReadonlyFieldsSection.vue";
74
74
  import { ref } from "vue";
75
+ import { useFormValidation } from "@/composables/useFormValidation";
75
76
 
76
77
  const ui = useUIStore();
77
78
  const account = ref({
@@ -82,19 +83,10 @@ const account = ref({
82
83
 
83
84
  if (ui.currentlyEditing?.email) account.value = ui.currentlyEditing;
84
85
 
85
- const errors = ref([]);
86
-
87
- const validate = (p) => {
88
- errors.value = [];
89
-
90
- if (!p.email.includes("@")) errors.value.push("email");
91
- if (p.password.length < 5) errors.value.push("password");
92
-
93
- return errors.value.length === 0;
94
- };
86
+ const { errors, validateAccount } = useFormValidation();
95
87
 
96
88
  function done() {
97
- if (!validate(account.value)) return;
89
+ if (!validateAccount(account.value)) return;
98
90
  ui.toggleModal("");
99
91
  ui.addAccount(account.value);
100
92
  }