Add AddressDetailBottomSheet with same UX as orders
All checks were successful
Build Docker Image / build (push) Successful in 4m21s
All checks were successful
Build Docker Image / build (push) Successful in 4m21s
- Panel slides left when address is selected - Bottom sheet slides up with address details - Shows location, map preview, edit/delete actions
This commit is contained in:
@@ -1,55 +1,54 @@
|
||||
<template>
|
||||
<CatalogPage
|
||||
:items="mapPoints"
|
||||
:loading="isLoading"
|
||||
:use-server-clustering="false"
|
||||
map-id="addresses-map"
|
||||
point-color="#10b981"
|
||||
:hovered-id="hoveredAddressId"
|
||||
:show-panel="true"
|
||||
panel-width="w-96"
|
||||
:hide-view-toggle="true"
|
||||
@select="onMapSelect"
|
||||
@update:hovered-id="hoveredAddressId = $event"
|
||||
>
|
||||
<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>
|
||||
<CatalogPage
|
||||
:items="mapPoints"
|
||||
:loading="isLoading"
|
||||
:use-server-clustering="false"
|
||||
map-id="addresses-map"
|
||||
point-color="#10b981"
|
||||
:hovered-id="hoveredAddressId"
|
||||
:show-panel="!selectedAddressId"
|
||||
panel-width="w-96"
|
||||
:hide-view-toggle="true"
|
||||
@select="onMapSelect"
|
||||
@update:hovered-id="hoveredAddressId = $event"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- 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 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>
|
||||
</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 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>
|
||||
</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"
|
||||
>
|
||||
<!-- Addresses list -->
|
||||
<div class="flex-1 overflow-y-auto p-3 space-y-2">
|
||||
<template v-if="displayItems.length > 0">
|
||||
<div
|
||||
v-for="item in displayItems"
|
||||
:key="item.uuid"
|
||||
class="bg-white/10 rounded-lg p-3 hover:bg-white/20 transition-colors cursor-pointer"
|
||||
:class="{ 'ring-2 ring-emerald-500': selectedAddressId === item.uuid }"
|
||||
@click="selectedAddressId = item.uuid"
|
||||
@mouseenter="hoveredAddressId = item.uuid"
|
||||
@mouseleave="hoveredAddressId = undefined"
|
||||
>
|
||||
@@ -61,29 +60,37 @@
|
||||
</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>
|
||||
</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>
|
||||
</CatalogPage>
|
||||
<!-- 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>
|
||||
</CatalogPage>
|
||||
|
||||
<!-- Address Detail Bottom Sheet -->
|
||||
<AddressDetailBottomSheet
|
||||
:is-open="!!selectedAddressId"
|
||||
:address-uuid="selectedAddressId"
|
||||
@close="selectedAddressId = null"
|
||||
@deleted="selectedAddressId = null"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -104,6 +111,7 @@ const {
|
||||
|
||||
const hoveredAddressId = ref<string>()
|
||||
const searchQuery = ref('')
|
||||
const selectedAddressId = ref<string | null>(null)
|
||||
|
||||
// Map points
|
||||
const mapPoints = computed(() => {
|
||||
@@ -130,7 +138,7 @@ const displayItems = computed(() => {
|
||||
|
||||
const onMapSelect = (item: { uuid?: string | null }) => {
|
||||
if (item.uuid) {
|
||||
navigateTo(localePath(`/clientarea/addresses/${item.uuid}`))
|
||||
selectedAddressId.value = item.uuid
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user