@necrolab/dashboard 0.4.3

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 (240) hide show
  1. package/.claude/settings.local.json +45 -0
  2. package/.eslintrc.js +24 -0
  3. package/.prettierignore +1 -0
  4. package/.prettierrc +10 -0
  5. package/.vscode/extensions.json +3 -0
  6. package/ICONS.md +21 -0
  7. package/README.md +65 -0
  8. package/backend/api.js +430 -0
  9. package/backend/auth.js +62 -0
  10. package/backend/batching.js +43 -0
  11. package/backend/endpoints.js +343 -0
  12. package/backend/index.js +23 -0
  13. package/backend/mock-data.js +66 -0
  14. package/backend/mock-src/classes/logger.js +112 -0
  15. package/backend/mock-src/classes/utils.js +42 -0
  16. package/backend/mock-src/ticketmaster.js +92 -0
  17. package/backend/validator.js +62 -0
  18. package/config/configs.json +20 -0
  19. package/config/filter.json +3 -0
  20. package/config/presale.csv +3 -0
  21. package/config/proxies.txt +6 -0
  22. package/config/used-codes.json +4 -0
  23. package/index.html +114 -0
  24. package/index.js +2 -0
  25. package/jsconfig.json +16 -0
  26. package/package.json +48 -0
  27. package/postcss.config.js +6 -0
  28. package/postinstall.js +9 -0
  29. package/public/android-chrome-192x192.png +0 -0
  30. package/public/android-chrome-512x512.png +0 -0
  31. package/public/apple-touch-icon.png +0 -0
  32. package/public/favicon-16x16.png +0 -0
  33. package/public/favicon-32x32.png +0 -0
  34. package/public/favicon.ico +0 -0
  35. package/public/flags/ae.svg +1 -0
  36. package/public/flags/at.svg +1 -0
  37. package/public/flags/au.svg +1 -0
  38. package/public/flags/be.svg +1 -0
  39. package/public/flags/ch.svg +1 -0
  40. package/public/flags/cz.svg +1 -0
  41. package/public/flags/de.svg +1 -0
  42. package/public/flags/dk.svg +1 -0
  43. package/public/flags/es.svg +1 -0
  44. package/public/flags/nl.svg +1 -0
  45. package/public/flags/no.svg +1 -0
  46. package/public/flags/nz.svg +1 -0
  47. package/public/flags/pl.svg +1 -0
  48. package/public/flags/se.svg +1 -0
  49. package/public/flags/uk.svg +1 -0
  50. package/public/flags/us.svg +1 -0
  51. package/public/img/award.svg +3 -0
  52. package/public/img/background.svg +14 -0
  53. package/public/img/bag_w.svg +12 -0
  54. package/public/img/banks/amex.svg +4 -0
  55. package/public/img/banks/mastercard.svg +4 -0
  56. package/public/img/banks/visa.svg +4 -0
  57. package/public/img/camera.svg +3 -0
  58. package/public/img/close.svg +3 -0
  59. package/public/img/controls/disable.svg +5 -0
  60. package/public/img/controls/enable.svg +5 -0
  61. package/public/img/groups.svg +3 -0
  62. package/public/img/hand.svg +3 -0
  63. package/public/img/key.svg +3 -0
  64. package/public/img/logo.png +0 -0
  65. package/public/img/logo_icon.png +0 -0
  66. package/public/img/logo_icon_2.png +0 -0
  67. package/public/img/logo_trans.png +0 -0
  68. package/public/img/loyalty.svg +3 -0
  69. package/public/img/mail.svg +3 -0
  70. package/public/img/pencil.svg +3 -0
  71. package/public/img/profile.svg +4 -0
  72. package/public/img/reload.svg +3 -0
  73. package/public/img/sandclock.svg +25 -0
  74. package/public/img/save.svg +5 -0
  75. package/public/img/savings.svg +3 -0
  76. package/public/img/scanner.svg +3 -0
  77. package/public/img/sell.svg +3 -0
  78. package/public/img/shield.svg +3 -0
  79. package/public/img/ski.svg +3 -0
  80. package/public/img/stadium.svg +8 -0
  81. package/public/img/stadium_w.svg +8 -0
  82. package/public/img/timer.svg +3 -0
  83. package/public/manifest.json +27 -0
  84. package/public/robots.txt +2 -0
  85. package/run +10 -0
  86. package/src/App.vue +307 -0
  87. package/src/assets/css/_input.scss +197 -0
  88. package/src/assets/css/main.scss +269 -0
  89. package/src/assets/css/tailwind.css +3 -0
  90. package/src/assets/img/award.svg +3 -0
  91. package/src/assets/img/background.svg +11 -0
  92. package/src/assets/img/camera.svg +3 -0
  93. package/src/assets/img/close.svg +3 -0
  94. package/src/assets/img/eyes/closed.svg +13 -0
  95. package/src/assets/img/eyes/open.svg +12 -0
  96. package/src/assets/img/groups.svg +3 -0
  97. package/src/assets/img/hand.svg +3 -0
  98. package/src/assets/img/key.svg +3 -0
  99. package/src/assets/img/logo.png +0 -0
  100. package/src/assets/img/logo_icon.png +0 -0
  101. package/src/assets/img/logo_icon_2.png +0 -0
  102. package/src/assets/img/logo_trans.png +0 -0
  103. package/src/assets/img/loyalty.svg +3 -0
  104. package/src/assets/img/mail.svg +3 -0
  105. package/src/assets/img/pencil.svg +3 -0
  106. package/src/assets/img/reload.svg +3 -0
  107. package/src/assets/img/savings.svg +3 -0
  108. package/src/assets/img/scanner.svg +3 -0
  109. package/src/assets/img/sell.svg +3 -0
  110. package/src/assets/img/shield.svg +3 -0
  111. package/src/assets/img/ski.svg +3 -0
  112. package/src/assets/img/square_check.svg +5 -0
  113. package/src/assets/img/square_uncheck.svg +5 -0
  114. package/src/assets/img/stadium.svg +8 -0
  115. package/src/assets/img/timer.svg +3 -0
  116. package/src/assets/img/wildcard.svg +7 -0
  117. package/src/components/Auth/LoginForm.vue +48 -0
  118. package/src/components/Editors/Account/Account.vue +119 -0
  119. package/src/components/Editors/Account/AccountCreator.vue +147 -0
  120. package/src/components/Editors/Account/AccountView.vue +87 -0
  121. package/src/components/Editors/Account/CreateAccount.vue +106 -0
  122. package/src/components/Editors/Profile/CreateProfile.vue +321 -0
  123. package/src/components/Editors/Profile/Profile.vue +142 -0
  124. package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
  125. package/src/components/Editors/Profile/ProfileView.vue +96 -0
  126. package/src/components/Editors/TagLabel.vue +16 -0
  127. package/src/components/Editors/TagToggle.vue +41 -0
  128. package/src/components/Filter/Filter.vue +409 -0
  129. package/src/components/Filter/FilterPreview.vue +236 -0
  130. package/src/components/Filter/PriceSortToggle.vue +105 -0
  131. package/src/components/Table/Header.vue +5 -0
  132. package/src/components/Table/Row.vue +5 -0
  133. package/src/components/Table/Table.vue +14 -0
  134. package/src/components/Table/index.js +4 -0
  135. package/src/components/Tasks/CheckStock.vue +62 -0
  136. package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
  137. package/src/components/Tasks/Controls/MobileControls.vue +32 -0
  138. package/src/components/Tasks/Controls/index.js +3 -0
  139. package/src/components/Tasks/CreateTaskAXS.vue +339 -0
  140. package/src/components/Tasks/CreateTaskTM.vue +459 -0
  141. package/src/components/Tasks/MassEdit.vue +50 -0
  142. package/src/components/Tasks/QuickSettings.vue +167 -0
  143. package/src/components/Tasks/ScrapeVenue.vue +42 -0
  144. package/src/components/Tasks/Stats.vue +66 -0
  145. package/src/components/Tasks/Task.vue +296 -0
  146. package/src/components/Tasks/TaskLabel.vue +20 -0
  147. package/src/components/Tasks/TaskView.vue +126 -0
  148. package/src/components/Tasks/Utilities.vue +33 -0
  149. package/src/components/icons/Award.vue +8 -0
  150. package/src/components/icons/Bag.vue +8 -0
  151. package/src/components/icons/BagWhite.vue +8 -0
  152. package/src/components/icons/Box.vue +8 -0
  153. package/src/components/icons/Camera.vue +8 -0
  154. package/src/components/icons/Cart.vue +8 -0
  155. package/src/components/icons/Check.vue +5 -0
  156. package/src/components/icons/Checkmark.vue +11 -0
  157. package/src/components/icons/Click.vue +8 -0
  158. package/src/components/icons/Close.vue +21 -0
  159. package/src/components/icons/CloseX.vue +5 -0
  160. package/src/components/icons/Console.vue +13 -0
  161. package/src/components/icons/Down.vue +8 -0
  162. package/src/components/icons/Edit.vue +13 -0
  163. package/src/components/icons/Event.vue +8 -0
  164. package/src/components/icons/Expand.vue +8 -0
  165. package/src/components/icons/Filter.vue +13 -0
  166. package/src/components/icons/Gear.vue +8 -0
  167. package/src/components/icons/Group.vue +8 -0
  168. package/src/components/icons/Hand.vue +8 -0
  169. package/src/components/icons/Key.vue +21 -0
  170. package/src/components/icons/Logout.vue +13 -0
  171. package/src/components/icons/Loyalty.vue +8 -0
  172. package/src/components/icons/Mail.vue +8 -0
  173. package/src/components/icons/Menu.vue +8 -0
  174. package/src/components/icons/Pause.vue +5 -0
  175. package/src/components/icons/Pencil.vue +21 -0
  176. package/src/components/icons/Play.vue +8 -0
  177. package/src/components/icons/Plus.vue +8 -0
  178. package/src/components/icons/Profile.vue +18 -0
  179. package/src/components/icons/Reload.vue +7 -0
  180. package/src/components/icons/Sandclock.vue +33 -0
  181. package/src/components/icons/Savings.vue +8 -0
  182. package/src/components/icons/Scanner.vue +8 -0
  183. package/src/components/icons/Scrape.vue +8 -0
  184. package/src/components/icons/Sell.vue +21 -0
  185. package/src/components/icons/Shield.vue +8 -0
  186. package/src/components/icons/Shrink.vue +8 -0
  187. package/src/components/icons/Ski.vue +8 -0
  188. package/src/components/icons/Spinner.vue +42 -0
  189. package/src/components/icons/SquareCheck.vue +18 -0
  190. package/src/components/icons/SquareUncheck.vue +18 -0
  191. package/src/components/icons/Stadium.vue +13 -0
  192. package/src/components/icons/StadiumWhite.vue +13 -0
  193. package/src/components/icons/Status.vue +8 -0
  194. package/src/components/icons/Tag.vue +8 -0
  195. package/src/components/icons/Tasks.vue +13 -0
  196. package/src/components/icons/Ticket.vue +8 -0
  197. package/src/components/icons/Timer.vue +8 -0
  198. package/src/components/icons/Trash.vue +8 -0
  199. package/src/components/icons/Up.vue +10 -0
  200. package/src/components/icons/Wildcard.vue +18 -0
  201. package/src/components/icons/index.js +111 -0
  202. package/src/components/ui/Modal.vue +61 -0
  203. package/src/components/ui/Navbar.vue +207 -0
  204. package/src/components/ui/ReconnectIndicator.vue +90 -0
  205. package/src/components/ui/Splash.vue +24 -0
  206. package/src/components/ui/controls/CountryChooser.vue +87 -0
  207. package/src/components/ui/controls/EyeToggle.vue +11 -0
  208. package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
  209. package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
  210. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  211. package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
  212. package/src/components/ui/controls/atomic/Switch.vue +84 -0
  213. package/src/libs/Filter.js +593 -0
  214. package/src/libs/ansii.js +565 -0
  215. package/src/libs/panzoom.js +1413 -0
  216. package/src/main.js +23 -0
  217. package/src/registerServiceWorker.js +32 -0
  218. package/src/router/index.js +65 -0
  219. package/src/stores/cities.json +1 -0
  220. package/src/stores/connection.js +399 -0
  221. package/src/stores/countries.js +88 -0
  222. package/src/stores/logger.js +103 -0
  223. package/src/stores/requests.js +88 -0
  224. package/src/stores/sampleData.js +1034 -0
  225. package/src/stores/ui.js +584 -0
  226. package/src/stores/utils.js +554 -0
  227. package/src/types/index.js +42 -0
  228. package/src/utils/debug.js +1 -0
  229. package/src/views/Accounts.vue +191 -0
  230. package/src/views/Console.vue +224 -0
  231. package/src/views/Editor.vue +785 -0
  232. package/src/views/FilterBuilder.vue +785 -0
  233. package/src/views/Login.vue +27 -0
  234. package/src/views/Profiles.vue +209 -0
  235. package/src/views/Tasks.vue +157 -0
  236. package/static/offline.html +184 -0
  237. package/tailwind.config.js +57 -0
  238. package/vite.config.js +63 -0
  239. package/vue.config.js +32 -0
  240. package/workbox-config.js +66 -0
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <button
3
+ class="check-component"
4
+ @click="
5
+ checked = !checked;
6
+ $emit('valueUpdate', checked);
7
+ "
8
+ :class="{ 'border-light-300': !toggled }"
9
+ >
10
+ <div class="flex-center w-4 h-4">
11
+ <transition name="fade">
12
+ <CheckmarkIcon v-if="toggled" class="will-change-auto" />
13
+ </transition>
14
+ </div>
15
+ </button>
16
+ </template>
17
+ <style lang="scss" scoped>
18
+ .check-component {
19
+ @apply border-dashed duration-200 rounded border flex items-center justify-center w-4 h-4;
20
+ }
21
+ </style>
22
+ <script setup>
23
+ import { CheckmarkIcon } from "@/components/icons";
24
+ import { ref } from "vue";
25
+ const props = defineProps({ toggled: { type: Boolean, required: false, default: false } });
26
+
27
+ const checked = ref(props.toggled);
28
+ </script>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <div @click="toggleOpened" class="dropdown">
3
+ <span class="dropdown-display">
4
+ <span class="dropdown-value" :class="capitalize ? 'capitalize' : ''">
5
+ {{ currentValue ? currentValue : props.default }}
6
+ </span>
7
+ <DownIcon class="dropdown-arrow" :class="opened ? 'rotate-180' : ''" />
8
+ </span>
9
+ <transition name="dropdown-fade">
10
+ <div
11
+ class="dropdown-menu"
12
+ v-if="opened"
13
+ @click.stop
14
+ >
15
+ <button
16
+ v-bind:key="f"
17
+ class="dropdown-item"
18
+ :class="i !== 0 ? 'border-t border-light-300' : ''"
19
+ v-for="(f, i) in !allowDefault ? props.options : ['', ...props.options]"
20
+ @click.stop="chose(f)"
21
+ >
22
+ <span class="dropdown-item-text" :class="capitalize ? 'capitalize' : ''">
23
+ {{f ? f : props.default}}
24
+ </span>
25
+ <CheckmarkIcon v-if="(f || props.default) === currentValue" class="ml-2" />
26
+ </button>
27
+ </div>
28
+ </transition>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup>
33
+ import { ref, computed } from "vue";
34
+ import { DownIcon, CheckmarkIcon } from "@/components/icons";
35
+ import { useUIStore } from "@/stores/ui";
36
+ const ui = useUIStore();
37
+
38
+ const props = defineProps({
39
+ onClick: { type: Function },
40
+ default: { type: String },
41
+ value: { type: String },
42
+ options: { type: Object },
43
+ allowDefault: { type: Boolean },
44
+ rightAmount: { type: String },
45
+ topPadding: { type: String },
46
+ capitalize: { type: Boolean }
47
+ });
48
+
49
+ const currentValue = ref(props.value);
50
+ const id = Math.random();
51
+ const opened = computed(() => ui.currentDropdown === id);
52
+
53
+ const toggleOpened = () => {
54
+ if (opened.value) ui.setCurrentDropdown("");
55
+ else ui.setCurrentDropdown(id);
56
+ };
57
+
58
+ const chose = (f) => {
59
+ ui.logger.Info("Dropdown: chosen", f, "hiding...");
60
+ currentValue.value = f;
61
+ ui.setCurrentDropdown("");
62
+ if (props.onClick) props.onClick(f);
63
+ // Ensure dropdown closes
64
+ setTimeout(() => {
65
+ ui.setCurrentDropdown("");
66
+ }, 50);
67
+ };
68
+ </script>
69
+
70
+ <style scoped>
71
+ .dropdown {
72
+ @apply relative w-full p-2 h-12 text-white ml-auto rounded-lg ring-0;
73
+ }
74
+
75
+ @media (max-width: 810px) {
76
+ .dropdown {
77
+ @apply h-10;
78
+ }
79
+ }
80
+
81
+ .dropdown-display {
82
+ @apply flex items-center justify-between z-10;
83
+ }
84
+
85
+ .dropdown-value {
86
+ @apply w-full overflow-hidden block truncate pr-2;
87
+ }
88
+
89
+ .dropdown-arrow {
90
+ @apply min-w-4 min-h-4 transition-transform duration-200 absolute right-2;
91
+ }
92
+
93
+ .dropdown-menu {
94
+ @apply absolute border border-light-300 rounded-lg shadow-lg z-50 max-h-40 overflow-y-auto;
95
+ top: 2.5rem;
96
+ left: -1px;
97
+ width: calc(100% + 2px);
98
+ box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
99
+ background-color: rgb(24, 24, 35);
100
+ scrollbar-width: none; /* Firefox */
101
+ }
102
+
103
+ .dropdown-menu::-webkit-scrollbar {
104
+ display: none; /* Chrome, Safari, Edge */
105
+ }
106
+
107
+ .dropdown-item {
108
+ @apply cursor-pointer text-left w-full py-2 px-3 text-white hover:bg-dark-400 transition-all duration-150 flex items-center justify-between;
109
+ }
110
+
111
+ .dropdown-item:first-child {
112
+ @apply rounded-t-lg;
113
+ }
114
+
115
+ .dropdown-item:last-child {
116
+ @apply rounded-b-lg;
117
+ }
118
+
119
+ .dropdown-item-text {
120
+ @apply overflow-hidden;
121
+ }
122
+
123
+ /* Transition animations */
124
+ .dropdown-fade-enter-active,
125
+ .dropdown-fade-leave-active {
126
+ @apply transition-all duration-200;
127
+ }
128
+
129
+ .dropdown-fade-enter-from,
130
+ .dropdown-fade-leave-to {
131
+ @apply opacity-0 transform scale-95 -translate-y-1;
132
+ }
133
+
134
+ .dropdown-fade-enter-to,
135
+ .dropdown-fade-leave-from {
136
+ @apply opacity-100 transform scale-100 translate-y-0;
137
+ }
138
+ </style>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <button
3
+ :class="[
4
+ 'btn-primary',
5
+ {
6
+ 'opacity-50 cursor-not-allowed': loading || disabled,
7
+ 'btn-secondary': variant === 'secondary',
8
+ 'btn-danger': variant === 'danger'
9
+ }
10
+ ]"
11
+ :disabled="loading || disabled"
12
+ @click="$emit('click')"
13
+ v-bind="$attrs"
14
+ >
15
+ <div v-if="loading" class="flex-center gap-2">
16
+ <div class="loading-spinner"></div>
17
+ <span v-if="loadingText">{{ loadingText }}</span>
18
+ </div>
19
+ <slot v-else />
20
+ </button>
21
+ </template>
22
+
23
+ <script setup>
24
+ defineProps({
25
+ loading: {
26
+ type: Boolean,
27
+ default: false
28
+ },
29
+ disabled: {
30
+ type: Boolean,
31
+ default: false
32
+ },
33
+ loadingText: {
34
+ type: String,
35
+ default: ''
36
+ },
37
+ variant: {
38
+ type: String,
39
+ default: 'primary',
40
+ validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
41
+ }
42
+ });
43
+
44
+ defineEmits(['click']);
45
+ </script>
@@ -0,0 +1,262 @@
1
+ <template>
2
+ <div @click="toggleOpened" class="dropdown">
3
+ <span class="dropdown-display">
4
+ <span class="dropdown-value" :class="capitalize ? 'capitalize' : ''">
5
+ {{ displayValue }}
6
+ </span>
7
+ <div class="dropdown-counter">
8
+ <span v-if="selectedOptions.length > 1" class="counter-badge">
9
+ {{ selectedOptions.length }}
10
+ </span>
11
+ <DownIcon class="dropdown-arrow" :class="opened ? 'rotate-180' : ''" />
12
+ </div>
13
+ </span>
14
+ <transition name="dropdown-fade">
15
+ <div
16
+ class="dropdown-menu multi"
17
+ v-if="opened"
18
+ :style="{ maxHeight: selectedOptions.length > 0 ? '240px' : '160px' }"
19
+ @click.stop
20
+ >
21
+ <div class="option-list">
22
+ <button
23
+ v-for="(option, i) in props.options"
24
+ :key="option.value"
25
+ class="dropdown-item"
26
+ :class="i !== 0 ? 'border-t border-light-300' : ''"
27
+ @click.stop="toggleOption(option.value)"
28
+ >
29
+ <span class="dropdown-item-text" :class="capitalize ? 'capitalize' : ''">
30
+ {{ option.label }}
31
+ </span>
32
+ <CheckmarkIcon v-if="selectedOptions.includes(option.value)" class="ml-2" />
33
+ </button>
34
+ </div>
35
+
36
+ <div v-if="selectedOptions.length > 0" class="selected-summary">
37
+ <div class="flex items-center justify-between">
38
+ <div class="selected-count">
39
+ <span class="count-badge">
40
+ {{ selectedOptions.length }}
41
+ </span>
42
+ <span class="count-label">
43
+ item{{ selectedOptions.length === 1 ? '' : 's' }} selected
44
+ </span>
45
+ </div>
46
+ <button class="clear-button" @click.stop="clearAll">
47
+ Clear All
48
+ </button>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </transition>
53
+ </div>
54
+ </template>
55
+
56
+ <script setup>
57
+ import { ref, computed } from "vue";
58
+ import { DownIcon, CheckmarkIcon } from "@/components/icons";
59
+ import { useUIStore } from "@/stores/ui";
60
+
61
+ const ui = useUIStore();
62
+
63
+ const props = defineProps({
64
+ onSelect: { type: Function },
65
+ default: { type: String },
66
+ options: { type: Array, required: true },
67
+ rightAmount: { type: String },
68
+ topPadding: { type: String },
69
+ capitalize: { type: Boolean }
70
+ });
71
+
72
+ const selectedOptions = ref([]);
73
+ const id = Math.random();
74
+ const opened = computed(() => ui.currentDropdown === id);
75
+
76
+ const displayValue = computed(() => {
77
+ if (selectedOptions.value.length === 0) {
78
+ return props.default || "Select options...";
79
+ }
80
+
81
+ if (selectedOptions.value.length === 1) {
82
+ const option = props.options.find(opt => opt.value === selectedOptions.value[0]);
83
+ return option ? option.label : selectedOptions.value[0];
84
+ }
85
+
86
+ if (selectedOptions.value.length <= 2) {
87
+ const labels = selectedOptions.value.map(val => {
88
+ const option = props.options.find(opt => opt.value === val);
89
+ return option ? option.label : val;
90
+ });
91
+ return labels.join(", ");
92
+ }
93
+
94
+ const firstOption = props.options.find(opt => opt.value === selectedOptions.value[0]);
95
+ const firstName = firstOption ? firstOption.label : selectedOptions.value[0];
96
+ return `${firstName} +${selectedOptions.value.length - 1} more`;
97
+ });
98
+
99
+ const getSelectedLabels = () => {
100
+ return selectedOptions.value.map(val => {
101
+ const option = props.options.find(opt => opt.value === val);
102
+ return option ? option.label : val;
103
+ });
104
+ };
105
+
106
+ const toggleOpened = () => {
107
+ if (opened.value) ui.setCurrentDropdown("");
108
+ else ui.setCurrentDropdown(id);
109
+ };
110
+
111
+ const toggleOption = (option) => {
112
+ const index = selectedOptions.value.indexOf(option);
113
+ if (index === -1) {
114
+ selectedOptions.value.push(option);
115
+ } else {
116
+ selectedOptions.value.splice(index, 1);
117
+ }
118
+
119
+ // Handle default logic
120
+ if (selectedOptions.value.length === 0 && props.default) {
121
+ selectedOptions.value = [props.default];
122
+ }
123
+ if (selectedOptions.value.length > 1 && selectedOptions.value.includes(props.default) && option !== props.default) {
124
+ selectedOptions.value = selectedOptions.value.filter((e) => e !== props.default);
125
+ } else if (option === props.default) {
126
+ selectedOptions.value = [props.default];
127
+ }
128
+
129
+ if (typeof props.onSelect === "function") {
130
+ props.onSelect(selectedOptions.value);
131
+ }
132
+
133
+ // Don't close dropdown when selecting options
134
+ };
135
+
136
+ const clearAll = () => {
137
+ // Default to first option instead of empty
138
+ if (props.options && props.options.length > 0) {
139
+ selectedOptions.value = [props.options[0].value];
140
+ if (typeof props.onSelect === "function") {
141
+ props.onSelect([props.options[0].value]);
142
+ }
143
+ } else if (props.default) {
144
+ selectedOptions.value = [props.default];
145
+ if (typeof props.onSelect === "function") {
146
+ props.onSelect([props.default]);
147
+ }
148
+ } else {
149
+ selectedOptions.value = [];
150
+ if (typeof props.onSelect === "function") {
151
+ props.onSelect([]);
152
+ }
153
+ }
154
+ };
155
+
156
+ // Initialize with default option if provided
157
+ if (props.default && !selectedOptions.value.includes(props.default)) {
158
+ selectedOptions.value = [props.default];
159
+ }
160
+ </script>
161
+
162
+ <style scoped>
163
+ .dropdown {
164
+ @apply relative w-full p-2 h-12 text-white ml-auto rounded-lg ring-0;
165
+ }
166
+
167
+ @media (max-width: 810px) {
168
+ .dropdown {
169
+ @apply h-10;
170
+ }
171
+ }
172
+
173
+ .dropdown-display {
174
+ @apply flex items-center justify-between z-10;
175
+ }
176
+
177
+ .dropdown-value {
178
+ @apply w-full overflow-hidden block truncate pr-2;
179
+ }
180
+
181
+ .dropdown-counter {
182
+ @apply flex items-center gap-2 absolute right-2;
183
+ }
184
+
185
+ .counter-badge {
186
+ @apply bg-green-500 text-white text-xs font-semibold px-1.5 py-0.5 rounded-full text-center min-w-[18px];
187
+ }
188
+
189
+ .dropdown-arrow {
190
+ @apply min-w-4 min-h-4 transition-transform duration-200;
191
+ }
192
+
193
+ .dropdown-menu {
194
+ @apply absolute border border-light-300 rounded-lg shadow-lg z-50 overflow-y-auto;
195
+ top: 2.5rem;
196
+ left: -1px;
197
+ width: calc(100% + 2px);
198
+ box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
199
+ background-color: rgb(24, 24, 35);
200
+ scrollbar-width: none; /* Firefox */
201
+ }
202
+
203
+ .dropdown-menu::-webkit-scrollbar {
204
+ display: none; /* Chrome, Safari, Edge */
205
+ }
206
+
207
+ .dropdown-menu.multi .option-list {
208
+ @apply max-h-40 overflow-y-auto;
209
+ }
210
+
211
+ .dropdown-item {
212
+ @apply cursor-pointer text-left w-full py-2 px-3 text-white hover:bg-dark-400 transition-all duration-150 flex justify-between items-center;
213
+ }
214
+
215
+ .dropdown-item:first-child {
216
+ @apply rounded-t-lg;
217
+ }
218
+
219
+ .dropdown-item:last-child {
220
+ @apply rounded-b-lg;
221
+ }
222
+
223
+ .dropdown-item-text {
224
+ @apply overflow-hidden;
225
+ }
226
+
227
+ .selected-summary {
228
+ @apply border-t border-light-300 bg-dark-600 w-full px-4 py-3;
229
+ }
230
+
231
+ .selected-count {
232
+ @apply flex items-center gap-2;
233
+ }
234
+
235
+ .count-badge {
236
+ @apply bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-full;
237
+ }
238
+
239
+ .count-label {
240
+ @apply text-xs text-light-400 font-medium;
241
+ }
242
+
243
+ .clear-button {
244
+ @apply text-xs bg-dark-500 hover:bg-dark-400 text-white transition-all duration-150 font-medium px-2 py-1 rounded border border-light-300 hover:border-light-400;
245
+ }
246
+
247
+ /* Transition animations */
248
+ .dropdown-fade-enter-active,
249
+ .dropdown-fade-leave-active {
250
+ @apply transition-all duration-200;
251
+ }
252
+
253
+ .dropdown-fade-enter-from,
254
+ .dropdown-fade-leave-to {
255
+ @apply opacity-0 transform scale-95 -translate-y-1;
256
+ }
257
+
258
+ .dropdown-fade-enter-to,
259
+ .dropdown-fade-leave-from {
260
+ @apply opacity-100 transform scale-100 translate-y-0;
261
+ }
262
+ </style>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <label class="switch">
3
+ <input type="checkbox" v-model="value" />
4
+ <span class="slider round"></span>
5
+ </label>
6
+ </template>
7
+
8
+ <script setup>
9
+ const value = defineModel();
10
+ </script>
11
+
12
+ <style lang="scss" scoped>
13
+ /* The switch - the box around the slider */
14
+ .switch {
15
+ position: relative;
16
+ display: inline-block;
17
+ width: 60px;
18
+ height: 30px;
19
+ }
20
+
21
+ /* Hide default HTML checkbox */
22
+ .switch input {
23
+ opacity: 0;
24
+ width: 0;
25
+ height: 0;
26
+ }
27
+
28
+ /* The slider */
29
+ .slider {
30
+ position: absolute;
31
+ cursor: pointer;
32
+ top: 0;
33
+ left: 0;
34
+ right: 0;
35
+ bottom: 0;
36
+ transition: 0.2s;
37
+ opacity: 0.4;
38
+ @apply border border-white;
39
+ }
40
+
41
+ .slider:before {
42
+ position: absolute;
43
+ content: "";
44
+ height: 20px;
45
+ width: 20px;
46
+ left: 4px;
47
+ bottom: 4px;
48
+ background-color: white;
49
+ transition: 0.2s;
50
+ }
51
+
52
+ input:checked + .slider {
53
+ opacity: 1;
54
+ }
55
+
56
+ input:checked + .slider:before {
57
+ -webkit-transform: translateX(26px);
58
+ -ms-transform: translateX(26px);
59
+ transform: translateX(26px);
60
+ }
61
+
62
+ /* Rounded sliders */
63
+ .slider.round {
64
+ border-radius: 34px;
65
+ }
66
+
67
+ .slider.round:before {
68
+ border-radius: 50%;
69
+ }
70
+
71
+ @media (max-width: 810px) {
72
+ .switch {
73
+ width: 50px;
74
+ min-width: 50px;
75
+ height: 22px;
76
+
77
+ .slider:before {
78
+ width: 15px;
79
+ top: 3px;
80
+ height: 15px;
81
+ }
82
+ }
83
+ }
84
+ </style>