fix(marketplace): коллективный заказ — единая карточка партии на всех стадиях
Переделал страницу: все партии (на сборе и уже принятые) рендерятся ОДНИМ шаблоном — шапка (товар + КУ + бейдж этапа), прогресс-бар сбора, свои заказы строками. Убраны разные по вёрстке виды (метрик-сетка + большие OrderCard для принятых), убран сырой UUID партии. Отличие стадий — только заполнение бара и подпись этапа, а не разные сущности. Бэкенд: group_accumulated_quantity/group_min_volume теперь заполняются на любой стадии — для принятых партий коллективный объём берётся суммой по cycle_id (новый агрегат sumByCycleIds), для собирающихся — суммой активного пула пары оферта×КУ. Так прогресс-бар осмыслен на каждой карточке. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+35
-15
@@ -79,15 +79,23 @@ export class MarketplaceOrderDisplayService {
|
||||
const accounts = opts?.withParticipantNames
|
||||
? orders.flatMap((o) => [o.orderer_account, o.supplier_account])
|
||||
: [];
|
||||
const [offers, branchByBraname, nameByAccount, groupSums] = await Promise.all([
|
||||
// Идентификаторы сформированных партий — для коллективного объёма принятых
|
||||
// партий (тот же прогресс, что и у накопителя; единый вид карточки).
|
||||
const cycleIds = opts?.withGroupProgress
|
||||
? ([...new Set(orders.map((o) => o.cycle_id).filter((c): c is string => !!c))])
|
||||
: [];
|
||||
const [offers, branchByBraname, nameByAccount, groupSums, cycleSums] = await Promise.all([
|
||||
this.offerRepo.findByIds(offerIds),
|
||||
this.resolveBranches(branames),
|
||||
this.resolveAccountNames(accounts),
|
||||
// «Сколько накоплено» по парам (offer × КУ) считаем только когда лента
|
||||
// должна показать прогресс сбора (стол заказчика) — иначе лишний запрос.
|
||||
// «Сколько накоплено» по парам (offer × КУ) и по партиям считаем только
|
||||
// когда лента должна показать прогресс сбора (стол заказчика).
|
||||
opts?.withGroupProgress
|
||||
? this.orderRepo.sumActiveByOfferBranch(config.coopname, offerIds)
|
||||
: Promise.resolve([]),
|
||||
cycleIds.length
|
||||
? this.orderRepo.sumByCycleIds(config.coopname, cycleIds)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
const offerById = new Map(offers.map((offer) => [offer.id, offer]));
|
||||
// Ключ (offer_id::braname) → накоплено всеми на этапе сбора.
|
||||
@@ -95,18 +103,32 @@ export class MarketplaceOrderDisplayService {
|
||||
for (const g of groupSums) {
|
||||
accumulatedByKey.set(`${g.offer_id}::${g.delivery_braname}`, g.total);
|
||||
}
|
||||
// cycle_id → коллективный объём уже сформированной партии.
|
||||
const cycleTotalById = new Map<string, number>();
|
||||
for (const c of cycleSums) cycleTotalById.set(c.cycle_id, c.total);
|
||||
|
||||
for (const order of orders) {
|
||||
const offer = offerById.get(order.offer_id);
|
||||
const branch = branchByBraname.get(order.delivery_braname);
|
||||
// Прогресс сбора — только для заказов на этапе накопления (ACTIVE). После
|
||||
// приёма поставщиком сбор завершён, прогресс не показываем (пусто).
|
||||
const isCollecting =
|
||||
!!opts?.withGroupProgress && order.status === MarketplaceOrderStatuses.ACTIVE;
|
||||
const groupKey = `${order.offer_id}::${order.delivery_braname}`;
|
||||
const minVolume =
|
||||
offer?.delivery_points?.find((d) => d.braname === order.delivery_braname)
|
||||
?.min_supply_volume ?? null;
|
||||
// Целевой минимум КУ — свойство пары (offer × КУ), известен на любой
|
||||
// стадии. Накоплено: на этапе сбора (ACTIVE) — сумма активного пула пары,
|
||||
// иначе — коллективный объём партии (cycle_id), в которую заказ вошёл.
|
||||
const minVolume = opts?.withGroupProgress
|
||||
? (offer?.delivery_points?.find((d) => d.braname === order.delivery_braname)
|
||||
?.min_supply_volume ?? null)
|
||||
: null;
|
||||
let accumulated: number | null = null;
|
||||
if (opts?.withGroupProgress) {
|
||||
if (order.status === MarketplaceOrderStatuses.ACTIVE) {
|
||||
accumulated =
|
||||
accumulatedByKey.get(`${order.offer_id}::${order.delivery_braname}`) ??
|
||||
order.quantity;
|
||||
} else if (order.cycle_id) {
|
||||
accumulated = cycleTotalById.get(order.cycle_id) ?? order.quantity;
|
||||
} else {
|
||||
accumulated = order.quantity;
|
||||
}
|
||||
}
|
||||
result.set(order.id, {
|
||||
product_name: offer?.product_name ?? null,
|
||||
unit_of_measure: offer?.unit_of_measure ?? null,
|
||||
@@ -114,10 +136,8 @@ export class MarketplaceOrderDisplayService {
|
||||
delivery_point_address: branch?.address ?? null,
|
||||
orderer_name: nameByAccount.get(order.orderer_account) ?? null,
|
||||
supplier_name: nameByAccount.get(order.supplier_account) ?? null,
|
||||
group_accumulated_quantity: isCollecting
|
||||
? (accumulatedByKey.get(groupKey) ?? order.quantity)
|
||||
: null,
|
||||
group_min_volume: isCollecting ? minVolume : null,
|
||||
group_accumulated_quantity: accumulated,
|
||||
group_min_volume: minVolume,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
+11
@@ -137,6 +137,17 @@ export interface MarketplaceOrderDomainRepository
|
||||
offerIds: string[]
|
||||
): Promise<Array<{ offer_id: string; delivery_braname: string; total: number }>>;
|
||||
|
||||
/**
|
||||
* Сумма quantity по сформированным партиям (cycle_id) — всех пайщиков. Нужна,
|
||||
* чтобы заказчик видел коллективный объём уже принятой партии тем же
|
||||
* прогрессом, что и у партии на этапе сбора (единый вид карточки на ленте
|
||||
* коллективного заказа). Результат — строки (cycle_id, total).
|
||||
*/
|
||||
sumByCycleIds(
|
||||
coopname: string,
|
||||
cycleIds: string[]
|
||||
): Promise<Array<{ cycle_id: string; total: number }>>;
|
||||
|
||||
/**
|
||||
* Story 4.3: все Order'ы, привязанные к заявке `cycle_id`. Используется
|
||||
* cron'ом expireUnacceptedPending для per-Order unblk пулу заявки,
|
||||
|
||||
+18
@@ -320,6 +320,24 @@ export class MarketplaceOrderRepositoryAdapter implements MarketplaceOrderDomain
|
||||
}));
|
||||
}
|
||||
|
||||
async sumByCycleIds(
|
||||
coopname: string,
|
||||
cycleIds: string[]
|
||||
): Promise<Array<{ cycle_id: string; total: number }>> {
|
||||
if (cycleIds.length === 0) return [];
|
||||
const rows = await this.repo
|
||||
.createQueryBuilder('o')
|
||||
.select('o.cycle_id', 'cycle_id')
|
||||
.addSelect('COALESCE(SUM(o.quantity), 0)', 'total')
|
||||
.where('o.coopname = :coop AND o.cycle_id IN (:...cids)', {
|
||||
coop: coopname,
|
||||
cids: cycleIds,
|
||||
})
|
||||
.groupBy('o.cycle_id')
|
||||
.getRawMany<{ cycle_id: string; total: string }>();
|
||||
return rows.map((r) => ({ cycle_id: r.cycle_id, total: Number(r.total ?? 0) }));
|
||||
}
|
||||
|
||||
async deleteByBlockNumGreaterThan(blockNum: number): Promise<void> {
|
||||
await this.repo
|
||||
.createQueryBuilder()
|
||||
|
||||
+78
-134
@@ -1,13 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { FailAlert } from 'src/shared/api';
|
||||
import {
|
||||
OrderCard,
|
||||
orderStatusDisplay,
|
||||
toOrderCardModel,
|
||||
} from 'src/widgets/Marketplace/OrderCard';
|
||||
import { orderStatusDisplay } from 'src/widgets/Marketplace/OrderCard';
|
||||
import { RefreshButton } from 'src/widgets/Marketplace/RefreshButton';
|
||||
import { EmptyState } from 'src/shared/ui/base';
|
||||
import { BaseBadge, EmptyState } from 'src/shared/ui/base';
|
||||
import { PageHint } from 'src/shared/ui/domain';
|
||||
import { marketplaceUnitShort } from 'src/shared/lib/consts/marketplace-units';
|
||||
import { fetchMyOrders } from '../../MyOrders/api';
|
||||
@@ -19,19 +15,17 @@ import type {
|
||||
/**
|
||||
* Эпик 4 / Story 4.4: orderer-стол «Коллективный заказ».
|
||||
*
|
||||
* Зеркало стола поставщика «Входящие заказы», но глазами заказчика и без ФИО
|
||||
* других пайщиков. Заказы заказчика сгруппированы в ПАРТИИ:
|
||||
* - «накопитель» — собственные активные (ACTIVE) заказы по паре (оферта × КУ)
|
||||
* копятся к минимальному объёму поставки. Заказчик видит КОЛЛЕКТИВНЫЙ
|
||||
* прогресс сбора (`group_accumulated_quantity` — накоплено всеми пайщиками,
|
||||
* `group_min_volume` — целевой минимум КУ) и свой вклад. Это главное, что
|
||||
* хочет видеть заказчик: насколько собралась его коллективная закупка.
|
||||
* - «сформированная» — уже принятые поставщиком заказы (по cycle_id): метрики
|
||||
* этапа + карточки заказов.
|
||||
* Страница — способ отслеживания процесса сбора коллективных заказов, в которых
|
||||
* участвует пайщик (сами его заказы живут на «Моих заказах»). Заказы
|
||||
* сгруппированы в ПАРТИИ по паре (оферта × КУ): активные копятся к минимальному
|
||||
* объёму, принятые поставщиком — уже сформированы. Все партии рендерятся ОДНОЙ
|
||||
* И ТОЙ ЖЕ карточкой: шапка (товар + КУ + бейдж этапа), прогресс-бар сбора,
|
||||
* свои заказы строками. Отличается только заполнение бара и подпись этапа —
|
||||
* никаких разных по вёрстке сущностей.
|
||||
*
|
||||
* Источник данных — `Queries.Marketplace.ListMyOrders` (reuse MyOrders/api),
|
||||
* который видит ТОЛЬКО заказы текущего пайщика. Коллективные суммы (`group_*`)
|
||||
* приходят с бэкенда уже агрегированными по всем участникам. Polling 15s.
|
||||
* Источник данных — `Queries.Marketplace.ListMyOrders` (видит только заказы
|
||||
* пайщика). Коллективные суммы (`group_accumulated_quantity` — накоплено всеми,
|
||||
* `group_min_volume` — целевой минимум КУ) приходят с бэкенда. Polling 15s.
|
||||
*/
|
||||
|
||||
const POLL_INTERVAL_MS = 15_000;
|
||||
@@ -41,7 +35,7 @@ const items = ref<MarketplaceOrderView[]>([]);
|
||||
const loading = ref(false);
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// Этап сформированной партии = минимальный по рангу среди не-отменённых.
|
||||
// Этап партии = минимальный по рангу среди не-отменённых.
|
||||
const STAGE_RANK: Record<MarketplaceOrderStatusView, number> = {
|
||||
ACTIVE: 0,
|
||||
ACCEPTED_PENDING_SUPPLIER: 1,
|
||||
@@ -56,28 +50,27 @@ const STAGE_RANK: Record<MarketplaceOrderStatusView, number> = {
|
||||
CANCELLED_BY_SUPPLIER: 99,
|
||||
};
|
||||
|
||||
interface OrdererParty {
|
||||
interface CollectiveParty {
|
||||
key: string;
|
||||
/** collecting — копится к min (ACTIVE); formed — уже принята (cycle_id). */
|
||||
kind: 'collecting' | 'formed';
|
||||
cycle_id: string | null;
|
||||
/** collecting — копится (ACTIVE); formed — уже принята поставщиком. */
|
||||
collecting: boolean;
|
||||
offer_id: string;
|
||||
productName: string;
|
||||
pvzName: string;
|
||||
unitLabel: string;
|
||||
orders: MarketplaceOrderView[];
|
||||
/** Свой вклад (сумма собственных заказов в этой группе). */
|
||||
/** Свой вклад (сумма собственных заказов в этой партии). */
|
||||
ownUnits: number;
|
||||
ownCost: number;
|
||||
/** Накоплено всеми пайщиками на этапе сбора (с бэкенда). */
|
||||
/** Накоплено всеми пайщиками (с бэкенда): пул сбора или объём партии. */
|
||||
groupAccumulated: number | null;
|
||||
/** Целевой минимальный объём поставки на этот КУ. */
|
||||
groupMinVolume: number | null;
|
||||
stageStatus: MarketplaceOrderStatusView;
|
||||
}
|
||||
|
||||
const parties = computed<OrdererParty[]>(() => {
|
||||
const buckets = new Map<string, OrdererParty>();
|
||||
const parties = computed<CollectiveParty[]>(() => {
|
||||
const buckets = new Map<string, CollectiveParty>();
|
||||
for (const o of items.value) {
|
||||
const collecting = o.status === 'ACTIVE';
|
||||
const key = collecting
|
||||
@@ -89,8 +82,7 @@ const parties = computed<OrdererParty[]>(() => {
|
||||
if (!p) {
|
||||
p = {
|
||||
key,
|
||||
kind: collecting ? 'collecting' : 'formed',
|
||||
cycle_id: collecting ? null : (o.cycle_id ?? null),
|
||||
collecting,
|
||||
offer_id: o.offer_id,
|
||||
productName: o.product_name || 'Товар по предложению',
|
||||
pvzName: o.delivery_point_name || o.delivery_braname,
|
||||
@@ -98,8 +90,8 @@ const parties = computed<OrdererParty[]>(() => {
|
||||
orders: [],
|
||||
ownUnits: 0,
|
||||
ownCost: 0,
|
||||
groupAccumulated: collecting ? (o.group_accumulated_quantity ?? null) : null,
|
||||
groupMinVolume: collecting ? (o.group_min_volume ?? null) : null,
|
||||
groupAccumulated: o.group_accumulated_quantity ?? null,
|
||||
groupMinVolume: o.group_min_volume ?? null,
|
||||
stageStatus: o.status,
|
||||
};
|
||||
buckets.set(key, p);
|
||||
@@ -114,31 +106,36 @@ const parties = computed<OrdererParty[]>(() => {
|
||||
}
|
||||
}
|
||||
return [...buckets.values()].sort((a, b) => {
|
||||
// Накопители выше — по ним идёт сбор, заказчику важнее прогресс.
|
||||
if (a.kind !== b.kind) return a.kind === 'collecting' ? -1 : 1;
|
||||
// Собирающиеся партии выше — по ним идёт активный сбор.
|
||||
if (a.collecting !== b.collecting) return a.collecting ? -1 : 1;
|
||||
return a.orders[0].created_at < b.orders[0].created_at ? 1 : -1;
|
||||
});
|
||||
});
|
||||
|
||||
const hasParties = computed(() => parties.value.length > 0);
|
||||
const collectingCount = computed(
|
||||
() => parties.value.filter((p) => p.kind === 'collecting').length,
|
||||
);
|
||||
|
||||
// Поштучный сбор: min ≤ 1 (или не задан) — прогресс-бар не нужен.
|
||||
function isPerPiece(p: OrdererParty): boolean {
|
||||
return p.groupMinVolume == null || p.groupMinVolume <= 1;
|
||||
// Есть ли целевой объём сбора (иначе — поштучный приём, бар показываем полным).
|
||||
function hasTarget(p: CollectiveParty): boolean {
|
||||
return p.groupMinVolume != null && p.groupMinVolume > 1;
|
||||
}
|
||||
|
||||
function progressRatio(p: OrdererParty): number {
|
||||
if (!p.groupMinVolume || p.groupMinVolume <= 0) return 1;
|
||||
return Math.min(1, (p.groupAccumulated ?? 0) / p.groupMinVolume);
|
||||
function accumulated(p: CollectiveParty): number {
|
||||
return p.groupAccumulated ?? p.ownUnits;
|
||||
}
|
||||
|
||||
function reachedMin(p: OrdererParty): boolean {
|
||||
return (
|
||||
p.groupMinVolume != null && (p.groupAccumulated ?? 0) >= p.groupMinVolume
|
||||
);
|
||||
function reachedMin(p: CollectiveParty): boolean {
|
||||
return hasTarget(p) ? accumulated(p) >= (p.groupMinVolume as number) : true;
|
||||
}
|
||||
|
||||
function progressRatio(p: CollectiveParty): number {
|
||||
if (!hasTarget(p)) return 1;
|
||||
return Math.min(1, accumulated(p) / (p.groupMinVolume as number));
|
||||
}
|
||||
|
||||
function barColor(p: CollectiveParty): string {
|
||||
// Идёт сбор и минимум ещё не набран — основной цвет; иначе (набрано или
|
||||
// партия уже принята) — успех.
|
||||
return p.collecting && !reachedMin(p) ? 'primary' : 'positive';
|
||||
}
|
||||
|
||||
function formatCost(value: number): string {
|
||||
@@ -189,10 +186,9 @@ q-page.collective(role="region", aria-label="Коллективный заказ
|
||||
|
||||
.collective__col
|
||||
PageHint(storage-key="mp:collective:banner-dismissed")
|
||||
| Ваши заказы сгруппированы в партии по предложению и кооперативному
|
||||
| участку. Пока партия копится — видно, сколько уже набрано всеми
|
||||
| пайщиками к минимальному объёму поставки. После приёма поставщиком
|
||||
| партия переходит в работу.
|
||||
| Отслеживание сбора коллективных заказов, в которых вы участвуете. Партия
|
||||
| копится по предложению и кооперативному участку до минимального объёма
|
||||
| поставки; на любой стадии видно, сколько уже набрано всеми пайщиками.
|
||||
|
||||
q-inner-loading(:showing="loading && items.length === 0")
|
||||
q-spinner(color="primary", size="2em")
|
||||
@@ -206,13 +202,9 @@ q-page.collective(role="region", aria-label="Коллективный заказ
|
||||
q-icon(name="inventory_2", size="48px")
|
||||
|
||||
.collective__list(v-if="hasParties")
|
||||
.t-muted.collective__counter Партий в сборе: {{ collectingCount }} / Всего партий: {{ parties.length }}
|
||||
|
||||
.collective__party(
|
||||
v-for="p in parties",
|
||||
:key="p.key",
|
||||
:class="`collective__party--${p.kind}`"
|
||||
)
|
||||
//- ЕДИНАЯ карточка для любой партии (сбор/принята) — отличается только
|
||||
//- заполнением прогресса и бейджем этапа, не вёрсткой.
|
||||
.collective__party(v-for="p in parties", :key="p.key")
|
||||
.collective__party-head
|
||||
.row.items-center.q-gutter-sm.no-wrap
|
||||
div.col
|
||||
@@ -220,72 +212,41 @@ q-page.collective(role="region", aria-label="Коллективный заказ
|
||||
.t-muted.collective__party-sub
|
||||
q-icon(name="place", size="14px")
|
||||
| КУ «{{ p.pvzName }}»
|
||||
BaseBadge(:variant="orderStatusDisplay(p.stageStatus).variant") {{ orderStatusDisplay(p.stageStatus).label }}
|
||||
span.chip.chip--accent
|
||||
q-icon(name="layers", size="14px")
|
||||
| {{ p.orders.length }} зак.
|
||||
|
||||
//- НАКОПИТЕЛЬ: коллективный прогресс сбора к минимальному объёму.
|
||||
template(v-if="p.kind === 'collecting'")
|
||||
.collective__progress(v-if="!isPerPiece(p)")
|
||||
.collective__progress-row
|
||||
span Накоплено всеми: {{ p.groupAccumulated ?? 0 }} {{ p.unitLabel }}
|
||||
span.t-muted цель — от {{ p.groupMinVolume }} {{ p.unitLabel }}
|
||||
q-linear-progress.collective__progress-bar(
|
||||
:value="progressRatio(p)",
|
||||
rounded,
|
||||
size="10px",
|
||||
:color="reachedMin(p) ? 'positive' : 'primary'",
|
||||
track-color="grey-3"
|
||||
)
|
||||
.t-muted.collective__progress-hint(v-if="reachedMin(p)")
|
||||
q-icon(name="check_circle", size="14px", color="positive")
|
||||
| Минимальный объём набран — поставщик может принять партию.
|
||||
.t-muted.collective__progress-hint(v-else)
|
||||
| Сбор продолжается. Поставщик может принять партию и раньше — минимум лишь ориентир.
|
||||
.collective__progress
|
||||
.collective__progress-row
|
||||
span Накоплено всеми: {{ accumulated(p) }} {{ p.unitLabel }}
|
||||
span.t-muted(v-if="hasTarget(p)") цель — от {{ p.groupMinVolume }} {{ p.unitLabel }}
|
||||
q-linear-progress.collective__progress-bar(
|
||||
:value="progressRatio(p)",
|
||||
rounded,
|
||||
size="10px",
|
||||
:color="barColor(p)",
|
||||
track-color="grey-3"
|
||||
)
|
||||
.t-muted.collective__progress-hint(v-if="p.collecting && reachedMin(p)")
|
||||
q-icon(name="check_circle", size="14px", color="positive")
|
||||
| Минимальный объём набран — поставщик может принять партию.
|
||||
.t-muted.collective__progress-hint(v-else-if="p.collecting")
|
||||
| Сбор продолжается. Поставщик может принять партию и раньше — минимум лишь ориентир.
|
||||
.t-muted.collective__progress-hint(v-else)
|
||||
q-icon(name="local_shipping", size="14px")
|
||||
| Партия набрана. Этап: {{ orderStatusDisplay(p.stageStatus).label }}.
|
||||
|
||||
.collective__progress(v-else)
|
||||
.collective__progress-row
|
||||
span К поставке: {{ p.groupAccumulated ?? p.ownUnits }} {{ p.unitLabel }}
|
||||
.t-muted.collective__progress-hint Поштучный сбор — поставщик принимает заказы по мере поступления.
|
||||
//- Свои заказы в партии — компактными строками (товар/КУ уже в шапке).
|
||||
.collective__own-orders
|
||||
.collective__own-order(v-for="o in p.orders", :key="o.id")
|
||||
span.collective__own-order-who Ваш заказ № {{ o.id.slice(0, 8) }}
|
||||
span.collective__own-order-qty {{ o.quantity }} {{ p.unitLabel }}
|
||||
span.collective__own-order-cost {{ formatCost(parseFloat(o.total_cost) || 0) }}
|
||||
|
||||
//- Свои заказы в накопителе — компактными строками (без больших
|
||||
//- карточек: товар/КУ уже в шапке партии, дубль не нужен).
|
||||
.collective__own-orders
|
||||
.collective__own-order(v-for="o in p.orders", :key="o.id")
|
||||
span.collective__own-order-who Ваш заказ № {{ o.id.slice(0, 8) }}
|
||||
span.collective__own-order-qty {{ o.quantity }} {{ p.unitLabel }}
|
||||
span.collective__own-order-cost {{ formatCost(parseFloat(o.total_cost) || 0) }}
|
||||
|
||||
.collective__own
|
||||
span.t-muted Ваш вклад в партию
|
||||
span.collective__own-val {{ formatCost(p.ownCost) }} · {{ p.ownUnits }} {{ p.unitLabel }}
|
||||
|
||||
//- СФОРМИРОВАННАЯ партия: метрики этапа + карточки заказов.
|
||||
template(v-else)
|
||||
.row.q-col-gutter-md.collective__metrics
|
||||
.col-6.col-md-3
|
||||
.t-muted Этап
|
||||
.collective__metric-val {{ orderStatusDisplay(p.stageStatus).label }}
|
||||
.col-6.col-md-3
|
||||
.t-muted Ваших единиц
|
||||
.collective__metric-val {{ p.ownUnits }} {{ p.unitLabel }}
|
||||
.col-6.col-md-3
|
||||
.t-muted Ваша сумма
|
||||
.collective__metric-val {{ formatCost(p.ownCost) }}
|
||||
.col-6.col-md-3
|
||||
.t-muted Партия
|
||||
.collective__metric-val
|
||||
span(v-if="p.cycle_id") № {{ p.cycle_id }}
|
||||
span(v-else) одиночный
|
||||
|
||||
.collective__cards
|
||||
OrderCard(
|
||||
v-for="o in p.orders",
|
||||
:key="o.id",
|
||||
:order="toOrderCardModel(o)",
|
||||
role="orderer",
|
||||
readonly
|
||||
)
|
||||
.collective__own
|
||||
span.t-muted Ваш вклад в партию
|
||||
span.collective__own-val {{ formatCost(p.ownCost) }} · {{ p.ownUnits }} {{ p.unitLabel }}
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -395,23 +356,6 @@ q-page.collective(role="region", aria-label="Коллективный заказ
|
||||
color: var(--p-ink-1);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
&__metrics {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__metric-val {
|
||||
font-size: var(--p-fs-body);
|
||||
color: var(--p-ink-1);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--p-3, 12px);
|
||||
margin-top: var(--p-2, 8px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
Reference in New Issue
Block a user