refactor(clientarea): use CatalogPage with #panel slot for orders/addresses
All checks were successful
Build Docker Image / build (push) Successful in 4m21s

- Add panelWidth and hideViewToggle props to CatalogPage
- Update orders page to use CatalogPage with narrow panel (w-50)
- Update addresses page to use CatalogPage with narrow panel (w-50)
- Remove unused ClientAreaMapPage component
This commit is contained in:
Ruslan Bakiev
2026-01-28 09:19:01 +07:00
parent 63e8d47b79
commit 984daa7a84
4 changed files with 205 additions and 425 deletions

View File

@@ -1,54 +1,89 @@
<template>
<ClientAreaMapPage
:items="items"
<CatalogPage
:items="mapPoints"
:loading="isLoading"
:use-server-clustering="false"
map-id="addresses-map"
point-color="#10b981"
:hovered-id="hoveredAddressId"
:total-count="items.length"
:title="t('cabinetNav.addresses')"
:search-query="searchQuery"
@select="onSelectAddress"
:show-panel="true"
panel-width="w-50"
:hide-view-toggle="true"
@select="onMapSelect"
@update:hovered-id="hoveredAddressId = $event"
@update:search-query="searchQuery = $event"
>
<template #header>
<NuxtLink :to="localePath('/clientarea/addresses/new')">
<button class="btn btn-sm w-full bg-white/10 border-white/20 text-white hover:bg-white/20">
<Icon name="lucide:plus" size="14" class="mr-1" />
{{ t('profileAddresses.actions.add') }}
</button>
</NuxtLink>
</template>
<template #card="{ item }">
<NuxtLink :to="localePath(`/clientarea/addresses/${item.uuid}`)" class="block">
<div class="bg-white/10 rounded-lg p-4 hover:bg-white/20 transition-colors">
<div class="flex items-start gap-3">
<span class="text-2xl">{{ isoToEmoji(item.countryCode) }}</span>
<div class="flex-1 min-w-0">
<div class="font-semibold truncate">{{ item.name }}</div>
<div class="text-sm text-white/60 line-clamp-2">{{ item.address }}</div>
</div>
</div>
<template #panel>
<!-- Panel header -->
<div class="p-4 border-b border-white/10 flex-shrink-0">
<div class="flex items-center justify-between mb-3">
<span class="font-semibold">{{ t('cabinetNav.addresses') }}</span>
</div>
</NuxtLink>
</template>
<template #empty>
<div class="text-center py-8">
<div class="text-4xl mb-2">📍</div>
<div class="font-semibold mb-1">{{ t('profileAddresses.empty.title') }}</div>
<div class="text-sm text-white/60 mb-4">{{ t('profileAddresses.empty.description') }}</div>
<!-- Search -->
<div class="relative mb-3">
<input
v-model="searchQuery"
type="text"
:placeholder="t('common.search')"
class="input input-sm w-full bg-white/10 border-white/20 text-white placeholder:text-white/50"
/>
<Icon name="lucide:search" size="16" class="absolute right-3 top-1/2 -translate-y-1/2 text-white/50" />
</div>
<!-- Add button -->
<NuxtLink :to="localePath('/clientarea/addresses/new')">
<button class="btn btn-sm bg-white/10 border-white/20 text-white hover:bg-white/20">
<button class="btn btn-sm w-full bg-white/10 border-white/20 text-white hover:bg-white/20">
<Icon name="lucide:plus" size="14" class="mr-1" />
{{ t('profileAddresses.empty.cta') }}
{{ t('profileAddresses.actions.add') }}
</button>
</NuxtLink>
</div>
<!-- Addresses list -->
<div class="flex-1 overflow-y-auto p-3 space-y-2">
<template v-if="displayItems.length > 0">
<NuxtLink
v-for="item in displayItems"
:key="item.uuid"
:to="localePath(`/clientarea/addresses/${item.uuid}`)"
class="block"
>
<div
class="bg-white/10 rounded-lg p-3 hover:bg-white/20 transition-colors cursor-pointer"
@mouseenter="hoveredAddressId = item.uuid"
@mouseleave="hoveredAddressId = undefined"
>
<div class="flex items-start gap-2">
<span class="text-xl">{{ isoToEmoji(item.countryCode) }}</span>
<div class="flex-1 min-w-0">
<div class="font-semibold text-sm truncate">{{ item.name }}</div>
<div class="text-xs text-white/60 line-clamp-2">{{ item.address }}</div>
</div>
</div>
</div>
</NuxtLink>
</template>
<template v-else>
<div class="text-center py-8">
<div class="text-3xl mb-2">📍</div>
<div class="font-semibold text-sm mb-1">{{ t('profileAddresses.empty.title') }}</div>
<div class="text-xs text-white/60 mb-3">{{ t('profileAddresses.empty.description') }}</div>
<NuxtLink :to="localePath('/clientarea/addresses/new')">
<button class="btn btn-sm bg-white/10 border-white/20 text-white hover:bg-white/20">
<Icon name="lucide:plus" size="14" class="mr-1" />
{{ t('profileAddresses.empty.cta') }}
</button>
</NuxtLink>
</div>
</template>
</div>
<!-- Footer -->
<div class="p-3 border-t border-white/10 flex-shrink-0">
<span class="text-xs text-white/50">{{ displayItems.length }} {{ t('catalog.of') }} {{ items.length }}</span>
</div>
</template>
</ClientAreaMapPage>
</CatalogPage>
</template>
<script setup lang="ts">
@@ -70,7 +105,30 @@ const {
const hoveredAddressId = ref<string>()
const searchQuery = ref('')
const onSelectAddress = (item: { uuid?: string | null }) => {
// Map points
const mapPoints = computed(() => {
return items.value
.filter(addr => addr.uuid && addr.latitude && addr.longitude)
.map(addr => ({
uuid: addr.uuid!,
name: addr.name || '',
latitude: Number(addr.latitude),
longitude: Number(addr.longitude)
}))
})
// Display items with search filter
const displayItems = computed(() => {
if (!searchQuery.value) return items.value
const query = searchQuery.value.toLowerCase()
return items.value.filter(item =>
item.name?.toLowerCase().includes(query) ||
item.address?.toLowerCase().includes(query)
)
})
const onMapSelect = (item: { uuid?: string | null }) => {
if (item.uuid) {
navigateTo(localePath(`/clientarea/addresses/${item.uuid}`))
}