feat(ui): unify logistics-style side panel flow
This commit is contained in:
@@ -1,111 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<CatalogPage
|
||||
:items="mapPoints"
|
||||
:loading="isLoading"
|
||||
:use-server-clustering="false"
|
||||
map-id="orders-map"
|
||||
point-color="#6366f1"
|
||||
:hovered-id="hoveredOrderId"
|
||||
:show-panel="!selectedOrderId"
|
||||
panel-width="w-96"
|
||||
:hide-view-toggle="true"
|
||||
@select="onMapSelect"
|
||||
@update:hovered-id="hoveredOrderId = $event"
|
||||
>
|
||||
<template #panel>
|
||||
<!-- Panel header -->
|
||||
<div class="p-4 border-b border-base-300 flex-shrink-0">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-indigo-500/20 flex items-center justify-center">
|
||||
<Icon name="lucide:package" size="16" class="text-indigo-400" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold text-sm">{{ t('cabinetNav.orders') }}</span>
|
||||
<div class="text-xs text-base-content/50">{{ filteredItems.length }} {{ t('orders.total', 'total') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed inset-0">
|
||||
<ClientOnly>
|
||||
<CatalogMap
|
||||
map-id="orders-map"
|
||||
:items="mapPoints"
|
||||
:use-server-clustering="false"
|
||||
point-color="#6366f1"
|
||||
entity-type="offer"
|
||||
:hovered-item-id="hoveredOrderId"
|
||||
:fit-padding-left="460"
|
||||
@select-item="onMapSelect"
|
||||
/>
|
||||
</ClientOnly>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="relative mb-3">
|
||||
<MapSidePanel
|
||||
:title="t('cabinetNav.orders')"
|
||||
:initial-collapsed="false"
|
||||
width-class="w-[min(calc(100vw-1rem),440px)] md:w-[420px] xl:w-[460px]"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
:placeholder="t('common.search')"
|
||||
class="input input-sm w-full bg-base-200 border-base-300 text-base-content placeholder:text-base-content/50"
|
||||
/>
|
||||
<Icon name="lucide:search" size="16" class="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/50" />
|
||||
class="input input-sm w-full bg-white border-[#dccfbf] text-[#2f2418] placeholder:text-[#8a7761]"
|
||||
>
|
||||
<Icon name="lucide:search" size="16" class="absolute right-3 top-1/2 -translate-y-1/2 text-[#8a7761]" />
|
||||
</div>
|
||||
|
||||
<!-- Filter dropdown -->
|
||||
<div class="dropdown dropdown-end w-full">
|
||||
<label tabindex="0" class="btn btn-sm w-full bg-base-200 border-base-300 text-base-content hover:bg-base-200 justify-between">
|
||||
<label tabindex="0" class="btn btn-sm w-full bg-white border-[#dccfbf] text-[#2f2418] hover:bg-[#f9f4ed] justify-between">
|
||||
<span>{{ selectedFilterLabel }}</span>
|
||||
<Icon name="lucide:chevron-down" size="14" />
|
||||
</label>
|
||||
<ul tabindex="0" class="dropdown-content menu menu-sm z-50 p-2 shadow bg-base-200 rounded-box w-full mt-2">
|
||||
<ul tabindex="0" class="dropdown-content menu menu-sm z-50 p-2 shadow bg-white rounded-box w-full mt-2">
|
||||
<li v-for="filter in filters" :key="filter.id">
|
||||
<a
|
||||
:class="{ 'active': selectedFilter === filter.id }"
|
||||
@click="selectedFilter = filter.id"
|
||||
>{{ filter.label }}</a>
|
||||
<a :class="{ active: selectedFilter === filter.id }" @click="selectedFilter = filter.id">{{ filter.label }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Orders list -->
|
||||
<div class="flex-1 overflow-y-auto p-3 space-y-2">
|
||||
<div class="mt-4 flex flex-col gap-2">
|
||||
<template v-if="displayItems.length > 0">
|
||||
<div
|
||||
<button
|
||||
v-for="item in displayItems"
|
||||
:key="item.uuid"
|
||||
class="bg-base-200 rounded-lg p-3 hover:bg-base-200 transition-colors cursor-pointer"
|
||||
:class="{ 'ring-2 ring-indigo-500': selectedOrderId === item.uuid }"
|
||||
class="rounded-2xl border border-[#e4d9ca] bg-white p-3 text-left transition-colors hover:bg-[#f8f3ec]"
|
||||
:class="selectedOrderId === item.uuid ? 'ring-2 ring-[#2f2416]' : ''"
|
||||
@click="selectedOrderId = item.uuid"
|
||||
@mouseenter="hoveredOrderId = item.uuid"
|
||||
@mouseleave="hoveredOrderId = undefined"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-semibold text-sm">#{{ item.name }}</span>
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<span class="truncate font-semibold text-sm text-[#2f2418]">#{{ item.name }}</span>
|
||||
<span class="badge badge-sm" :class="getStatusBadgeClass(item.status)">
|
||||
{{ getStatusText(item.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-base-content/70 space-y-1">
|
||||
|
||||
<div class="space-y-1 text-xs text-[#6f6353]">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon name="lucide:map-pin" size="12" class="text-base-content/40" />
|
||||
<Icon name="lucide:map-pin" size="12" />
|
||||
<span class="truncate">{{ item.sourceLocationName }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon name="lucide:navigation" size="12" class="text-base-content/40" />
|
||||
<Icon name="lucide:navigation" size="12" />
|
||||
<span class="truncate">{{ item.destinationLocationName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-base-content/50 mt-2">
|
||||
{{ getOrderDate(item) }}
|
||||
</div>
|
||||
</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('orders.no_orders') }}</div>
|
||||
<div class="text-xs text-base-content/60">{{ t('orders.no_orders_desc') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-[#95836d]">{{ getOrderDate(item) }}</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<div v-else class="rounded-2xl border border-[#e4d9ca] bg-white px-4 py-8 text-center text-[#7c6d5a]">
|
||||
<div class="text-2xl">📦</div>
|
||||
<div class="mt-2 text-sm font-semibold">{{ t('orders.no_orders') }}</div>
|
||||
<div class="mt-1 text-xs">{{ t('orders.no_orders_desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="p-3 border-t border-base-300 flex-shrink-0">
|
||||
<span class="text-xs text-base-content/50">{{ displayItems.length }} {{ t('catalog.of') }} {{ filteredItems.length }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</CatalogPage>
|
||||
<template #footer>
|
||||
<span class="text-xs text-[#7c6d5a]">{{ displayItems.length }} {{ t('catalog.of') }} {{ filteredItems.length }}</span>
|
||||
</template>
|
||||
</MapSidePanel>
|
||||
|
||||
<!-- Order Detail Bottom Sheet -->
|
||||
<OrderDetailBottomSheet
|
||||
:is-open="!!selectedOrderId"
|
||||
:order-uuid="selectedOrderId"
|
||||
@@ -128,7 +109,6 @@ const { t } = useI18n()
|
||||
|
||||
const {
|
||||
filteredItems,
|
||||
isLoading,
|
||||
filters,
|
||||
selectedFilter,
|
||||
init,
|
||||
@@ -140,13 +120,11 @@ const hoveredOrderId = ref<string>()
|
||||
const searchQuery = ref('')
|
||||
const selectedOrderId = ref<string | null>(null)
|
||||
|
||||
// Selected filter label
|
||||
const selectedFilterLabel = computed(() => {
|
||||
const filter = filters.value.find(f => f.id === selectedFilter.value)
|
||||
return filter?.label || t('ordersList.filters.status')
|
||||
})
|
||||
|
||||
// Map points - source locations
|
||||
const mapPoints = computed(() => {
|
||||
return filteredItems.value
|
||||
.filter(order => order.uuid && order.sourceLatitude && order.sourceLongitude)
|
||||
@@ -158,7 +136,6 @@ const mapPoints = computed(() => {
|
||||
}))
|
||||
})
|
||||
|
||||
// Display items with search filter
|
||||
const displayItems = computed(() => {
|
||||
let items = filteredItems.value.filter(order => order.uuid)
|
||||
|
||||
@@ -174,9 +151,9 @@ const displayItems = computed(() => {
|
||||
return items
|
||||
})
|
||||
|
||||
const onMapSelect = (item: { uuid?: string | null }) => {
|
||||
if (item.uuid) {
|
||||
selectedOrderId.value = item.uuid
|
||||
const onMapSelect = (uuid: string) => {
|
||||
if (uuid) {
|
||||
selectedOrderId.value = uuid
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user