From 2bd2c8dbcabc7c25ba862f65546658c6ef538446 Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Sun, 1 Mar 2026 15:19:55 +0300 Subject: [PATCH] Enhance frontend components and introduce new features - Added a custom light theme in app.css to match the dark theme's palette. - Introduced AdminBreadcrumbs component for improved navigation in admin pages. - Implemented Skeleton component for loading states in various views. - Added ToastContainer for displaying notifications and alerts. - Enhanced MapView with loading indicators and improved marker handling. - Updated MapCoordsDisplay to allow copying of shareable links. - Refactored MapControls and MapContextMenu for better usability. - Improved user experience in profile and admin pages with loading states and search functionality. --- frontend-nuxt/assets/css/app.css | 22 ++ frontend-nuxt/components/AdminBreadcrumbs.vue | 55 +++++ frontend-nuxt/components/MapView.vue | 45 +++- frontend-nuxt/components/Skeleton.vue | 10 + frontend-nuxt/components/ToastContainer.vue | 54 +++++ frontend-nuxt/components/map/MapControls.vue | 217 +++++++++++------- .../components/map/MapCoordsDisplay.vue | 43 +++- frontend-nuxt/composables/useMapLayers.ts | 12 +- frontend-nuxt/composables/useToast.ts | 36 +++ frontend-nuxt/layouts/default.vue | 157 +++++++++---- frontend-nuxt/lib/LeafletCustomTypes.ts | 19 +- frontend-nuxt/lib/Marker.ts | 20 +- frontend-nuxt/pages/admin/index.vue | 205 ++++++++++++++--- frontend-nuxt/pages/admin/maps/[id].vue | 7 + frontend-nuxt/pages/profile.vue | 127 +++++++--- 15 files changed, 817 insertions(+), 212 deletions(-) create mode 100644 frontend-nuxt/components/AdminBreadcrumbs.vue create mode 100644 frontend-nuxt/components/Skeleton.vue create mode 100644 frontend-nuxt/components/ToastContainer.vue create mode 100644 frontend-nuxt/composables/useToast.ts diff --git a/frontend-nuxt/assets/css/app.css b/frontend-nuxt/assets/css/app.css index 7f05be1..078071d 100644 --- a/frontend-nuxt/assets/css/app.css +++ b/frontend-nuxt/assets/css/app.css @@ -3,6 +3,28 @@ themes: light --default; } +/* Custom light theme — OKLCH matching the dark theme's violet palette */ +@plugin "daisyui/theme" { + name: "light"; + color-scheme: light; + --color-primary: oklch(50% 0.22 277); + --color-primary-content: oklch(100% 0 0); + --color-secondary: oklch(52% 0.22 293); + --color-secondary-content: oklch(100% 0 0); + --color-accent: oklch(55% 0.14 203); + --color-accent-content: oklch(100% 0 0); + --color-neutral: oklch(25% 0.02 249); + --color-neutral-content: oklch(98% 0.005 249); + --color-base-100: oklch(99% 0.005 250); + --color-base-200: oklch(96% 0.008 251); + --color-base-300: oklch(92% 0.01 250); + --color-base-content: oklch(22% 0.02 249); + --color-info: oklch(55% 0.15 250); + --color-success: oklch(55% 0.16 155); + --color-warning: oklch(75% 0.15 85); + --color-error: oklch(55% 0.22 25); +} + @plugin "daisyui/theme" { name: "dark"; prefersdark: true; diff --git a/frontend-nuxt/components/AdminBreadcrumbs.vue b/frontend-nuxt/components/AdminBreadcrumbs.vue new file mode 100644 index 0000000..8c1afe5 --- /dev/null +++ b/frontend-nuxt/components/AdminBreadcrumbs.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend-nuxt/components/MapView.vue b/frontend-nuxt/components/MapView.vue index ba82907..d3944f6 100644 --- a/frontend-nuxt/components/MapView.vue +++ b/frontend-nuxt/components/MapView.vue @@ -1,5 +1,19 @@ @@ -189,6 +288,7 @@ definePageMeta({ middleware: 'admin' }) const api = useMapApi() +const toast = useToast() const users = ref([]) const maps = ref>([]) const settings = ref({ prefix: '', defaultHide: false, title: '' }) @@ -199,20 +299,53 @@ const merging = ref(false) const mergeFile = ref(null) const mergeFileRef = ref(null) const wipeModalRef = ref(null) +const rebuildModalRef = ref(null) +const loading = ref(true) +const userSearch = ref('') +const mapSearch = ref('') +const mapsSortBy = ref<'ID' | 'Name'>('ID') +const mapsSortDir = ref<'asc' | 'desc'>('asc') -const message = ref<{ type: 'success' | 'error'; text: string }>({ type: 'success', text: '' }) -let messageTimeout: ReturnType | null = null -function setMessage(type: 'success' | 'error', text: string) { - message.value = { type, text } - if (messageTimeout) clearTimeout(messageTimeout) - messageTimeout = setTimeout(() => { - message.value = { type: 'success', text: '' } - messageTimeout = null - }, 4000) +const filteredUsers = computed(() => { + const q = userSearch.value.trim().toLowerCase() + if (!q) return users.value + return users.value.filter((u) => u.toLowerCase().includes(q)) +}) + +const filteredMaps = computed(() => { + const q = mapSearch.value.trim().toLowerCase() + if (!q) return maps.value + return maps.value.filter( + (m) => + m.Name.toLowerCase().includes(q) || + String(m.ID).includes(q) + ) +}) + +const sortedFilteredMaps = computed(() => { + const list = [...filteredMaps.value] + const by = mapsSortBy.value + const dir = mapsSortDir.value + list.sort((a, b) => { + let cmp = 0 + if (by === 'ID') cmp = a.ID - b.ID + else cmp = a.Name.localeCompare(b.Name) + return dir === 'asc' ? cmp : -cmp + }) + return list +}) + +function setMapsSort(by: 'ID' | 'Name') { + if (mapsSortBy.value === by) mapsSortDir.value = mapsSortDir.value === 'asc' ? 'desc' : 'asc' + else { + mapsSortBy.value = by + mapsSortDir.value = 'asc' + } } onMounted(async () => { await Promise.all([loadUsers(), loadMaps(), loadSettings()]) + loading.value = false }) async function loadUsers() { @@ -244,21 +377,26 @@ async function saveSettings() { savingSettings.value = true try { await api.adminSettingsPost(settings.value) - setMessage('success', 'Settings saved.') + toast.success('Settings saved.') } catch (e) { - setMessage('error', (e as Error)?.message ?? 'Failed to save settings.') + toast.error((e as Error)?.message ?? 'Failed to save settings.') } finally { savingSettings.value = false } } -async function rebuildZooms() { +function confirmRebuildZooms() { + rebuildModalRef.value?.showModal() +} + +async function doRebuildZooms() { + rebuildModalRef.value?.close() rebuilding.value = true try { await api.adminRebuildZooms() - setMessage('success', 'Zooms rebuilt.') + toast.success('Zooms rebuilt.') } catch (e) { - setMessage('error', (e as Error)?.message ?? 'Failed to rebuild zooms.') + toast.error((e as Error)?.message ?? 'Failed to rebuild zooms.') } finally { rebuilding.value = false } @@ -274,9 +412,9 @@ async function doWipe() { await api.adminWipe() wipeModalRef.value?.close() await loadMaps() - setMessage('success', 'All data wiped.') + toast.success('All data wiped.') } catch (e) { - setMessage('error', (e as Error)?.message ?? 'Failed to wipe.') + toast.error((e as Error)?.message ?? 'Failed to wipe.') } finally { wiping.value = false } @@ -297,11 +435,12 @@ async function doMerge() { mergeFile.value = null if (mergeFileRef.value) mergeFileRef.value.value = '' await loadMaps() - setMessage('success', 'Merge completed.') + toast.success('Merge completed.') } catch (e) { - setMessage('error', (e as Error)?.message ?? 'Merge failed.') + toast.error((e as Error)?.message ?? 'Merge failed.') } finally { merging.value = false } } + diff --git a/frontend-nuxt/pages/admin/maps/[id].vue b/frontend-nuxt/pages/admin/maps/[id].vue index df17b2f..702d7a8 100644 --- a/frontend-nuxt/pages/admin/maps/[id].vue +++ b/frontend-nuxt/pages/admin/maps/[id].vue @@ -48,8 +48,10 @@ const mapsLoaded = ref(false) const form = ref({ name: '', hidden: false, priority: false }) const loading = ref(false) const error = ref('') +const adminMapName = useState('admin-breadcrumb-map-name', () => null) onMounted(async () => { + adminMapName.value = null try { const maps = await api.adminMaps() mapsLoaded.value = true @@ -57,6 +59,7 @@ onMounted(async () => { if (found) { map.value = found form.value = { name: found.Name, hidden: found.Hidden, priority: found.Priority } + adminMapName.value = found.Name } } catch { mapsLoaded.value = true @@ -64,6 +67,10 @@ onMounted(async () => { } }) +onUnmounted(() => { + adminMapName.value = null +}) + async function submit() { if (!map.value) return error.value = '' diff --git a/frontend-nuxt/pages/profile.vue b/frontend-nuxt/pages/profile.vue index e955502..eade2c6 100644 --- a/frontend-nuxt/pages/profile.vue +++ b/frontend-nuxt/pages/profile.vue @@ -2,31 +2,84 @@

Profile

+
-

- - Upload tokens -

-

Tokens for upload API. Generate and copy as needed.

-
    -
  • - {{ uploadTokenDisplay(t) }} -
+
+ + +
+
+ +
@@ -52,6 +105,9 @@