Adopt logistics visual system across webapp
This commit is contained in:
166
app/pages/manager/quotations/index.vue
Normal file
166
app/pages/manager/quotations/index.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'manager',
|
||||
middleware: ['auth-oidc'],
|
||||
})
|
||||
|
||||
type QuotationCard = {
|
||||
id: string
|
||||
client: string
|
||||
from: string
|
||||
to: string
|
||||
mode: string
|
||||
weight: string
|
||||
status: 'draft' | 'pricing' | 'graph-ready'
|
||||
note: string
|
||||
}
|
||||
|
||||
const search = ref('')
|
||||
const quotations = useState<QuotationCard[]>('manager-quotation-cards', () => [
|
||||
{
|
||||
id: 'qt-001',
|
||||
client: 'Client 42A7',
|
||||
from: 'Guangzhou',
|
||||
to: 'Moscow',
|
||||
mode: 'Multimodal',
|
||||
weight: '800 kg',
|
||||
status: 'pricing',
|
||||
note: 'Базовый сценарий для переноса quotation flow из logistics.',
|
||||
},
|
||||
{
|
||||
id: 'qt-002',
|
||||
client: 'Client 90F2',
|
||||
from: 'Shenzhen',
|
||||
to: 'Novorossiysk',
|
||||
mode: 'Sea + truck',
|
||||
weight: '1 200 kg',
|
||||
status: 'graph-ready',
|
||||
note: 'Маршрут уже мыслится как graph-native corridor без Odoo-связки.',
|
||||
},
|
||||
{
|
||||
id: 'qt-003',
|
||||
client: 'Client 18D1',
|
||||
from: 'Yiwu',
|
||||
to: 'Kazan',
|
||||
mode: 'Rail',
|
||||
weight: '2 400 kg',
|
||||
status: 'draft',
|
||||
note: 'Черновик manager workspace под tariffs и decision rules.',
|
||||
},
|
||||
])
|
||||
|
||||
const filteredCards = computed(() => {
|
||||
const query = search.value.trim().toLowerCase()
|
||||
if (!query) return quotations.value
|
||||
|
||||
return quotations.value.filter((item) => {
|
||||
return [item.client, item.from, item.to, item.mode, item.note, item.status]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
.includes(query)
|
||||
})
|
||||
})
|
||||
|
||||
function createNewQuotation() {
|
||||
quotations.value = [
|
||||
{
|
||||
id: `qt-${String(quotations.value.length + 1).padStart(3, '0')}`,
|
||||
client: `Client ${(Math.random().toString(16).slice(2, 6)).toUpperCase()}`,
|
||||
from: 'Shanghai',
|
||||
to: 'Saint Petersburg',
|
||||
mode: 'Multimodal',
|
||||
weight: '950 kg',
|
||||
status: 'draft',
|
||||
note: 'Новый manager draft. Дальше его нужно привязать к реальному quotation microservice.',
|
||||
},
|
||||
...quotations.value,
|
||||
]
|
||||
}
|
||||
|
||||
function statusMeta(status: QuotationCard['status']) {
|
||||
if (status === 'graph-ready') return { label: 'Graph ready', className: 'bg-emerald-100 text-emerald-700' }
|
||||
if (status === 'pricing') return { label: 'Pricing', className: 'bg-amber-100 text-amber-700' }
|
||||
return { label: 'Draft', className: 'bg-stone-200 text-stone-700' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<section class="grid gap-3 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,0.7fr)]">
|
||||
<article class="rounded-[28px] bg-white p-6">
|
||||
<p class="text-xs font-bold uppercase tracking-[0.16em] text-[#8c7b67]">Quotations workspace</p>
|
||||
<h2 class="mt-2 text-3xl font-black text-[#2f2418]">Exact logistics shell, Optovia logic next</h2>
|
||||
<p class="mt-4 max-w-[760px] text-sm leading-6 text-[#5f4b33]">
|
||||
Этот экран уже переведён в визуальный паттерн `logistic`: те же теплые поверхности, те же rounded cards, тот же manager rhythm.
|
||||
Следующий шаг здесь очевидный: посадить quotation CRUD и tariff selection на ваши microservices и graph-модель.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="rounded-[28px] bg-[#2f2418] p-6 text-white shadow-[0_20px_44px_rgba(47,36,24,0.18)]">
|
||||
<p class="text-xs font-bold uppercase tracking-[0.16em] text-white/60">Migration note</p>
|
||||
<h3 class="mt-2 text-2xl font-black">No Odoo frontend path</h3>
|
||||
<p class="mt-4 text-sm leading-6 text-white/78">
|
||||
Quotation UI here is ready to stop depending on old Odoo-driven flows. Сейчас это staging surface, дальше сюда подключается новый graph-native backend contract.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="mt-6 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<label class="block flex-1">
|
||||
<span class="sr-only">Search quotations</span>
|
||||
<input
|
||||
v-model="search"
|
||||
class="input h-12 w-full rounded-full border-0 bg-white px-5 shadow-none"
|
||||
placeholder="Поиск по клиенту, маршруту или статусу"
|
||||
>
|
||||
</label>
|
||||
<button class="btn rounded-full border-0 bg-[#2f2418] px-6 text-white hover:bg-[#493824]" @click="createNewQuotation">
|
||||
Создать quotation draft
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="mt-6 space-y-3">
|
||||
<article
|
||||
v-for="item in filteredCards"
|
||||
:key="item.id"
|
||||
class="rounded-[28px] bg-white px-6 py-5 shadow-none transition-[transform,box-shadow] hover:-translate-y-0.5 hover:shadow-[0_18px_34px_rgba(62,47,26,0.12)]"
|
||||
>
|
||||
<div class="grid gap-3 md:grid-cols-[minmax(0,2fr)_auto_minmax(0,1.3fr)_auto] md:items-center">
|
||||
<div class="flex min-w-0 items-center gap-3 text-lg font-black leading-tight text-[#2f2418]">
|
||||
<p class="truncate">{{ item.from }}</p>
|
||||
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#efe7da] text-[#7a684f]">
|
||||
<svg viewBox="0 0 20 20" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M3.5 10h13" />
|
||||
<path d="M12 5.5 16.5 10 12 14.5" />
|
||||
</svg>
|
||||
</span>
|
||||
<p class="truncate">{{ item.to }}</p>
|
||||
</div>
|
||||
|
||||
<p class="whitespace-nowrap text-sm font-medium text-[#6f6353]">
|
||||
{{ item.weight }}
|
||||
</p>
|
||||
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-semibold text-[#2f2418]">{{ item.client }}</p>
|
||||
<p class="mt-1 truncate text-sm text-[#6f6353]">{{ item.mode }} · {{ item.note }}</p>
|
||||
</div>
|
||||
|
||||
<div class="md:text-right">
|
||||
<span class="inline-flex rounded-full px-3 py-1 text-xs font-semibold" :class="statusMeta(item.status).className">
|
||||
{{ statusMeta(item.status).label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article
|
||||
v-if="filteredCards.length === 0"
|
||||
class="rounded-[28px] bg-white p-8 text-center"
|
||||
>
|
||||
<p class="text-lg font-semibold text-[#2f2418]">Quotation cards не найдены</p>
|
||||
<p class="mt-2 text-sm text-[#6f6353]">Измени строку поиска или добавь новый draft.</p>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user