Files
2026-04-11 08:31:34 +07:00

167 lines
6.3 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>