All checks were successful
Build Docker Image / build (push) Successful in 4m3s
- Add strictScalars: true to codegen.ts with proper scalar mappings (Date, Decimal, JSONString, JSON, UUID, BigInt → string/Record) - Replace all ref<any[]> with proper GraphQL-derived types - Add type guards for null filtering in arrays - Fix bugs exposed by typing (locationLatitude vs latitude, etc.) - Add interfaces for external components (MapboxSearchBox) This enables end-to-end type safety from GraphQL schema to frontend.
156 lines
4.5 KiB
Vue
156 lines
4.5 KiB
Vue
<template>
|
|
<aside class="w-64 bg-base-100 h-screen flex flex-col border-r border-base-300">
|
|
<!-- Header with back button -->
|
|
<div class="p-4 border-b border-base-300">
|
|
<NuxtLink
|
|
:to="localePath('/catalog')"
|
|
class="btn btn-sm btn-ghost gap-2"
|
|
>
|
|
<Icon name="lucide:arrow-left" size="18" />
|
|
{{ t('catalogMap.actions.list_view') }}
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div role="tablist" class="tabs tabs-bordered px-4 border-b border-base-300">
|
|
<button
|
|
role="tab"
|
|
class="tab"
|
|
:class="{ 'tab-active': activeTab === 'hubs' }"
|
|
@click="$emit('update:activeTab', 'hubs')"
|
|
>
|
|
{{ t('catalogMap.tabs.hubs') }}
|
|
<span class="badge badge-sm ml-1">{{ hubs.length }}</span>
|
|
</button>
|
|
<button
|
|
role="tab"
|
|
class="tab"
|
|
:class="{ 'tab-active': activeTab === 'suppliers' }"
|
|
@click="$emit('update:activeTab', 'suppliers')"
|
|
>
|
|
{{ t('catalogMap.tabs.suppliers') }}
|
|
<span class="badge badge-sm ml-1">{{ suppliers.length }}</span>
|
|
</button>
|
|
<button
|
|
role="tab"
|
|
class="tab"
|
|
:class="{ 'tab-active': activeTab === 'offers' }"
|
|
@click="$emit('update:activeTab', 'offers')"
|
|
>
|
|
{{ t('catalogMap.tabs.offers') }}
|
|
<span class="badge badge-sm ml-1">{{ offers.length }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="flex-1 overflow-y-auto p-2 bg-base-200">
|
|
<!-- Loading -->
|
|
<div v-if="isLoading" class="flex items-center justify-center h-32">
|
|
<span class="loading loading-spinner loading-md"></span>
|
|
</div>
|
|
|
|
<!-- Hubs Tab -->
|
|
<div v-else-if="activeTab === 'hubs'" class="space-y-2">
|
|
<HubCard
|
|
v-for="(hub, index) in hubs"
|
|
:key="hub.uuid ?? index"
|
|
:hub="hub"
|
|
selectable
|
|
:is-selected="selectedItemId === hub.uuid"
|
|
@select="$emit('select', hub, 'hub')"
|
|
/>
|
|
<div v-if="hubs.length === 0" class="text-center text-base-content/50 py-8">
|
|
{{ t('catalogMap.empty.hubs') }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Suppliers Tab -->
|
|
<div v-else-if="activeTab === 'suppliers'" class="space-y-2">
|
|
<SupplierCard
|
|
v-for="(supplier, index) in suppliers"
|
|
:key="supplier.uuid ?? index"
|
|
:supplier="supplier"
|
|
selectable
|
|
:is-selected="selectedItemId === supplier.uuid"
|
|
@select="$emit('select', supplier, 'supplier')"
|
|
/>
|
|
<div v-if="suppliers.length === 0" class="text-center text-base-content/50 py-8">
|
|
{{ t('catalogMap.empty.suppliers') }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Offers Tab -->
|
|
<div v-else-if="activeTab === 'offers'" class="space-y-2">
|
|
<OfferCard
|
|
v-for="(offer, index) in offers"
|
|
:key="offer.uuid ?? index"
|
|
:offer="offer"
|
|
selectable
|
|
:is-selected="selectedItemId === offer.uuid"
|
|
@select="$emit('select', offer, 'offer')"
|
|
/>
|
|
<div v-if="offers.length === 0" class="text-center text-base-content/50 py-8">
|
|
{{ t('catalogMap.empty.offers') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Hub {
|
|
uuid?: string | null
|
|
name?: string | null
|
|
country?: string | null
|
|
countryCode?: string | null
|
|
distance?: string
|
|
transportTypes?: (string | null)[] | null
|
|
}
|
|
|
|
interface Supplier {
|
|
uuid?: string | null
|
|
teamUuid?: string | null
|
|
name?: string | null
|
|
country?: string | null
|
|
countryCode?: string | null
|
|
logo?: string | null
|
|
onTimeRate?: number | null
|
|
offersCount?: number | null
|
|
isVerified?: boolean | null
|
|
}
|
|
|
|
interface Offer {
|
|
uuid?: string | null
|
|
productUuid?: string | null
|
|
productName?: string | null
|
|
categoryName?: string | null
|
|
locationUuid?: string | null
|
|
locationName?: string | null
|
|
locationCountry?: string | null
|
|
locationCountryCode?: string | null
|
|
quantity?: number | string | null
|
|
unit?: string | null
|
|
pricePerUnit?: number | string | null
|
|
currency?: string | null
|
|
status?: string | null
|
|
validUntil?: string | null
|
|
}
|
|
|
|
defineProps<{
|
|
activeTab: 'hubs' | 'suppliers' | 'offers'
|
|
hubs: Hub[]
|
|
suppliers: Supplier[]
|
|
offers: Offer[]
|
|
selectedItemId: string | null
|
|
isLoading: boolean
|
|
}>()
|
|
|
|
defineEmits<{
|
|
'update:activeTab': [tab: 'hubs' | 'suppliers' | 'offers']
|
|
'select': [item: Hub | Supplier | Offer, type: 'hub' | 'supplier' | 'offer']
|
|
}>()
|
|
|
|
const localePath = useLocalePath()
|
|
const { t } = useI18n()
|
|
</script>
|