Gate order actions by status, add final-approve after checks, and select customers in new orders.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-11 18:11:43 +02:00
co-authored by Cursor
parent 3848617108
commit 5d70002345
20 changed files with 305 additions and 53 deletions
+9
View File
@@ -320,6 +320,15 @@
color: var(--color-text);
}
.admin-input[type='number'],
.admin-input[type='tel'],
.admin-input[inputmode='numeric'],
.admin-input[inputmode='decimal'],
.admin-input[inputmode='tel'],
.admin-input[dir='ltr'] {
text-align: right;
}
select.admin-input {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 24 24'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.75' d='m7 10 5 5 5-5'/%3E%3C/svg%3E");
+13 -1
View File
@@ -181,7 +181,8 @@
.admin-detail-panel__header {
flex-shrink: 0;
display: flex;
display: grid;
grid-template-columns: 40px 1fr 40px;
align-items: center;
gap: 12px;
padding: 12px var(--page-padding);
@@ -200,12 +201,23 @@
justify-content: center;
color: var(--color-text);
flex-shrink: 0;
grid-column: 1;
transition:
background var(--transition-fast),
border-color var(--transition-fast);
}
.admin-detail-panel__back:hover {
background: var(--color-border-light);
border-color: var(--color-border);
}
.admin-detail-panel__title {
margin: 0;
font-size: 1rem;
font-weight: 700;
text-align: center;
grid-column: 2;
}
.admin-detail-panel__body {
+3
View File
@@ -39,6 +39,9 @@ const ICON_MAP: Record<string, string> = {
filter: 'vuesax-linear:filter',
more: 'vuesax-linear:more',
'chevron-left': 'vuesax-linear:arrow-left-1',
'chevron-right': 'vuesax-linear:arrow-right-1',
/** RTL back — same direction as user app IconChevronBack */
'chevron-back': 'vuesax-linear:arrow-right-1',
'chevron-down': 'vuesax-linear:arrow-down',
print: 'vuesax-linear:printer',
sms: 'vuesax-linear:sms',
+7
View File
@@ -49,6 +49,7 @@ function onInput(event: Event) {
type="text"
inputmode="numeric"
dir="ltr"
class="admin-currency-input"
:class="inputClass"
:value="displayValue"
:placeholder="placeholder"
@@ -56,3 +57,9 @@ function onInput(event: Event) {
@input="onInput"
/>
</template>
<style scoped>
.admin-currency-input {
text-align: right;
}
</style>
+3 -1
View File
@@ -61,7 +61,9 @@ function onBackdropClick(event: MouseEvent) {
align-items: center;
justify-content: center;
padding: 16px;
background: rgba(15, 23, 42, 0.45);
background: rgba(15, 23, 42, 0.52);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.admin-modal {
+3 -1
View File
@@ -3,7 +3,7 @@ import type { OrderStatus } from '@/types'
import { ORDER_STATUS_META } from '@/utils/orderStatus'
defineProps<{
status: OrderStatus | 'active' | 'inactive' | 'pending_check_review'
status: OrderStatus | 'active' | 'inactive' | 'pending_check_review' | 'check_approved' | 'check_rejected'
}>()
/** فقط وضعیت‌هایی که سفارش نیستند */
@@ -11,6 +11,8 @@ const extraMeta: Record<string, { label: string; tone: string }> = {
active: { label: 'فعال', tone: 'success' },
inactive: { label: 'غیرفعال', tone: 'neutral' },
pending_check_review: { label: 'در انتظار بررسی', tone: 'warning' },
check_approved: { label: 'تأیید شده', tone: 'success' },
check_rejected: { label: 'رد شده', tone: 'danger' },
}
</script>
+23 -5
View File
@@ -1,8 +1,8 @@
import { computed, inject, onMounted, provide, reactive, ref, watch, type InjectionKey } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { companiesApi, mapAdminOrder, ordersApi } from '@/services/api'
import { companiesApi, customersApi, mapAdminOrder, ordersApi } from '@/services/api'
import { ApiError } from '@/services/http'
import type { Company, Order, RawOrderStatus, SettlementMethod } from '@/types'
import type { Company, Customer, Order, RawOrderStatus, SettlementMethod } from '@/types'
import { rawStatusLabel } from '@/utils/orderStatus'
import { useToast } from '@/composables/useToast'
@@ -13,6 +13,7 @@ export function useOrdersPage() {
const toast = useToast()
const companies = ref<Company[]>([])
const customers = ref<Customer[]>([])
const orderList = ref<Order[]>([])
const activeTab = ref('all')
const selectedOrderId = ref<string>('')
@@ -24,6 +25,7 @@ export function useOrdersPage() {
const saving = ref(false)
const emptyForm = () => ({
customerId: '',
customerName: '',
companyId: companies.value[0]?.id ?? '',
settlementMethod: 'check' as SettlementMethod,
@@ -34,9 +36,18 @@ export function useOrdersPage() {
const form = reactive(emptyForm())
const isFormValid = computed(
() => form.customerName.trim().length > 0 && form.companyId.length > 0,
() => form.customerId.length > 0 && form.companyId.length > 0,
)
const activeCustomers = computed(() => customers.value.filter((c) => c.isActive))
function applyCustomer(customerId: string) {
const customer = customers.value.find((c) => c.id === customerId)
form.customerId = customerId
form.customerName = customer?.name ?? ''
form.customerPhone = customer?.phone ?? ''
}
const filteredOrders = computed(() => {
let list = orderList.value
if (activeTab.value !== 'all') {
@@ -89,12 +100,14 @@ export function useOrdersPage() {
async function loadData() {
try {
const [ordersData, companiesData] = await Promise.all([
const [ordersData, companiesData, customersData] = await Promise.all([
ordersApi.list(),
companiesApi.list(),
customersApi.list(),
])
orderList.value = ordersData.map((o) => mapAdminOrder(o) as Order)
companies.value = companiesData
customers.value = customersData
if (!form.companyId && companiesData[0]) {
form.companyId = companiesData[0].id
}
@@ -196,7 +209,7 @@ export function useOrdersPage() {
async function submitAddOrder() {
if (!isFormValid.value) {
formError.value = 'نام مشتری و شرکت الزامی هستند.'
formError.value = 'انتخاب مشتری و شرکت الزامی است.'
return
}
@@ -204,6 +217,8 @@ export function useOrdersPage() {
try {
const created = await ordersApi.create({
companyId: form.companyId,
customerId: form.customerId || undefined,
userId: form.customerId || undefined,
customerName: form.customerName.trim(),
customerPhone: form.customerPhone.trim() || undefined,
settlementMethod: form.settlementMethod,
@@ -223,6 +238,9 @@ export function useOrdersPage() {
return {
companies,
customers,
activeCustomers,
applyCustomer,
orderList,
activeTab,
selectedOrderId,
+1
View File
@@ -211,6 +211,7 @@ export const customersApi = {
export type AdminCreateOrderPayload = {
companyId: string
customerId?: string
customerName?: string
customerPhone?: string
userId?: string
+19
View File
@@ -55,6 +55,25 @@ export function rawStatusLabel(status?: string): string {
return RAW_ORDER_STATUS_LABELS[status as RawOrderStatus] ?? status
}
/** وضعیت‌هایی که هنوز فاکتور صادر نشده و قیمت‌گذاری اقدام اصلی است. */
export const PRE_INVOICE_RAW_STATUSES: RawOrderStatus[] = [
'cart',
'submitted',
'awaiting_review',
'pricing',
'needs_correction',
]
const PRE_INVOICE_ADMIN_STATUSES: OrderStatus[] = ['pending_review', 'needs_correction']
export function canPriceOrder(order: {
status: OrderStatus
rawStatus?: RawOrderStatus
}): boolean {
if (order.rawStatus) return PRE_INVOICE_RAW_STATUSES.includes(order.rawStatus)
return PRE_INVOICE_ADMIN_STATUSES.includes(order.status)
}
export const ORDER_TABS = [
{ key: 'all', label: 'همه' },
{ key: 'pending_review', label: 'در انتظار بررسی' },
+2
View File
@@ -61,6 +61,8 @@ async function submit() {
class="admin-input login-page__input"
placeholder="۰۹۱۲۱۱۱۱۲۲۲۲"
autocomplete="username"
inputmode="tel"
dir="ltr"
@input="auth.clearError()"
/>
</div>
+69 -6
View File
@@ -29,6 +29,24 @@ const totalCheckAmount = computed(() =>
orderChecks.value.reduce((sum, c) => sum + c.amount, 0),
)
const pendingChecks = computed(() =>
orderChecks.value.filter((c) => c.status === 'pending_review'),
)
const allChecksApproved = computed(
() =>
orderChecks.value.length > 0 &&
orderChecks.value.every((c) => c.status === 'approved'),
)
const canFinalApprove = computed(() => {
if (!allChecksApproved.value || !order.value) return false
const status = order.value.status
return status === 'checks_approved' || status === 'awaiting_check_review'
})
const finalizing = ref(false)
const reviewHistory = computed(() => {
const items = order.value?.statusHistory ?? []
const checkRelated = new Set([
@@ -87,8 +105,8 @@ function goBack() {
}
function checkBadgeStatus(status: Check['status']) {
if (status === 'approved') return 'final_approval'
if (status === 'rejected') return 'needs_correction'
if (status === 'approved') return 'check_approved'
if (status === 'rejected') return 'check_rejected'
return 'pending_check_review'
}
@@ -177,7 +195,7 @@ async function updateCheckStatus(check: Check, status: Check['status']) {
}
async function approveAll() {
const pending = orderChecks.value.filter((c) => c.status === 'pending_review')
const pending = pendingChecks.value
if (!pending.length) {
toast.error('چکی برای تأیید وجود ندارد.')
return
@@ -198,6 +216,34 @@ async function approveAll() {
}
toast.success('همه چک‌های در انتظار تأیید شدند.')
}
async function finalApproveOrder() {
if (!order.value || finalizing.value) return
const ok = await confirm({
title: 'تأیید نهایی سفارش',
message:
'با تأیید نهایی، سفارش از مرحله بررسی چک خارج می‌شود و وارد آماده‌سازی می‌گردد. ادامه می‌دهید؟',
tone: 'primary',
confirmLabel: 'تأیید نهایی',
cancelLabel: 'انصراف',
})
if (!ok) return
finalizing.value = true
try {
const updated = await ordersApi.updateStatus(order.value.id, {
status: 'final_approved',
note: 'تأیید نهایی پس از تأیید همه چک‌ها',
})
order.value = updated
toast.success('سفارش تأیید نهایی شد.')
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'تأیید نهایی انجام نشد.')
} finally {
finalizing.value = false
}
}
</script>
<template>
@@ -352,14 +398,31 @@ async function approveAll() {
<div class="checks-page__footer">
<p class="checks-page__note">
<template v-if="order?.checkRassDate">
<template v-if="canFinalApprove">
همه چک‌ها تأیید شده‌اند. برای ادامه، تأیید نهایی سفارش را ثبت کنید.
</template>
<template v-else-if="order?.checkRassDate">
راس: {{ formatDate(order.checkRassDate) }}
</template>
<template v-else>راس بر اساس تاریخ ثبت سفارش محاسبه می‌شود.</template>
</p>
<div class="admin-action-group">
<button type="button" class="admin-btn admin-btn--success admin-btn--sm" @click="approveAll">
تأیید همه
<button
v-if="canFinalApprove"
type="button"
class="admin-btn admin-btn--success admin-btn--sm"
:disabled="finalizing"
@click="finalApproveOrder"
>
{{ finalizing ? 'در حال ثبت…' : 'تأیید نهایی سفارش' }}
</button>
<button
v-if="pendingChecks.length"
type="button"
class="admin-btn admin-btn--success admin-btn--sm"
@click="approveAll"
>
تأیید همه چک‌ها
</button>
<button type="button" class="admin-btn admin-btn--outline admin-btn--sm" @click="goBack">
بازگشت
+4
View File
@@ -218,6 +218,8 @@ function goBack() {
type="text"
class="admin-input"
placeholder="1234567890"
inputmode="numeric"
dir="ltr"
/>
</div>
</div>
@@ -243,6 +245,8 @@ function goBack() {
type="text"
class="admin-input"
placeholder="6104 3378 0077 1234"
inputmode="numeric"
dir="ltr"
/>
</div>
<div class="admin-form__field">
+16 -2
View File
@@ -275,11 +275,25 @@ function clearSearch() {
<div class="admin-form__row">
<div class="admin-form__field">
<label for="customer-phone">تلفن <span class="admin-form__required">*</span></label>
<input id="customer-phone" v-model="form.phone" type="text" class="admin-input" />
<input
id="customer-phone"
v-model="form.phone"
type="tel"
class="admin-input"
inputmode="tel"
dir="ltr"
/>
</div>
<div class="admin-form__field">
<label for="customer-national-id">شناسه ملی <span class="admin-form__required">*</span></label>
<input id="customer-national-id" v-model="form.nationalId" type="text" class="admin-input" />
<input
id="customer-national-id"
v-model="form.nationalId"
type="text"
class="admin-input"
inputmode="numeric"
dir="ltr"
/>
</div>
</div>
+5 -4
View File
@@ -10,7 +10,8 @@ import { formatCurrency, formatNumber } from '@/utils/format'
import { confirm } from '@/composables/useConfirm'
import { useToast } from '@/composables/useToast'
import AdminCurrencyInput from '@/components/ui/AdminCurrencyInput.vue'
import type { OrderItem } from '@/types'
import { PRE_INVOICE_RAW_STATUSES } from '@/utils/orderStatus'
import type { OrderItem, RawOrderStatus } from '@/types'
const route = useRoute()
const router = useRouter()
@@ -48,10 +49,10 @@ const displayOrder = computed(() =>
)
/** تا وقتی سفارش در این وضعیت‌هاست، فاکتور برای مشتری ارسال نشده است. */
const PRE_INVOICE_STATUSES = ['cart', 'submitted', 'awaiting_review', 'pricing', 'needs_correction']
const invoiceSent = computed(() =>
order.value ? !PRE_INVOICE_STATUSES.includes(order.value.status) : false,
order.value
? !PRE_INVOICE_RAW_STATUSES.includes(order.value.status as RawOrderStatus)
: false,
)
const canSendInvoice = computed(() => !!order.value?.finalAmount && !invoiceSent.value)
+2
View File
@@ -2,6 +2,7 @@
import { provideOrdersPage } from '@/composables/useOrdersPage'
import DesktopOrdersPage from '@/views/orders/components/DesktopOrdersPage.vue'
import CompactOrdersPage from '@/views/orders/components/CompactOrdersPage.vue'
import OrderAddModal from '@/views/orders/components/OrderAddModal.vue'
provideOrdersPage()
</script>
@@ -9,4 +10,5 @@ provideOrdersPage()
<template>
<DesktopOrdersPage class="desktop-only" />
<CompactOrdersPage class="compact-only" />
<OrderAddModal />
</template>
@@ -4,12 +4,13 @@ import AdminIcon from '@/components/icons/AdminIcon.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import BaseModal from '@/components/ui/BaseModal.vue'
import OrderDetailContent from '@/views/orders/components/OrderDetailContent.vue'
import OrderAddModal from '@/views/orders/components/OrderAddModal.vue'
import { useOrdersPageContext } from '@/composables/useOrdersPage'
import { ORDER_TABS } from '@/utils/orderStatus'
import { formatCurrency, formatDateTime, formatMeterage } from '@/utils/format'
const isMobile = useMediaQuery('(max-width: 767px)')
/** Matches `.compact-only` visible range (< 1280px) */
const isCompactLayout = useMediaQuery('(max-width: 1279px)')
const {
activeTab,
@@ -141,7 +142,7 @@ function handleCashReceipt() {
aria-label="بازگشت به لیست"
@click="closeDetailPanel"
>
<AdminIcon name="chevron-left" :size="20" />
<AdminIcon name="chevron-back" :size="20" />
</button>
<h2 class="admin-detail-panel__title">جزئیات سفارش</h2>
</div>
@@ -161,7 +162,7 @@ function handleCashReceipt() {
<!-- Tablet / small desktop: modal -->
<BaseModal
:open="!isMobile && showDetailModal && !!selectedOrder"
:open="isCompactLayout && !isMobile && showDetailModal && !!selectedOrder"
title="جزئیات سفارش"
size="xl"
@close="closeDetailModal"
@@ -179,8 +180,6 @@ function handleCashReceipt() {
/>
</template>
</BaseModal>
<OrderAddModal />
</div>
</template>
@@ -1,15 +1,18 @@
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import BaseModal from '@/components/ui/BaseModal.vue'
import OrderDetailContent from '@/views/orders/components/OrderDetailContent.vue'
import OrderAddModal from '@/views/orders/components/OrderAddModal.vue'
import { useOrdersPageContext } from '@/composables/useOrdersPage'
import { ORDER_TABS } from '@/utils/orderStatus'
import { ORDER_TABS, canPriceOrder } from '@/utils/orderStatus'
import { formatCurrency, formatDateTime, formatMeterage } from '@/utils/format'
import type { Order } from '@/types'
/** Matches `.desktop-only` in responsive.css (>= 1280px) */
const isDesktopLayout = useMediaQuery('(min-width: 1280px)')
const {
activeTab,
selectedOrderId,
@@ -241,6 +244,7 @@ function runMenuAction(action: 'detail' | 'pricing' | 'checks' | 'cash') {
جزئیات سفارش
</button>
<button
v-if="menuOrder && canPriceOrder(menuOrder)"
type="button"
role="menuitem"
class="orders-page__menu-item"
@@ -270,7 +274,7 @@ function runMenuAction(action: 'detail' | 'pricing' | 'checks' | 'cash') {
</Teleport>
<BaseModal
:open="showDetailModal && !!selectedOrder"
:open="isDesktopLayout && showDetailModal && !!selectedOrder"
title="جزئیات سفارش"
size="xl"
@close="closeDetailModal"
@@ -306,8 +310,6 @@ function runMenuAction(action: 'detail' | 'pricing' | 'checks' | 'cash') {
</button>
</template>
</BaseModal>
<OrderAddModal />
</div>
</template>
+46 -9
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
import { computed, watch } from 'vue'
import BaseModal from '@/components/ui/BaseModal.vue'
import AdminSelect from '@/components/ui/AdminSelect.vue'
import { useOrdersPageContext } from '@/composables/useOrdersPage'
@@ -10,6 +10,8 @@ const {
form,
isFormValid,
companies,
activeCustomers,
applyCustomer,
closeAddModal,
submitAddOrder,
} = useOrdersPageContext()
@@ -18,10 +20,31 @@ const companyOptions = computed(() =>
companies.value.map((company) => ({ value: company.id, label: company.name })),
)
const customerOptions = computed(() =>
activeCustomers.value.map((customer) => ({
value: customer.id,
label: customer.phone
? `${customer.name} — ${customer.phone}`
: customer.name,
})),
)
const settlementOptions = [
{ value: 'check', label: 'چکی' },
{ value: 'cash', label: 'نقدی' },
]
watch(
() => form.customerId,
(id) => {
if (!id) {
form.customerName = ''
form.customerPhone = ''
return
}
applyCustomer(id)
},
)
</script>
<template>
@@ -30,14 +53,17 @@ const settlementOptions = [
<p v-if="formError" class="admin-form__error" role="alert">{{ formError }}</p>
<div class="admin-form__field">
<label for="order-customer">نام مشتری <span class="admin-form__required">*</span></label>
<input
id="order-customer"
v-model="form.customerName"
type="text"
class="admin-input"
placeholder="مثال: پارچه‌سرای نیکو"
<label>نام مشتری <span class="admin-form__required">*</span></label>
<AdminSelect
v-model="form.customerId"
block
aria-label="انتخاب مشتری"
placeholder="انتخاب مشتری از لیست"
:options="customerOptions"
/>
<p v-if="!customerOptions.length" class="admin-form__hint">
مشتری فعالی یافت نشد. ابتدا از بخش مشتریان ثبت کنید.
</p>
</div>
<div class="admin-form__row">
@@ -80,7 +106,10 @@ const settlementOptions = [
v-model="form.customerPhone"
type="tel"
class="admin-input"
placeholder="۰۹۱۲۱۲۳۴۵۶۷"
placeholder="پس از انتخاب مشتری پر می‌شود"
inputmode="tel"
dir="ltr"
readonly
/>
</div>
</div>
@@ -101,3 +130,11 @@ const settlementOptions = [
</template>
</BaseModal>
</template>
<style scoped>
.admin-form__hint {
margin: 6px 0 0;
font-size: 0.75rem;
color: var(--color-text-muted);
}
</style>
@@ -4,8 +4,9 @@ import AdminIcon from '@/components/icons/AdminIcon.vue'
import AdminSelect from '@/components/ui/AdminSelect.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import { formatCurrency, formatMeterage, formatNumber } from '@/utils/format'
import { RAW_ORDER_STATUS_LABELS, rawStatusLabel } from '@/utils/orderStatus'
import type { Order, RawOrderStatus } from '@/types'
import { CUSTOMER_TYPE_LABELS } from '@/utils/adminLabels'
import { ORDER_STATUS_META, RAW_ORDER_STATUS_LABELS, canPriceOrder, rawStatusLabel } from '@/utils/orderStatus'
import type { CustomerType, Order, RawOrderStatus } from '@/types'
const props = withDefaults(
defineProps<{
@@ -61,6 +62,34 @@ const canSubmitStatus = computed(
const useTable = computed(() => props.layout === 'desktop')
const badgeStatusLabel = computed(
() => ORDER_STATUS_META[props.order.status]?.label ?? props.order.status,
)
/** Avoid duplicating the same Persian label under the badge. */
const distinctRawStatusLabel = computed(() => {
const raw = rawStatusLabel(props.order.rawStatus)
if (!raw || raw === badgeStatusLabel.value) return ''
return raw
})
const customerTypeLabel = computed(() => {
const type = props.order.customerType as CustomerType | undefined
if (!type) return '—'
return CUSTOMER_TYPE_LABELS[type] ?? props.order.customerType ?? '—'
})
const showPricingAction = computed(() => canPriceOrder(props.order))
const showChecksAction = computed(() => props.order.settlementMethod === 'check')
const showCashAction = computed(() => props.order.settlementMethod === 'cash')
/** Primary CTA follows the current workflow stage. */
const primaryAction = computed<'pricing' | 'checks' | 'cash' | null>(() => {
if (showPricingAction.value) return 'pricing'
if (showChecksAction.value) return 'checks'
if (showCashAction.value) return 'cash'
return null
})
watch([() => props.order.id, manualMode], () => {
nextStatus.value = ''
statusNote.value = ''
@@ -89,25 +118,35 @@ function submitStatus() {
<strong>{{ order.orderNumber }}</strong>
<StatusBadge :status="order.status" />
</div>
<p class="order-detail__raw-status">{{ rawStatusLabel(order.rawStatus) }}</p>
<p v-if="distinctRawStatusLabel" class="order-detail__raw-status">
{{ distinctRawStatusLabel }}
</p>
</div>
<div class="order-detail__actions no-print">
<button type="button" class="admin-btn admin-btn--primary admin-btn--sm" @click="emit('pricing')">
<button
v-if="showPricingAction"
type="button"
class="admin-btn admin-btn--sm"
:class="primaryAction === 'pricing' ? 'admin-btn--primary' : 'admin-btn--outline'"
@click="emit('pricing')"
>
قیمت‌گذاری سفارش
</button>
<button
v-if="order.settlementMethod === 'check'"
v-if="showChecksAction"
type="button"
class="admin-btn admin-btn--outline admin-btn--sm"
class="admin-btn admin-btn--sm"
:class="primaryAction === 'checks' ? 'admin-btn--primary' : 'admin-btn--outline'"
@click="emit('checks')"
>
مدیریت چک‌ها
</button>
<button
v-if="order.settlementMethod === 'cash'"
v-if="showCashAction"
type="button"
class="admin-btn admin-btn--outline admin-btn--sm"
class="admin-btn admin-btn--sm"
:class="primaryAction === 'cash' ? 'admin-btn--primary' : 'admin-btn--outline'"
@click="emit('cashReceipt')"
>
رسید پرداخت
@@ -127,7 +166,7 @@ function submitStatus() {
<div class="order-detail__info-row">
<AdminIcon name="phone" :size="16" />
<span class="order-detail__info-label">تلفن</span>
<span class="order-detail__info-value" dir="ltr">{{ order.customerPhone ?? '—' }}</span>
<span class="order-detail__info-value order-detail__info-value--phone" dir="ltr">{{ order.customerPhone ?? '—' }}</span>
</div>
<div class="order-detail__info-row">
<AdminIcon name="id-card" :size="16" />
@@ -137,7 +176,7 @@ function submitStatus() {
<div class="order-detail__info-row">
<AdminIcon name="user" :size="16" />
<span class="order-detail__info-label">نوع مشتری</span>
<span class="order-detail__info-value">{{ order.customerType ?? '—' }}</span>
<span class="order-detail__info-value">{{ customerTypeLabel }}</span>
</div>
</div>
</section>
@@ -288,13 +327,17 @@ function submitStatus() {
.order-detail__id {
display: flex;
flex-wrap: wrap;
align-items: center;
flex-direction: column;
align-items: flex-start;
gap: 8px;
margin-bottom: 4px;
margin-bottom: 0;
font-size: 1rem;
}
.order-detail__id strong {
line-height: 1.3;
}
.order-detail__raw-status {
margin: 0;
font-size: 0.75rem;
@@ -359,6 +402,11 @@ function submitStatus() {
word-break: break-word;
}
.order-detail__info-value--phone {
text-align: right;
justify-self: stretch;
}
.order-detail__table {
overflow-x: auto;
}
+8 -1
View File
@@ -253,7 +253,14 @@ function clearSearch() {
<div class="admin-form__row">
<div class="admin-form__field">
<label for="user-phone">تلفن <span class="admin-form__required">*</span></label>
<input id="user-phone" v-model="form.phone" type="text" class="admin-input" />
<input
id="user-phone"
v-model="form.phone"
type="tel"
class="admin-input"
inputmode="tel"
dir="ltr"
/>
</div>
<div class="admin-form__field">
<label for="user-email">ایمیل <span class="admin-form__required">*</span></label>