@necrolab/dashboard 0.4.48 → 0.4.50
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 +2 -1
- package/.prettierrc +12 -1
- package/exit +209 -0
- package/index.html +1 -1
- package/package.json +1 -1
- package/public/manifest.json +8 -3
- package/src/assets/css/_input.scss +104 -111
- package/src/assets/css/_utilities.scss +441 -0
- package/src/assets/css/main.scss +228 -154
- package/src/components/Auth/LoginForm.vue +49 -6
- package/src/components/Editors/Account/Account.vue +156 -146
- package/src/components/Editors/Account/AccountCreator.vue +1 -1
- package/src/components/Editors/Account/AccountView.vue +13 -13
- package/src/components/Editors/Account/CreateAccount.vue +25 -16
- package/src/components/Editors/Profile/CreateProfile.vue +1 -1
- package/src/components/Editors/Profile/Profile.vue +1 -1
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +83 -19
- package/src/components/Editors/Profile/ProfileView.vue +11 -11
- package/src/components/Tasks/CreateTaskAXS.vue +3 -3
- package/src/components/Tasks/CreateTaskTM.vue +7 -35
- package/src/components/Tasks/QuickSettings.vue +112 -9
- package/src/components/Tasks/Stats.vue +29 -26
- package/src/components/Tasks/Task.vue +499 -365
- package/src/components/Tasks/TaskView.vue +187 -127
- package/src/components/icons/Sandclock.vue +2 -2
- package/src/components/icons/Stadium.vue +1 -1
- package/src/components/ui/Modal.vue +37 -35
- package/src/components/ui/controls/CountryChooser.vue +200 -62
- package/src/components/ui/controls/atomic/Dropdown.vue +177 -91
- package/src/components/ui/controls/atomic/MultiDropdown.vue +247 -168
- package/src/composables/useClickOutside.js +21 -0
- package/src/composables/useDropdownPosition.js +174 -0
- package/src/stores/sampleData.js +4 -2
- package/src/stores/ui.js +8 -6
- package/src/views/Accounts.vue +12 -19
- package/src/views/Console.vue +34 -47
- package/src/views/Editor.vue +1194 -730
- package/src/views/Login.vue +65 -119
- package/src/views/Profiles.vue +2 -2
- package/src/views/Tasks.vue +170 -137
- package/static/offline.html +192 -50
- package/tailwind.config.js +47 -21
|
@@ -1,140 +1,188 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<EventIcon class="ipadlg:mr-3" />
|
|
12
|
-
<h4 class="hidden ipadlg:flex">Event</h4>
|
|
13
|
-
<DownIcon v-if="ui.sortData.sortBy === 'eventId' && !ui.sortData.reversed" class="ml-1" />
|
|
14
|
-
<UpIcon v-if="ui.sortData.sortBy === 'eventId' && ui.sortData.reversed" class="ml-1" />
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
<div class="col-span-2 flex-center" v-once>
|
|
18
|
-
<TicketIcon class="mr-0 ipadlg:mr-3" />
|
|
19
|
-
<h4 class="hidden ipadlg:flex">Tickets</h4>
|
|
20
|
-
</div>
|
|
21
|
-
<div
|
|
22
|
-
class="col-span-5 md:col-span-4 lg:col-span-5 ipadlg:col-span-6 flex-center"
|
|
23
|
-
@click="ui.toggleSort('status')">
|
|
24
|
-
<StatusIcon class="mr-0 ipadlg:mr-3" />
|
|
25
|
-
<h4 class="hidden ipadlg:flex">Status</h4>
|
|
26
|
-
<DownIcon v-if="ui.sortData.sortBy === 'status' && !ui.sortData.reversed" class="ml-1" />
|
|
27
|
-
<UpIcon v-if="ui.sortData.sortBy === 'status' && ui.sortData.reversed" class="ml-1" />
|
|
28
|
-
</div>
|
|
29
|
-
<div class="col-span-2 ipadlg:col-span-3 flex-center" v-once>
|
|
30
|
-
<ClickIcon class="mr-0 ipadlg:mr-3" />
|
|
31
|
-
<h4 class="hidden ipadlg:flex">Actions</h4>
|
|
32
|
-
</div>
|
|
33
|
-
<div class="absolute right-5 hidden md:flex items-center top-3,5" @click="ui.toggleSort('taskId')">
|
|
34
|
-
<h4 class="">ID</h4>
|
|
35
|
-
<DownIcon v-if="ui.sortData.sortBy === 'taskId' && !ui.sortData.reversed" class="ml-1" />
|
|
36
|
-
<UpIcon v-if="ui.sortData.sortBy === 'taskId' && ui.sortData.reversed" class="ml-1" />
|
|
37
|
-
</div>
|
|
38
|
-
</Header>
|
|
2
|
+
<div class="table-component">
|
|
3
|
+
<Header class="text-center grid-cols-10 gap-2 lg:grid-cols-12">
|
|
4
|
+
<div class="col-span-1 lg:col-span-2 flex items-center justify-start">
|
|
5
|
+
<Checkbox
|
|
6
|
+
class="ml-2 mr-4 flex-shrink-0"
|
|
7
|
+
:toggled="ui.mainCheckbox.tasks"
|
|
8
|
+
@valueUpdate="ui.toggleMainCheckbox('tasks')"
|
|
9
|
+
:isHeader="true"
|
|
10
|
+
/>
|
|
39
11
|
<div
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<div v-else>
|
|
54
|
-
<TasksIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50 mx-auto" />
|
|
55
|
-
<p class="text-light-400 text-sm">
|
|
56
|
-
{{ ui.queueStats.total }} hidden task{{ ui.queueStats.total === 1 ? "" : "s" }}
|
|
57
|
-
</p>
|
|
58
|
-
<p class="text-light-500 text-xs mt-1">Adjust filters to see tasks</p>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
12
|
+
class="mx-auto hidden lg:flex items-center"
|
|
13
|
+
@click="ui.toggleSort('eventId')"
|
|
14
|
+
>
|
|
15
|
+
<EventIcon class="lg:mr-3" />
|
|
16
|
+
<h4 class="hidden lg:flex">Event</h4>
|
|
17
|
+
<DownIcon
|
|
18
|
+
v-if="ui.sortData.sortBy === 'eventId' && !ui.sortData.reversed"
|
|
19
|
+
class="ml-1"
|
|
20
|
+
/>
|
|
21
|
+
<UpIcon
|
|
22
|
+
v-if="ui.sortData.sortBy === 'eventId' && ui.sortData.reversed"
|
|
23
|
+
class="ml-1"
|
|
24
|
+
/>
|
|
61
25
|
</div>
|
|
62
|
-
|
|
26
|
+
</div>
|
|
27
|
+
<div class="col-span-2 flex-center" v-once>
|
|
28
|
+
<TicketIcon class="mr-0 lg:mr-3" />
|
|
29
|
+
<h4 class="hidden lg:flex">Tickets</h4>
|
|
30
|
+
</div>
|
|
31
|
+
<div
|
|
32
|
+
class="col-span-5 md:col-span-4 lg:col-span-5 flex-center"
|
|
33
|
+
@click="ui.toggleSort('status')"
|
|
34
|
+
>
|
|
35
|
+
<StatusIcon class="mr-0 lg:mr-3" />
|
|
36
|
+
<h4 class="hidden lg:flex">Status</h4>
|
|
37
|
+
<DownIcon
|
|
38
|
+
v-if="ui.sortData.sortBy === 'status' && !ui.sortData.reversed"
|
|
39
|
+
class="ml-1"
|
|
40
|
+
/>
|
|
41
|
+
<UpIcon
|
|
42
|
+
v-if="ui.sortData.sortBy === 'status' && ui.sortData.reversed"
|
|
43
|
+
class="ml-1"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="col-span-2 lg:col-span-3 flex-center" v-once>
|
|
47
|
+
<ClickIcon class="mr-0 lg:mr-3" />
|
|
48
|
+
<h4 class="hidden lg:flex">Actions</h4>
|
|
49
|
+
</div>
|
|
50
|
+
<div
|
|
51
|
+
class="absolute right-5 hidden lg:flex items-center top-3.5"
|
|
52
|
+
@click="ui.toggleSort('taskId')"
|
|
53
|
+
>
|
|
54
|
+
<h4 class="text-center text-xs">ID</h4>
|
|
55
|
+
<DownIcon
|
|
56
|
+
v-if="ui.sortData.sortBy === 'taskId' && !ui.sortData.reversed"
|
|
57
|
+
class="ml-1"
|
|
58
|
+
/>
|
|
59
|
+
<UpIcon
|
|
60
|
+
v-if="ui.sortData.sortBy === 'taskId' && ui.sortData.reversed"
|
|
61
|
+
class="ml-1"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
</Header>
|
|
65
|
+
<div
|
|
66
|
+
class="flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
|
|
67
|
+
:style="{ maxHeight: dynamicTableHeight }"
|
|
68
|
+
>
|
|
69
|
+
<div
|
|
70
|
+
v-for="(task, i) in getTasksInOrder()"
|
|
71
|
+
:key="task.taskId"
|
|
72
|
+
class="task-row-container"
|
|
73
|
+
>
|
|
74
|
+
<Task :task="task" :class="i % 2 == 1 ? 'table-row-even' : 'table-row-odd'" />
|
|
75
|
+
</div>
|
|
76
|
+
<div
|
|
77
|
+
v-if="getTasksInOrder().length === 0"
|
|
78
|
+
class="flex flex-col items-center justify-center py-8 empty-state text-center"
|
|
79
|
+
>
|
|
80
|
+
<div
|
|
81
|
+
v-if="
|
|
82
|
+
!ui.queueStats.queued &&
|
|
83
|
+
!ui.queueStats.sleeping &&
|
|
84
|
+
ui.queueStats.nextQueuePasses.length === 0
|
|
85
|
+
"
|
|
86
|
+
>
|
|
87
|
+
<TasksIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50 mx-auto" />
|
|
88
|
+
<p class="text-light-400 text-sm">No tasks yet</p>
|
|
89
|
+
<p class="text-light-500 text-xs mt-1">Create tasks to get started</p>
|
|
90
|
+
</div>
|
|
91
|
+
<div v-else>
|
|
92
|
+
<TasksIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50 mx-auto" />
|
|
93
|
+
<p class="text-light-400 text-sm">No tasks match current filters</p>
|
|
94
|
+
<p class="text-light-500 text-xs mt-1">Adjust filters to see tasks</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
63
99
|
</template>
|
|
64
100
|
<style lang="scss" scoped>
|
|
65
101
|
h4 {
|
|
66
|
-
|
|
102
|
+
@apply text-white;
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
.stop-pan {
|
|
70
|
-
|
|
106
|
+
touch-action: pan-y pan-up pan-down;
|
|
71
107
|
}
|
|
72
108
|
|
|
73
109
|
.task-row-container {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
110
|
+
min-height: 69px;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
transition: background-color 0.15s ease;
|
|
77
113
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
114
|
+
&:hover {
|
|
115
|
+
@apply bg-dark-550 !important;
|
|
116
|
+
}
|
|
81
117
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
118
|
+
@media (max-width: 768px) {
|
|
119
|
+
min-height: 58px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (max-width: 480px) and (orientation: portrait) {
|
|
123
|
+
min-height: 50px;
|
|
124
|
+
}
|
|
85
125
|
}
|
|
86
126
|
|
|
87
127
|
.empty-state {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
128
|
+
@apply bg-dark-400;
|
|
129
|
+
color: #969696;
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
font-weight: 500;
|
|
92
132
|
}
|
|
93
133
|
</style>
|
|
94
134
|
<script setup>
|
|
95
135
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
96
136
|
import { Table, Header } from "@/components/Table";
|
|
97
|
-
import {
|
|
137
|
+
import {
|
|
138
|
+
EventIcon,
|
|
139
|
+
TicketIcon,
|
|
140
|
+
StatusIcon,
|
|
141
|
+
ClickIcon,
|
|
142
|
+
DownIcon,
|
|
143
|
+
UpIcon,
|
|
144
|
+
TasksIcon,
|
|
145
|
+
} from "@/components/icons";
|
|
98
146
|
import Task from "./Task.vue";
|
|
99
147
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
100
148
|
import { useUIStore } from "@/stores/ui";
|
|
101
149
|
|
|
102
150
|
const props = defineProps({
|
|
103
|
-
|
|
151
|
+
tasks: { type: Object },
|
|
104
152
|
});
|
|
105
153
|
|
|
106
154
|
const shouldTaskShow = (task) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
if (ui.taskFilter === "All") return true;
|
|
156
|
+
else if (ui.taskFilter === "Checkout") return task.expirationTime || task.noCartholds;
|
|
157
|
+
else return true;
|
|
110
158
|
};
|
|
111
159
|
|
|
112
160
|
const ui = useUIStore();
|
|
113
161
|
|
|
114
162
|
const siteIdEdgeCases = {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
163
|
+
LN_US: ["TM_US"],
|
|
164
|
+
TM_CA: ["TM_US"],
|
|
165
|
+
TM_IE: ["TM_UK"],
|
|
166
|
+
TM_NZ: ["TM_AU"],
|
|
119
167
|
};
|
|
120
168
|
|
|
121
169
|
const getTasksInOrder = () => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
170
|
+
let out = [];
|
|
171
|
+
ui.taskIdOrder.forEach((id) => {
|
|
172
|
+
if (props.tasks[id] && !props.tasks[id]?.hidden) {
|
|
173
|
+
const task = props.tasks[id];
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
task.siteId !== ui.currentCountry.siteId &&
|
|
177
|
+
!siteIdEdgeCases[task.siteId]?.includes(ui.currentCountry.siteId)
|
|
178
|
+
)
|
|
179
|
+
return;
|
|
180
|
+
if (ui.currentEvent && task.eventId !== ui.currentEvent) return;
|
|
181
|
+
if (!shouldTaskShow(task)) return;
|
|
182
|
+
out.push(task);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return out;
|
|
138
186
|
};
|
|
139
187
|
|
|
140
188
|
// Dynamic height calculation to prevent page scrolling
|
|
@@ -142,42 +190,54 @@ const windowHeight = ref(window.innerHeight);
|
|
|
142
190
|
const windowWidth = ref(window.innerWidth);
|
|
143
191
|
|
|
144
192
|
const updateDimensions = () => {
|
|
145
|
-
|
|
146
|
-
|
|
193
|
+
windowHeight.value = window.innerHeight;
|
|
194
|
+
windowWidth.value = window.innerWidth;
|
|
147
195
|
};
|
|
148
196
|
|
|
149
197
|
onMounted(() => {
|
|
150
|
-
|
|
198
|
+
window.addEventListener("resize", updateDimensions);
|
|
151
199
|
});
|
|
152
200
|
|
|
153
201
|
onUnmounted(() => {
|
|
154
|
-
|
|
202
|
+
window.removeEventListener("resize", updateDimensions);
|
|
155
203
|
});
|
|
156
204
|
|
|
157
205
|
const dynamicTableHeight = computed(() => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
// Calculate available space for table
|
|
207
|
+
const headerHeight = 60; // Header + navbar
|
|
208
|
+
const titleHeight = 50; // Tasks title and mobile controls
|
|
209
|
+
const statsHeight = 50; // Queue stats
|
|
210
|
+
const controlsHeight = windowWidth.value >= 650 ? 60 : 0; // Desktop controls
|
|
211
|
+
const filtersHeight = 50; // Event dropdown and filter toggle
|
|
212
|
+
const utilitiesHeight =
|
|
213
|
+
windowWidth.value <= 480 && windowHeight.value > windowWidth.value ? 150 : 200; // Reduce utilities height in mobile portrait
|
|
214
|
+
const margins =
|
|
215
|
+
windowWidth.value >= 1024
|
|
216
|
+
? 40
|
|
217
|
+
: windowWidth.value <= 480 && windowHeight.value > windowWidth.value
|
|
218
|
+
? 15
|
|
219
|
+
: 20; // Reduce margins in mobile portrait
|
|
220
|
+
|
|
221
|
+
const totalUsedSpace =
|
|
222
|
+
headerHeight +
|
|
223
|
+
titleHeight +
|
|
224
|
+
statsHeight +
|
|
225
|
+
controlsHeight +
|
|
226
|
+
filtersHeight +
|
|
227
|
+
utilitiesHeight +
|
|
228
|
+
margins;
|
|
229
|
+
const availableHeight = windowHeight.value - totalUsedSpace;
|
|
230
|
+
|
|
231
|
+
// Calculate row height based on screen size
|
|
232
|
+
const rowHeight = windowWidth.value <= 768 ? 58 : 69; // Mobile vs desktop row height
|
|
233
|
+
const minRowsToShow = 2; // Always show at least 2 rows
|
|
234
|
+
const minHeight = minRowsToShow * rowHeight;
|
|
235
|
+
|
|
236
|
+
// Calculate how many complete rows can fit
|
|
237
|
+
const maxCompleteRows =
|
|
238
|
+
Math.floor(Math.max(availableHeight, minHeight) / rowHeight) + 1;
|
|
239
|
+
const exactHeight = maxCompleteRows * rowHeight;
|
|
240
|
+
|
|
241
|
+
return exactHeight + "px";
|
|
182
242
|
});
|
|
183
243
|
</script>
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
<g>
|
|
14
14
|
<g>
|
|
15
15
|
<path
|
|
16
|
-
|
|
16
|
+
fill="white"
|
|
17
17
|
d="M16.192,5.224V4.487h-8.77v0.737c0,0,1.334,3.713,3.838,5.428v1.785c0,0-2.761,2.686-3.838,5.775
|
|
18
18
|
v0.842h8.77v-0.842c-1.399-3.41-3.837-5.775-3.837-5.775v-1.785C15.759,7.726,16.192,5.224,16.192,5.224z"
|
|
19
19
|
/>
|
|
20
20
|
</g>
|
|
21
21
|
<g>
|
|
22
22
|
<path
|
|
23
|
-
|
|
23
|
+
fill="white"
|
|
24
24
|
d="M19.353,3.856V2.529h1.258V0H3.002v2.529h1.259v1.327c0,2.025,3.634,7.555,3.804,7.955
|
|
25
25
|
c-0.167,0.397-3.804,5.929-3.804,7.946v1.325H3.002v2.53h17.609v-2.53h-1.258v-1.325c0-2.025-3.635-7.521-3.829-7.951
|
|
26
26
|
C15.718,11.376,19.353,5.88,19.353,3.856z M18.096,19.757v1.325H5.519v-1.325c0-1.455,3.854-7.222,3.854-7.951
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<g mask="url(#mask0_16_241)">
|
|
7
7
|
<path
|
|
8
8
|
d="M2.37499 5.54165V2.37498L5.54166 3.95831L2.37499 5.54165ZM14.25 5.54165V2.37498L17.4167 3.95831L14.25 5.54165ZM8.70833 4.74998V1.58331L11.875 3.16665L8.70833 4.74998ZM8.70833 17.4166C7.70555 17.3903 6.77218 17.3079 5.9082 17.1696C5.0437 17.0308 4.29162 16.8559 3.65195 16.6448C3.01176 16.4337 2.50694 16.1896 2.13749 15.9125C1.76805 15.6354 1.58333 15.3451 1.58333 15.0416V7.91665C1.58333 7.58678 1.79127 7.27988 2.20716 6.99594C2.62252 6.71252 3.18645 6.46183 3.89895 6.24385C4.61145 6.02641 5.4493 5.85488 6.4125 5.72927C7.37569 5.60419 8.40486 5.54165 9.49999 5.54165C10.5951 5.54165 11.6243 5.60419 12.5875 5.72927C13.5507 5.85488 14.3885 6.02641 15.101 6.24385C15.8135 6.46183 16.3775 6.71252 16.7928 6.99594C17.2087 7.27988 17.4167 7.58678 17.4167 7.91665V15.0416C17.4167 15.3451 17.2319 15.6354 16.8625 15.9125C16.493 16.1896 15.9885 16.4337 15.3488 16.6448C14.7086 16.8559 13.9565 17.0308 13.0926 17.1696C12.2281 17.3079 11.2944 17.3903 10.2917 17.4166V14.25H8.70833V17.4166ZM9.49999 8.70831C10.7799 8.70831 11.885 8.63231 12.8155 8.48031C13.7454 8.32884 14.4875 8.15415 15.0417 7.95623C15.0417 7.89026 14.5403 7.73509 13.5375 7.49073C12.5347 7.2469 11.1889 7.12498 9.49999 7.12498C7.81111 7.12498 6.46527 7.2469 5.46249 7.49073C4.45972 7.73509 3.95833 7.89026 3.95833 7.95623C4.51249 8.15415 5.25481 8.32884 6.18529 8.48031C7.11523 8.63231 8.22013 8.70831 9.49999 8.70831ZM7.12499 15.7146V12.6666H11.875V15.7146C12.9305 15.609 13.7948 15.4538 14.4677 15.2491C15.1406 15.0448 15.5958 14.8635 15.8333 14.7052V9.34165C15.1076 9.63192 14.1972 9.86283 13.1021 10.0344C12.0069 10.2059 10.8062 10.2916 9.49999 10.2916C8.19374 10.2916 6.99305 10.2059 5.89791 10.0344C4.80277 9.86283 3.89236 9.63192 3.16666 9.34165V14.7052C3.40416 14.8635 3.85937 15.0448 4.53229 15.2491C5.2052 15.4538 6.06944 15.609 7.12499 15.7146Z"
|
|
9
|
-
fill="
|
|
9
|
+
fill="white"
|
|
10
10
|
/>
|
|
11
11
|
</g>
|
|
12
12
|
</svg>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="modal-mask pt-14
|
|
2
|
+
<div class="modal-mask pt-14 md:py-1 overflow-y-scroll scrollable" @touchmove.prevent>
|
|
3
3
|
<div class="component-modal" ref="target" @touchmove.stop>
|
|
4
4
|
<div class="modal-header">
|
|
5
5
|
<slot name="header" />
|
|
@@ -57,17 +57,19 @@ onClickOutside(target, (event) => {
|
|
|
57
57
|
<style lang="scss" scoped>
|
|
58
58
|
.modal-mask {
|
|
59
59
|
@apply fixed top-0 left-0 w-screen h-screen flex duration-300 ease-in-out;
|
|
60
|
-
z-index:
|
|
60
|
+
z-index: 25000;
|
|
61
61
|
background-color: rgba(17, 17, 17, 0.85);
|
|
62
62
|
backdrop-filter: blur(4px);
|
|
63
|
-
align-items: center;
|
|
63
|
+
align-items: center;
|
|
64
64
|
justify-content: center;
|
|
65
|
-
padding:
|
|
65
|
+
padding: 1rem;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
.component-modal {
|
|
69
69
|
width: 640px;
|
|
70
|
-
|
|
70
|
+
max-height: calc(100vh - 8rem); // Ensure bottom margin on desktop
|
|
71
|
+
margin-bottom: 3rem;
|
|
72
|
+
overflow-y: auto; // Make scrollable when content is too tall
|
|
71
73
|
@apply bg-dark-300 px-5 py-5 rounded-lg flex flex-col;
|
|
72
74
|
|
|
73
75
|
.modal-header {
|
|
@@ -80,6 +82,8 @@ onClickOutside(target, (event) => {
|
|
|
80
82
|
|
|
81
83
|
.modal-body {
|
|
82
84
|
@apply flex flex-col;
|
|
85
|
+
flex: 1;
|
|
86
|
+
min-height: 0;
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -88,54 +92,52 @@ onClickOutside(target, (event) => {
|
|
|
88
92
|
align-items: flex-start; // Start from top on mobile
|
|
89
93
|
justify-content: center;
|
|
90
94
|
padding: 1rem;
|
|
91
|
-
padding-top:
|
|
95
|
+
padding-top: 3rem;
|
|
96
|
+
padding-bottom: 2rem; // Ensure bottom space
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
.component-modal {
|
|
95
100
|
width: calc(100vw - 2rem);
|
|
96
|
-
|
|
97
|
-
overflow-y: auto; // Only add scrolling on mobile
|
|
101
|
+
margin-bottom: 6rem; // Increased bottom margin for better button access
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
.modal-body {
|
|
101
|
-
padding-bottom:
|
|
105
|
+
padding-bottom: 4rem; // Increased padding for create button accessibility
|
|
102
106
|
overflow-y: auto; // Allow scrolling on mobile
|
|
103
107
|
flex: 1;
|
|
104
108
|
min-height: 0;
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
@media
|
|
112
|
+
/* iPhone portrait mode - extra spacing for create button */
|
|
113
|
+
@media (max-width: 480px) and (orientation: portrait) {
|
|
110
114
|
.modal-mask {
|
|
111
|
-
|
|
112
|
-
left: 0;
|
|
113
|
-
right: 0;
|
|
114
|
-
bottom: 0;
|
|
115
|
-
width: 100vw;
|
|
116
|
-
height: 100vh;
|
|
117
|
-
padding: 0;
|
|
118
|
-
padding-top: calc(env(safe-area-inset-top, 0px) + 4rem); // Navbar + safe area
|
|
119
|
-
padding-bottom: env(safe-area-inset-bottom, 0.5rem);
|
|
120
|
-
padding-left: env(safe-area-inset-left, 0.5rem);
|
|
121
|
-
padding-right: env(safe-area-inset-right, 0.5rem);
|
|
122
|
-
align-items: flex-start;
|
|
123
|
-
justify-content: center;
|
|
115
|
+
padding-bottom: 3rem !important; // Extra bottom padding for iPhone
|
|
124
116
|
}
|
|
125
|
-
|
|
117
|
+
|
|
126
118
|
.component-modal {
|
|
127
|
-
|
|
128
|
-
max-
|
|
129
|
-
max-height: calc(100vh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 5rem);
|
|
130
|
-
min-height: auto;
|
|
131
|
-
margin: 0;
|
|
119
|
+
margin-bottom: 8rem !important; // Even more bottom margin for iPhone portrait
|
|
120
|
+
max-height: calc(100vh - 12rem) !important; // Ensure modal doesn't get too tall
|
|
132
121
|
}
|
|
133
|
-
|
|
122
|
+
|
|
134
123
|
.modal-body {
|
|
135
|
-
padding-bottom:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
124
|
+
padding-bottom: 5rem !important; // Extra padding for create button on iPhone
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// iPhone landscape mode - proper modal centering with consistent spacing
|
|
129
|
+
@media (max-height: 500px) and (orientation: landscape) {
|
|
130
|
+
.modal-mask {
|
|
131
|
+
padding: 1rem 1rem 4rem 1rem !important;
|
|
132
|
+
align-items: center !important;
|
|
133
|
+
justify-content: center !important;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.component-modal {
|
|
137
|
+
max-height: calc(100vh - 10rem) !important;
|
|
138
|
+
overflow-y: auto !important;
|
|
139
|
+
margin: 0 !important;
|
|
140
|
+
margin-bottom: 30px;
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
</style>
|