2026-09-23 01:12:53 +03:30
|
|
|
|
<!--
|
|
|
|
|
|
"سفارش مشتریان" — the sales expert's referred customers' orders.
|
|
|
|
|
|
|
|
|
|
|
|
Ported from legacy henza_pwa/src/views/user/src/components/ChildOrders.vue,
|
|
|
|
|
|
restyled into this app's design language (PageBar + SearchInput + cards, as
|
|
|
|
|
|
OrdersView uses). The LOGIC is the reference's and is identical in
|
|
|
|
|
|
user-app-v2; only the styling differs between the two apps.
|
|
|
|
|
|
|
|
|
|
|
|
Kept from the reference:
|
|
|
|
|
|
- the title "سفارشات مشتریان" (ChildOrders.vue:18)
|
|
|
|
|
|
- the search box over the list (:26)
|
|
|
|
|
|
- infinite paging: one page per scroll to the bottom (:327-338)
|
|
|
|
|
|
- per card: "سفارش : ORD-{id}", the item count, the total weight in grams,
|
|
|
|
|
|
the customer name, and the fixed status chip «تحویل مسئول فروش» — the
|
|
|
|
|
|
reference hardcodes that label (:48) because the endpoint only ever
|
|
|
|
|
|
returns orders at that status (`where('sts', 5)`, ApiMobileController:766)
|
|
|
|
|
|
- the card is tappable, as the reference's was (:29)
|
|
|
|
|
|
|
2026-10-29 18:09:02 +03:30
|
|
|
|
The reference's edit-order drawer behind that tap (toEditOrder ->
|
|
|
|
|
|
PUT /order/child/{id}) is back, in a form that keeps the reason it was held
|
|
|
|
|
|
back: it used to recalculate an order's weights and prices from values the
|
|
|
|
|
|
CLIENT sent. Here the expert enters ONLY the real raw weight of each piece;
|
|
|
|
|
|
the server applies the making charge the order was placed at, recomputes the
|
|
|
|
|
|
totals and the customer's debt, and refuses anyone but the expert the order
|
|
|
|
|
|
belongs to (OrdersService.expertAdjust). The figures shown under each field
|
|
|
|
|
|
are a preview of that formula, not what is saved. «ثبت نهایی» hands the order
|
|
|
|
|
|
to the customer.
|
2026-09-23 01:12:53 +03:30
|
|
|
|
|
|
|
|
|
|
Scope is the server's: every row belongs to a customer this expert referred.
|
|
|
|
|
|
No id travels from the client.
|
|
|
|
|
|
-->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="cho">
|
|
|
|
|
|
<PageBar title="سفارشات مشتریان" back-to="userPage::hasSubRoute">
|
|
|
|
|
|
<template #below>
|
|
|
|
|
|
<div class="cho__barExtra">
|
|
|
|
|
|
<SearchInput @search="readSearch" place-holder="جستجو" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</PageBar>
|
|
|
|
|
|
|
|
|
|
|
|
<Spinner :spinning="loading && !rows.length" tip="درحال دریافت اطلاعات">
|
2026-10-17 13:11:42 +03:30
|
|
|
|
<AppPullRefresh v-model="refreshing" @refresh="onRefresh">
|
2026-09-23 01:12:53 +03:30
|
|
|
|
<container class="cho__body">
|
|
|
|
|
|
<template v-if="rows.length">
|
|
|
|
|
|
<div class="cho__list">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="item in rows"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
class="cho__card"
|
|
|
|
|
|
@click="openDetail(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="cho__top">
|
|
|
|
|
|
<div class="cho__left">
|
|
|
|
|
|
<div class="cho__num">
|
|
|
|
|
|
<span class="cho__muted">سفارش :</span>
|
|
|
|
|
|
{{ item.orderNumber }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cho__meta">
|
|
|
|
|
|
<span class="cho__strong">{{ item.items?.length ?? 0 }}</span>
|
|
|
|
|
|
<span class="cho__unit">آیتم</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cho__right">
|
|
|
|
|
|
<span class="cho__status">تحویل مسئول فروش</span>
|
|
|
|
|
|
<div class="cho__meta">
|
|
|
|
|
|
<span class="cho__strong">{{ grams(item.w) }}</span>
|
|
|
|
|
|
<span class="cho__unit">گرم</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cho__divider" />
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cho__bottom">
|
|
|
|
|
|
<div class="cho__customer">
|
|
|
|
|
|
نام مشتری : {{ item.customer?.name ?? '—' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Icon icon="vuesax-linear:arrow-left-1" size="20" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="loading" class="cho__more">در حال دریافت…</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-else-if="!loading" class="cho__empty">
|
|
|
|
|
|
<Icon icon="vuesax-linear:box" size="26" />
|
|
|
|
|
|
<p>{{ search ? 'سفارشی با این مشخصات پیدا نشد.' : 'هنوز سفارشی ثبت نشده است.' }}</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-if="failed" class="cho__empty">
|
|
|
|
|
|
<Icon icon="vuesax-linear:danger" size="26" />
|
|
|
|
|
|
<p>دریافت اطلاعات با خطا روبهرو شد.</p>
|
|
|
|
|
|
<button type="button" class="cho__retry" @click="reload">تلاش دوباره</button>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Sentinel for the infinite scroll, as the reference used (:216). -->
|
|
|
|
|
|
<div ref="sentinel" class="cho__sentinel" />
|
|
|
|
|
|
</container>
|
2026-10-17 13:11:42 +03:30
|
|
|
|
</AppPullRefresh>
|
2026-09-23 01:12:53 +03:30
|
|
|
|
</Spinner>
|
|
|
|
|
|
|
|
|
|
|
|
<Drawer
|
2026-10-26 10:51:34 +03:30
|
|
|
|
v-model:visible="detailOpen"
|
2026-09-23 01:12:53 +03:30
|
|
|
|
placement="bottom"
|
|
|
|
|
|
height="auto"
|
|
|
|
|
|
:closable="false"
|
|
|
|
|
|
:body-style="{ padding: '0' }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-if="active" class="cho__detail">
|
|
|
|
|
|
<h2 class="cho__detailTitle">{{ active.orderNumber }}</h2>
|
|
|
|
|
|
<dl class="cho__rows">
|
|
|
|
|
|
<div class="cho__row">
|
|
|
|
|
|
<dt>نام مشتری</dt>
|
|
|
|
|
|
<dd>{{ active.customer?.name ?? '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cho__row">
|
|
|
|
|
|
<dt>شماره تماس</dt>
|
|
|
|
|
|
<dd dir="ltr">{{ active.customer?.mobile ?? '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cho__row">
|
|
|
|
|
|
<dt>وزن کل</dt>
|
|
|
|
|
|
<dd dir="ltr">{{ grams(active.w) }} گرم</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cho__row">
|
|
|
|
|
|
<dt>تعداد آیتمها</dt>
|
|
|
|
|
|
<dd>{{ active.items?.length ?? 0 }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cho__row">
|
|
|
|
|
|
<dt>تاریخ ثبت</dt>
|
|
|
|
|
|
<dd>{{ jdate(active.createdAt) }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cho__row">
|
|
|
|
|
|
<dt>وضعیت</dt>
|
|
|
|
|
|
<dd>تحویل مسئول فروش</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</dl>
|
2026-10-29 18:09:02 +03:30
|
|
|
|
|
|
|
|
|
|
<!-- The real weight of each piece. Only the raw weight is sent; the making
|
|
|
|
|
|
charge, totals and debt are worked out by the server, and the figure
|
|
|
|
|
|
shown under each field is a preview of that. -->
|
|
|
|
|
|
<h3 v-if="drafts.length" class="cho__editTitle">وزن واقعی هر عدد (گرم)</h3>
|
|
|
|
|
|
<div v-for="d in drafts" :key="d.id" class="cho__line">
|
|
|
|
|
|
<div class="cho__lineHead">
|
|
|
|
|
|
<span class="cho__lineName">{{ d.title }}</span>
|
|
|
|
|
|
<span class="cho__lineQty" dir="rtl"><bdi>{{ d.qty }}</bdi> عدد · اجرت <bdi>{{ d.netPercent }}٪</bdi></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<InputNumber v-model:value="d.weight" class="cho__input" :min="0.001" :step="0.01" :precision="3" size="large" />
|
|
|
|
|
|
<div class="cho__lineSum" dir="ltr">
|
|
|
|
|
|
{{ grams(preview(d)) }} g
|
|
|
|
|
|
<span class="cho__muted">({{ d.qty }} × {{ grams(unitPreview(d)) }})</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="drafts.length" class="cho__previewTotal">
|
|
|
|
|
|
<span>جمع وزن پس از ثبت</span>
|
|
|
|
|
|
<b dir="ltr">{{ grams(previewTotal) }} گرم</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p v-if="saveError" class="cho__error">{{ saveError }}</p>
|
|
|
|
|
|
<div v-if="drafts.length" class="cho__actions">
|
|
|
|
|
|
<button type="button" class="cho__save" :disabled="saving" @click="save(false)">ثبت تغییرات</button>
|
|
|
|
|
|
<button type="button" class="cho__final" :disabled="saving" @click="confirmFinal = true">ثبت نهایی</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p v-if="confirmFinal" class="cho__confirm">
|
|
|
|
|
|
آیا مطمئن به ثبت نهایی هستید؟ پس از آن سفارش تحویل مشتری میشود.
|
|
|
|
|
|
<button type="button" class="cho__final" :disabled="saving" @click="save(true)">بله، ثبت نهایی</button>
|
|
|
|
|
|
<button type="button" class="cho__link" @click="confirmFinal = false">بیخیال</button>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
2026-09-23 01:12:53 +03:30
|
|
|
|
<button type="button" class="cho__close" @click="detailOpen = false">بستن</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Drawer>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-10-29 18:09:02 +03:30
|
|
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
|
|
|
|
import { Drawer, InputNumber } from 'ant-design-vue'
|
2026-10-17 13:11:42 +03:30
|
|
|
|
import AppPullRefresh from '../../components/ui/AppPullRefresh.vue'
|
2026-09-23 01:12:53 +03:30
|
|
|
|
import Icon from '../../components/icon/Icon.vue'
|
|
|
|
|
|
import PageBar from '../../components/ui/PageBar.vue'
|
|
|
|
|
|
import Spinner from '../../components/Spinner.vue'
|
|
|
|
|
|
import SearchInput from '../../components/search/SearchInput.vue'
|
|
|
|
|
|
import { jdate } from '../../utils/filters'
|
|
|
|
|
|
import { api, type ExpertChildOrder } from '../../api/client'
|
|
|
|
|
|
|
|
|
|
|
|
const rows = ref<ExpertChildOrder[]>([])
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const refreshing = ref(false)
|
|
|
|
|
|
const failed = ref(false)
|
|
|
|
|
|
const search = ref<string | null>(null)
|
|
|
|
|
|
const page = ref(1)
|
|
|
|
|
|
/** Cleared when a page comes back short, so scrolling stops asking. */
|
|
|
|
|
|
const hasMore = ref(true)
|
|
|
|
|
|
|
|
|
|
|
|
const detailOpen = ref(false)
|
|
|
|
|
|
const active = ref<ExpertChildOrder | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
/** Weights arrive as decimal strings; shown to 3 places, as the reference did. */
|
|
|
|
|
|
function grams(value: string | number | null | undefined): string {
|
|
|
|
|
|
const n = Number(value ?? 0)
|
|
|
|
|
|
return Number.isFinite(n) ? n.toFixed(3) : '0.000'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function fetchPage() {
|
|
|
|
|
|
if (loading.value || !hasMore.value) return
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
failed.value = false
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await api.getExpertChildOrders(page.value, search.value)
|
|
|
|
|
|
const incoming = data.rows ?? []
|
|
|
|
|
|
rows.value = [...rows.value, ...incoming]
|
|
|
|
|
|
if (incoming.length < (data.limit ?? 20)) hasMore.value = false
|
|
|
|
|
|
else page.value += 1
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
failed.value = true
|
|
|
|
|
|
hasMore.value = false
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function reset() {
|
|
|
|
|
|
rows.value = []
|
|
|
|
|
|
page.value = 1
|
|
|
|
|
|
hasMore.value = true
|
|
|
|
|
|
failed.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function reload() {
|
|
|
|
|
|
reset()
|
|
|
|
|
|
await fetchPage()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The reference re-ran the query on every search (ChildOrders.vue:327). */
|
|
|
|
|
|
async function readSearch(value: string | undefined) {
|
|
|
|
|
|
search.value = value?.trim() ? value.trim() : null
|
|
|
|
|
|
await reload()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function onRefresh() {
|
2026-10-17 13:11:42 +03:30
|
|
|
|
try {
|
|
|
|
|
|
await reload()
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
refreshing.value = false
|
|
|
|
|
|
}
|
2026-09-23 01:12:53 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-29 18:09:02 +03:30
|
|
|
|
/** One line of the order as the expert edits it: the raw weight is the only input. */
|
|
|
|
|
|
interface Draft {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
title: string
|
|
|
|
|
|
qty: number
|
|
|
|
|
|
/** The percent the order was placed at (charge − promotion), kept as it is. */
|
|
|
|
|
|
netPercent: number
|
|
|
|
|
|
weight: number | undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const drafts = ref<Draft[]>([])
|
|
|
|
|
|
const saving = ref(false)
|
|
|
|
|
|
const saveError = ref('')
|
|
|
|
|
|
const confirmFinal = ref(false)
|
|
|
|
|
|
|
2026-09-23 01:12:53 +03:30
|
|
|
|
function openDetail(item: ExpertChildOrder) {
|
|
|
|
|
|
active.value = item
|
2026-10-29 18:09:02 +03:30
|
|
|
|
drafts.value = (item.items ?? []).map((i) => ({
|
|
|
|
|
|
id: i.id,
|
|
|
|
|
|
title: [i.productVariant?.product?.title, i.productVariant?.title].filter(Boolean).join(' — ') || 'محصول',
|
|
|
|
|
|
qty: i.quantity,
|
|
|
|
|
|
netPercent: Number(i.wagePercentSnapshot) - Number(i.discountPercentSnapshot),
|
|
|
|
|
|
weight: Number(i.rashWeightGramsSnapshot),
|
|
|
|
|
|
}))
|
|
|
|
|
|
saveError.value = ''
|
|
|
|
|
|
confirmFinal.value = false
|
2026-09-23 01:12:53 +03:30
|
|
|
|
detailOpen.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-29 18:09:02 +03:30
|
|
|
|
/** rash × (1 + net/100) — the server's own formula, shown so the effect is visible before saving. */
|
|
|
|
|
|
const unitPreview = (d: Draft) => (d.weight ?? 0) * (1 + d.netPercent / 100)
|
|
|
|
|
|
const preview = (d: Draft) => unitPreview(d) * d.qty
|
|
|
|
|
|
const previewTotal = computed(() => drafts.value.reduce((s, d) => s + preview(d), 0))
|
|
|
|
|
|
|
|
|
|
|
|
async function save(finalSubmit: boolean) {
|
|
|
|
|
|
if (!active.value || saving.value) return
|
|
|
|
|
|
if (drafts.value.some((d) => !d.weight || d.weight <= 0)) {
|
|
|
|
|
|
saveError.value = 'وزن هر عدد باید بیشتر از صفر باشد.'
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
saving.value = true
|
|
|
|
|
|
saveError.value = ''
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.adjustExpertChildOrder(
|
|
|
|
|
|
active.value.id,
|
|
|
|
|
|
drafts.value.map((d) => ({ id: d.id, rash_weight: d.weight as number })),
|
|
|
|
|
|
finalSubmit,
|
|
|
|
|
|
)
|
|
|
|
|
|
detailOpen.value = false
|
|
|
|
|
|
await reload()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// The server's Persian reason (not found, wrong status, accounting busy, …) is in the body.
|
|
|
|
|
|
const text = e instanceof Error ? e.message : ''
|
|
|
|
|
|
const m = text.match(/"message":"([^"]+)"/)
|
|
|
|
|
|
saveError.value = m ? m[1] : 'ثبت تغییرات انجام نشد؛ دوباره تلاش کنید.'
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saving.value = false
|
|
|
|
|
|
confirmFinal.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-23 01:12:53 +03:30
|
|
|
|
// Infinite scroll, the reference's behaviour (useInfiniteScroll at :216/:330).
|
|
|
|
|
|
const sentinel = ref<HTMLElement | null>(null)
|
|
|
|
|
|
let observer: IntersectionObserver | null = null
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
await fetchPage()
|
|
|
|
|
|
if (typeof IntersectionObserver !== 'undefined') {
|
|
|
|
|
|
observer = new IntersectionObserver((entries) => {
|
|
|
|
|
|
if (entries.some((e) => e.isIntersecting)) void fetchPage()
|
|
|
|
|
|
})
|
|
|
|
|
|
if (sentinel.value) observer.observe(sentinel.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => observer?.disconnect())
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
/*
|
|
|
|
|
|
v2 styling. The template and script above are IDENTICAL to user-app's — only
|
|
|
|
|
|
this block differs, which is the rule for the two apps: same logic, each its
|
|
|
|
|
|
own design. v2 draws a continuous list separated by hairlines rather than
|
|
|
|
|
|
outlined cards, matching its CartView and OrdersView.
|
|
|
|
|
|
*/
|
|
|
|
|
|
.cho__barExtra {
|
|
|
|
|
|
padding: 0 16px 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__body {
|
|
|
|
|
|
padding-top: 132px;
|
|
|
|
|
|
padding-bottom: 90px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__card {
|
|
|
|
|
|
padding: 18px 2px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-bottom: 1px solid @hairline;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__card:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__card:active {
|
|
|
|
|
|
opacity: 0.75;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__num {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__muted {
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__meta {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__strong {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__unit {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__status {
|
|
|
|
|
|
padding: 5px 14px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #06b6d4;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
background: fade(#06b6d4, 12%);
|
2026-09-23 01:12:53 +03:30
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The row separator does this job in v2, so the in-card rule is not drawn. */
|
|
|
|
|
|
.cho__divider {
|
|
|
|
|
|
height: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__bottom {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__customer {
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: @inkMuted;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__more,
|
|
|
|
|
|
.cho__sentinel {
|
|
|
|
|
|
padding: 12px 0;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__empty {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
padding: 34px 20px;
|
|
|
|
|
|
border: 1px dashed @hairline;
|
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__empty p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__retry {
|
|
|
|
|
|
padding: 8px 18px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
border: 1px solid @hairline;
|
|
|
|
|
|
background: @canvas;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__detail {
|
|
|
|
|
|
padding: 20px 16px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__detailTitle {
|
|
|
|
|
|
margin: 0 0 16px;
|
|
|
|
|
|
font-size: 17px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__rows {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 13px 2px;
|
|
|
|
|
|
border-bottom: 1px solid @hairline;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__row:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__row dt {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__row dd {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-29 18:09:02 +03:30
|
|
|
|
.cho__editTitle {
|
|
|
|
|
|
margin: 18px 0 6px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__line {
|
|
|
|
|
|
padding: 12px 0;
|
|
|
|
|
|
border-bottom: 1px solid @hairline;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__lineHead {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__lineName {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__lineQty {
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__lineSum {
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @inkSoft;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__previewTotal {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 14px 0 4px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__error {
|
|
|
|
|
|
margin: 10px 0 0;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
color: #c0392b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__save,
|
|
|
|
|
|
.cho__final {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
border: 1px solid @wine;
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__final {
|
|
|
|
|
|
background: @wine;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__save:disabled,
|
|
|
|
|
|
.cho__final:disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__confirm {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin: 12px 0 0;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
color: @inkSoft;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cho__link {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-23 01:12:53 +03:30
|
|
|
|
.cho__close {
|
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 13px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 14px;
|
2026-10-14 13:31:20 +03:30
|
|
|
|
background: @wine;
|
|
|
|
|
|
color: #fff;
|
2026-09-23 01:12:53 +03:30
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|