@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.
- package/.claude/settings.local.json +45 -0
- package/.eslintrc.js +24 -0
- package/.prettierignore +1 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +3 -0
- package/ICONS.md +21 -0
- package/README.md +65 -0
- package/backend/api.js +430 -0
- package/backend/auth.js +62 -0
- package/backend/batching.js +43 -0
- package/backend/endpoints.js +343 -0
- package/backend/index.js +23 -0
- package/backend/mock-data.js +66 -0
- package/backend/mock-src/classes/logger.js +112 -0
- package/backend/mock-src/classes/utils.js +42 -0
- package/backend/mock-src/ticketmaster.js +92 -0
- package/backend/validator.js +62 -0
- package/config/configs.json +20 -0
- package/config/filter.json +3 -0
- package/config/presale.csv +3 -0
- package/config/proxies.txt +6 -0
- package/config/used-codes.json +4 -0
- package/index.html +114 -0
- package/index.js +2 -0
- package/jsconfig.json +16 -0
- package/package.json +48 -0
- package/postcss.config.js +6 -0
- package/postinstall.js +9 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/flags/ae.svg +1 -0
- package/public/flags/at.svg +1 -0
- package/public/flags/au.svg +1 -0
- package/public/flags/be.svg +1 -0
- package/public/flags/ch.svg +1 -0
- package/public/flags/cz.svg +1 -0
- package/public/flags/de.svg +1 -0
- package/public/flags/dk.svg +1 -0
- package/public/flags/es.svg +1 -0
- package/public/flags/nl.svg +1 -0
- package/public/flags/no.svg +1 -0
- package/public/flags/nz.svg +1 -0
- package/public/flags/pl.svg +1 -0
- package/public/flags/se.svg +1 -0
- package/public/flags/uk.svg +1 -0
- package/public/flags/us.svg +1 -0
- package/public/img/award.svg +3 -0
- package/public/img/background.svg +14 -0
- package/public/img/bag_w.svg +12 -0
- package/public/img/banks/amex.svg +4 -0
- package/public/img/banks/mastercard.svg +4 -0
- package/public/img/banks/visa.svg +4 -0
- package/public/img/camera.svg +3 -0
- package/public/img/close.svg +3 -0
- package/public/img/controls/disable.svg +5 -0
- package/public/img/controls/enable.svg +5 -0
- package/public/img/groups.svg +3 -0
- package/public/img/hand.svg +3 -0
- package/public/img/key.svg +3 -0
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/loyalty.svg +3 -0
- package/public/img/mail.svg +3 -0
- package/public/img/pencil.svg +3 -0
- package/public/img/profile.svg +4 -0
- package/public/img/reload.svg +3 -0
- package/public/img/sandclock.svg +25 -0
- package/public/img/save.svg +5 -0
- package/public/img/savings.svg +3 -0
- package/public/img/scanner.svg +3 -0
- package/public/img/sell.svg +3 -0
- package/public/img/shield.svg +3 -0
- package/public/img/ski.svg +3 -0
- package/public/img/stadium.svg +8 -0
- package/public/img/stadium_w.svg +8 -0
- package/public/img/timer.svg +3 -0
- package/public/manifest.json +27 -0
- package/public/robots.txt +2 -0
- package/run +10 -0
- package/src/App.vue +307 -0
- package/src/assets/css/_input.scss +197 -0
- package/src/assets/css/main.scss +269 -0
- package/src/assets/css/tailwind.css +3 -0
- package/src/assets/img/award.svg +3 -0
- package/src/assets/img/background.svg +11 -0
- package/src/assets/img/camera.svg +3 -0
- package/src/assets/img/close.svg +3 -0
- package/src/assets/img/eyes/closed.svg +13 -0
- package/src/assets/img/eyes/open.svg +12 -0
- package/src/assets/img/groups.svg +3 -0
- package/src/assets/img/hand.svg +3 -0
- package/src/assets/img/key.svg +3 -0
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/assets/img/loyalty.svg +3 -0
- package/src/assets/img/mail.svg +3 -0
- package/src/assets/img/pencil.svg +3 -0
- package/src/assets/img/reload.svg +3 -0
- package/src/assets/img/savings.svg +3 -0
- package/src/assets/img/scanner.svg +3 -0
- package/src/assets/img/sell.svg +3 -0
- package/src/assets/img/shield.svg +3 -0
- package/src/assets/img/ski.svg +3 -0
- package/src/assets/img/square_check.svg +5 -0
- package/src/assets/img/square_uncheck.svg +5 -0
- package/src/assets/img/stadium.svg +8 -0
- package/src/assets/img/timer.svg +3 -0
- package/src/assets/img/wildcard.svg +7 -0
- package/src/components/Auth/LoginForm.vue +48 -0
- package/src/components/Editors/Account/Account.vue +119 -0
- package/src/components/Editors/Account/AccountCreator.vue +147 -0
- package/src/components/Editors/Account/AccountView.vue +87 -0
- package/src/components/Editors/Account/CreateAccount.vue +106 -0
- package/src/components/Editors/Profile/CreateProfile.vue +321 -0
- package/src/components/Editors/Profile/Profile.vue +142 -0
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
- package/src/components/Editors/Profile/ProfileView.vue +96 -0
- package/src/components/Editors/TagLabel.vue +16 -0
- package/src/components/Editors/TagToggle.vue +41 -0
- package/src/components/Filter/Filter.vue +409 -0
- package/src/components/Filter/FilterPreview.vue +236 -0
- package/src/components/Filter/PriceSortToggle.vue +105 -0
- package/src/components/Table/Header.vue +5 -0
- package/src/components/Table/Row.vue +5 -0
- package/src/components/Table/Table.vue +14 -0
- package/src/components/Table/index.js +4 -0
- package/src/components/Tasks/CheckStock.vue +62 -0
- package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
- package/src/components/Tasks/Controls/MobileControls.vue +32 -0
- package/src/components/Tasks/Controls/index.js +3 -0
- package/src/components/Tasks/CreateTaskAXS.vue +339 -0
- package/src/components/Tasks/CreateTaskTM.vue +459 -0
- package/src/components/Tasks/MassEdit.vue +50 -0
- package/src/components/Tasks/QuickSettings.vue +167 -0
- package/src/components/Tasks/ScrapeVenue.vue +42 -0
- package/src/components/Tasks/Stats.vue +66 -0
- package/src/components/Tasks/Task.vue +296 -0
- package/src/components/Tasks/TaskLabel.vue +20 -0
- package/src/components/Tasks/TaskView.vue +126 -0
- package/src/components/Tasks/Utilities.vue +33 -0
- package/src/components/icons/Award.vue +8 -0
- package/src/components/icons/Bag.vue +8 -0
- package/src/components/icons/BagWhite.vue +8 -0
- package/src/components/icons/Box.vue +8 -0
- package/src/components/icons/Camera.vue +8 -0
- package/src/components/icons/Cart.vue +8 -0
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Checkmark.vue +11 -0
- package/src/components/icons/Click.vue +8 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Console.vue +13 -0
- package/src/components/icons/Down.vue +8 -0
- package/src/components/icons/Edit.vue +13 -0
- package/src/components/icons/Event.vue +8 -0
- package/src/components/icons/Expand.vue +8 -0
- package/src/components/icons/Filter.vue +13 -0
- package/src/components/icons/Gear.vue +8 -0
- package/src/components/icons/Group.vue +8 -0
- package/src/components/icons/Hand.vue +8 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Logout.vue +13 -0
- package/src/components/icons/Loyalty.vue +8 -0
- package/src/components/icons/Mail.vue +8 -0
- package/src/components/icons/Menu.vue +8 -0
- package/src/components/icons/Pause.vue +5 -0
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +8 -0
- package/src/components/icons/Plus.vue +8 -0
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +7 -0
- package/src/components/icons/Sandclock.vue +33 -0
- package/src/components/icons/Savings.vue +8 -0
- package/src/components/icons/Scanner.vue +8 -0
- package/src/components/icons/Scrape.vue +8 -0
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Shield.vue +8 -0
- package/src/components/icons/Shrink.vue +8 -0
- package/src/components/icons/Ski.vue +8 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +13 -0
- package/src/components/icons/StadiumWhite.vue +13 -0
- package/src/components/icons/Status.vue +8 -0
- package/src/components/icons/Tag.vue +8 -0
- package/src/components/icons/Tasks.vue +13 -0
- package/src/components/icons/Ticket.vue +8 -0
- package/src/components/icons/Timer.vue +8 -0
- package/src/components/icons/Trash.vue +8 -0
- package/src/components/icons/Up.vue +10 -0
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +111 -0
- package/src/components/ui/Modal.vue +61 -0
- package/src/components/ui/Navbar.vue +207 -0
- package/src/components/ui/ReconnectIndicator.vue +90 -0
- package/src/components/ui/Splash.vue +24 -0
- package/src/components/ui/controls/CountryChooser.vue +87 -0
- package/src/components/ui/controls/EyeToggle.vue +11 -0
- package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
- package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
- package/src/components/ui/controls/atomic/Switch.vue +84 -0
- package/src/libs/Filter.js +593 -0
- package/src/libs/ansii.js +565 -0
- package/src/libs/panzoom.js +1413 -0
- package/src/main.js +23 -0
- package/src/registerServiceWorker.js +32 -0
- package/src/router/index.js +65 -0
- package/src/stores/cities.json +1 -0
- package/src/stores/connection.js +399 -0
- package/src/stores/countries.js +88 -0
- package/src/stores/logger.js +103 -0
- package/src/stores/requests.js +88 -0
- package/src/stores/sampleData.js +1034 -0
- package/src/stores/ui.js +584 -0
- package/src/stores/utils.js +554 -0
- package/src/types/index.js +42 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +191 -0
- package/src/views/Console.vue +224 -0
- package/src/views/Editor.vue +785 -0
- package/src/views/FilterBuilder.vue +785 -0
- package/src/views/Login.vue +27 -0
- package/src/views/Profiles.vue +209 -0
- package/src/views/Tasks.vue +157 -0
- package/static/offline.html +184 -0
- package/tailwind.config.js +57 -0
- package/vite.config.js +63 -0
- package/vue.config.js +32 -0
- 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>
|