Polish admin pricing UI, currency inputs, and active switch confirm.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 12:44:05 +02:00
co-authored by Cursor
parent 47c7d1dc27
commit 7fc9f98f86
14 changed files with 181 additions and 93 deletions
+8 -1
View File
@@ -122,12 +122,19 @@
.admin-card-item__header { .admin-card-item__header {
display: flex; display: flex;
align-items: flex-start; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 10px; gap: 10px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.admin-card-item__header-actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.admin-card-item__title { .admin-card-item__title {
margin: 0; margin: 0;
font-size: 0.875rem; font-size: 0.875rem;
+12 -9
View File
@@ -26,18 +26,21 @@ const { toggle, isLoading, hasError } = useAsyncToggle({
? `${props.entityType ?? 'مورد'} «${props.entityName}» فعال شد.` ? `${props.entityType ?? 'مورد'} «${props.entityName}» فعال شد.`
: `${props.entityType ?? 'مورد'} «${props.entityName}» غیرفعال شد.`, : `${props.entityType ?? 'مورد'} «${props.entityName}» غیرفعال شد.`,
}) })
async function onChange(next: boolean) { async function onChange(next: boolean) {
if (next === props.modelValue) return if (next === props.modelValue) return
if (!next) { const entity = props.entityType ?? 'مورد'
const ok = await confirm({ const ok = await confirm({
title: `غیرفعال‌سازی ${props.entityType ?? 'مورد'}`, title: next ? `فعال‌سازی ${entity}` : `غیرفعال‌سازی ${entity}`,
message: `آیا از غیرفعال کردن «${props.entityName}» مطمئن هستید؟`, message: next
tone: 'danger', ? `آیا از فعال کردن «${props.entityName}» مطمئن هستید؟`
confirmLabel: 'غیرفعال کردن', : `آیا از غیرفعال کردن «${props.entityName}» مطمئن هستید؟`,
}) tone: next ? 'primary' : 'danger',
if (!ok) return confirmLabel: next ? 'فعال کردن' : 'غیرفعال کردن',
} cancelLabel: 'انصراف',
})
if (!ok) return
const result = await toggle(props.id, props.modelValue) const result = await toggle(props.id, props.modelValue)
emit('update:modelValue', result) emit('update:modelValue', result)
+58
View File
@@ -0,0 +1,58 @@
<script setup lang="ts">
import { computed } from 'vue'
import { extractNumericString, formatNumber } from '@/utils/format'
const props = withDefaults(
defineProps<{
modelValue?: number | null
placeholder?: string
disabled?: boolean
id?: string
inputClass?: string
allowEmpty?: boolean
}>(),
{
modelValue: null,
placeholder: '',
disabled: false,
inputClass: 'admin-input',
allowEmpty: true,
},
)
const emit = defineEmits<{
'update:modelValue': [value: number | null]
}>()
const displayValue = computed(() => {
if (props.modelValue == null || !Number.isFinite(props.modelValue)) return ''
return formatNumber(props.modelValue)
})
function onInput(event: Event) {
const input = event.target as HTMLInputElement
const numeric = extractNumericString(input.value)
if (!numeric) {
emit('update:modelValue', props.allowEmpty ? null : 0)
input.value = ''
return
}
const next = Number(numeric)
emit('update:modelValue', next)
input.value = formatNumber(next)
}
</script>
<template>
<input
:id="id"
type="text"
inputmode="numeric"
dir="ltr"
:class="inputClass"
:value="displayValue"
:placeholder="placeholder"
:disabled="disabled"
@input="onInput"
/>
</template>
+11 -10
View File
@@ -26,13 +26,22 @@ const inputId = computed(() => props.id ?? `switch-${crypto.randomUUID().slice(0
const stateText = computed(() => (props.modelValue ? props.activeText : props.inactiveText)) const stateText = computed(() => (props.modelValue ? props.activeText : props.inactiveText))
function onChange(event: Event) { function onChange(event: Event) {
const input = event.target as HTMLInputElement
const next = input.checked
// Keep the visual state locked to modelValue until the parent accepts the change.
// Native click flips the DOM checkbox even when the parent cancels.
input.checked = props.modelValue
if (props.disabled || props.loading) return if (props.disabled || props.loading) return
emit('update:modelValue', (event.target as HTMLInputElement).checked) emit('update:modelValue', next)
} }
</script> </script>
<template> <template>
<label class="base-switch" :class="{ 'base-switch--disabled': disabled || loading, 'base-switch--error': error }"> <label
class="base-switch"
:class="{ 'base-switch--disabled': disabled || loading, 'base-switch--error': error }"
:title="stateText"
>
<span v-if="label" class="base-switch__label">{{ label }}</span> <span v-if="label" class="base-switch__label">{{ label }}</span>
<span class="base-switch__control"> <span class="base-switch__control">
<input <input
@@ -51,7 +60,6 @@ function onChange(event: Event) {
<span v-if="loading" class="base-switch__spinner" /> <span v-if="loading" class="base-switch__spinner" />
</span> </span>
</span> </span>
<span class="base-switch__state">{{ stateText }}</span>
</span> </span>
</label> </label>
</template> </template>
@@ -82,7 +90,6 @@ function onChange(event: Event) {
.base-switch__control { .base-switch__control {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 10px;
} }
.base-switch__input { .base-switch__input {
@@ -131,12 +138,6 @@ function onChange(event: Event) {
transform: translateX(-20px); transform: translateX(-20px);
} }
.base-switch__state {
font-size: 0.8125rem;
color: var(--color-text-muted);
min-width: 3.5rem;
}
.base-switch__spinner { .base-switch__spinner {
width: 12px; width: 12px;
height: 12px; height: 12px;
+1 -4
View File
@@ -82,7 +82,6 @@ export const orders: Order[] = [
requestedMeterage: 2000, requestedMeterage: 2000,
confirmedMeterage: 2000, confirmedMeterage: 2000,
supplyPrice: null, supplyPrice: null,
unitDiscount: 0,
totalAmount: null, totalAmount: null,
}, },
{ {
@@ -93,7 +92,6 @@ export const orders: Order[] = [
requestedMeterage: 1500, requestedMeterage: 1500,
confirmedMeterage: 1000, confirmedMeterage: 1000,
supplyPrice: null, supplyPrice: null,
unitDiscount: 0,
totalAmount: null, totalAmount: null,
}, },
{ {
@@ -104,7 +102,6 @@ export const orders: Order[] = [
requestedMeterage: 750, requestedMeterage: 750,
confirmedMeterage: 750, confirmedMeterage: 750,
supplyPrice: null, supplyPrice: null,
unitDiscount: 0,
totalAmount: null, totalAmount: null,
}, },
], ],
@@ -250,7 +247,7 @@ export const internalMessages: InternalMessage[] = [
id: 'msg-2', id: 'msg-2',
senderName: 'رضا کریمی', senderName: 'رضا کریمی',
subject: 'درخواست اصلاح قیمت', subject: 'درخواست اصلاح قیمت',
preview: 'مشتری درخواست تخفیف ۵٪ روی متراژ بالای ۳۰۰۰ متر داده است.', preview: 'مشتری درخواست اصلاح متراژ سفارش را داده است.',
sentAt: '2024-10-25T08:40:00', sentAt: '2024-10-25T08:40:00',
isRead: false, isRead: false,
}, },
-3
View File
@@ -203,13 +203,11 @@ export type OrderItemPricingPayload = {
confirmedMeterage?: number confirmedMeterage?: number
unitPrice?: number unitPrice?: number
supplyPrice?: number supplyPrice?: number
unitDiscount?: number
} }
export type UpdateOrderPricingPayload = { export type UpdateOrderPricingPayload = {
items: OrderItemPricingPayload[] items: OrderItemPricingPayload[]
shippingCost?: number shippingCost?: number
totalDiscount?: number
note?: string note?: string
} }
@@ -218,7 +216,6 @@ export type ApiOrder = Order & {
settlementType?: SettlementMethod settlementType?: SettlementMethod
totalMeterage?: number totalMeterage?: number
shippingCost?: number | null shippingCost?: number | null
totalDiscount?: number | null
finalAmount?: number | null finalAmount?: number | null
checkRassDays?: number | null checkRassDays?: number | null
checkRassDate?: string checkRassDate?: string
-1
View File
@@ -40,7 +40,6 @@ export interface OrderItem {
requestedMeterage: number requestedMeterage: number
confirmedMeterage: number | null confirmedMeterage: number | null
supplyPrice: number | null supplyPrice: number | null
unitDiscount: number
totalAmount: number | null totalAmount: number | null
} }
+13
View File
@@ -1,6 +1,19 @@
const persianDigits = '۰۱۲۳۴۵۶۷۸۹' const persianDigits = '۰۱۲۳۴۵۶۷۸۹'
const numberFormatter = new Intl.NumberFormat('fa-IR', { useGrouping: true }) const numberFormatter = new Intl.NumberFormat('fa-IR', { useGrouping: true })
export function normalizeDigits(input: string): string {
return input.replace(/[۰-۹]/g, (digit) => String(persianDigits.indexOf(digit)))
}
export function extractNumericString(input: string): string {
return normalizeDigits(input).replace(/[^\d]/g, '')
}
export function parseFormattedNumber(value: string): number {
const numeric = extractNumericString(value)
return numeric ? Number(numeric) : 0
}
export function formatNumber(value: number): string { export function formatNumber(value: number): string {
if (!Number.isFinite(value)) return '—' if (!Number.isFinite(value)) return '—'
return numberFormatter.format(value) return numberFormatter.format(value)
@@ -168,6 +168,7 @@ async function rejectPayment(payment: CashPayment) {
<div class="admin-card-item__meta"> <div class="admin-card-item__meta">
<span>{{ payment.customerName }}</span> <span>{{ payment.customerName }}</span>
<span>{{ formatCurrency(payment.amount) }}</span> <span>{{ formatCurrency(payment.amount) }}</span>
<span v-if="payment.paidAmount != null">پرداختی: {{ formatCurrency(payment.paidAmount) }}</span>
<span dir="ltr">{{ payment.trackingCode }}</span> <span dir="ltr">{{ payment.trackingCode }}</span>
</div> </div>
<div v-if="payment.status === 'pending_review'" class="admin-card-item__actions"> <div v-if="payment.status === 'pending_review'" class="admin-card-item__actions">
+3 -3
View File
@@ -5,7 +5,7 @@ import AdminIcon from '@/components/icons/AdminIcon.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue' import StatusBadge from '@/components/ui/StatusBadge.vue'
import { checksApi, mapAdminOrder, ordersApi, type ApiOrder } from '@/services/api' import { checksApi, mapAdminOrder, ordersApi, type ApiOrder } from '@/services/api'
import { ApiError } from '@/services/http' import { ApiError } from '@/services/http'
import { formatCurrency, formatNumber } from '@/utils/format' import { formatCurrency } from '@/utils/format'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
import type { Check } from '@/types' import type { Check } from '@/types'
@@ -161,7 +161,7 @@ async function approveAll() {
<tbody> <tbody>
<tr v-for="check in orderChecks" :key="check.id"> <tr v-for="check in orderChecks" :key="check.id">
<td>{{ check.row }}</td> <td>{{ check.row }}</td>
<td>{{ formatNumber(check.amount) }}</td> <td>{{ formatCurrency(check.amount) }}</td>
<td>{{ check.bankName }}</td> <td>{{ check.bankName }}</td>
<td>{{ check.sayadId }}</td> <td>{{ check.sayadId }}</td>
<td>{{ check.dueDate.slice(0, 10) }}</td> <td>{{ check.dueDate.slice(0, 10) }}</td>
@@ -196,7 +196,7 @@ async function approveAll() {
<div class="compact-only admin-card-list"> <div class="compact-only admin-card-list">
<article v-for="check in orderChecks" :key="check.id" class="admin-card-item"> <article v-for="check in orderChecks" :key="check.id" class="admin-card-item">
<div class="admin-card-item__header"> <div class="admin-card-item__header">
<p class="admin-card-item__title">چک {{ check.row }} — {{ formatNumber(check.amount) }} تومان</p> <p class="admin-card-item__title">چک {{ check.row }} — {{ formatCurrency(check.amount) }}</p>
<StatusBadge :status="checkBadgeStatus(check.status)" /> <StatusBadge :status="checkBadgeStatus(check.status)" />
</div> </div>
<div class="admin-card-item__meta"> <div class="admin-card-item__meta">
+12 -12
View File
@@ -220,13 +220,18 @@ function clearSearch() {
<article v-for="customer in filteredCustomers" :key="customer.id" class="admin-card-item"> <article v-for="customer in filteredCustomers" :key="customer.id" class="admin-card-item">
<div class="admin-card-item__header"> <div class="admin-card-item__header">
<p class="admin-card-item__title">{{ customer.name }}</p> <p class="admin-card-item__title">{{ customer.name }}</p>
<ActiveSwitchCell <div class="admin-card-item__header-actions">
:id="customer.id" <ActiveSwitchCell
v-model="customer.isActive" :id="customer.id"
:entity-name="customer.name" v-model="customer.isActive"
entity-type="مشتری" :entity-name="customer.name"
:persist="persistActive" entity-type="مشتری"
/> :persist="persistActive"
/>
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEditModal(customer)">
ویرایش
</button>
</div>
</div> </div>
<div class="admin-card-item__meta"> <div class="admin-card-item__meta">
<span>{{ customer.phone }}</span> <span>{{ customer.phone }}</span>
@@ -234,11 +239,6 @@ function clearSearch() {
<span>{{ customer.companyName }}</span> <span>{{ customer.companyName }}</span>
<span>{{ customer.orderCount }} سفارش · {{ formatMeterage(customer.totalMeterage) }}</span> <span>{{ customer.orderCount }} سفارش · {{ formatMeterage(customer.totalMeterage) }}</span>
</div> </div>
<div class="admin-card-item__actions">
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEditModal(customer)">
ویرایش
</button>
</div>
</article> </article>
</div> </div>
+38 -31
View File
@@ -9,6 +9,7 @@ import { ApiError } from '@/services/http'
import { formatCurrency, formatNumber } from '@/utils/format' import { formatCurrency, formatNumber } from '@/utils/format'
import { confirm } from '@/composables/useConfirm' import { confirm } from '@/composables/useConfirm'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
import AdminCurrencyInput from '@/components/ui/AdminCurrencyInput.vue'
import type { OrderItem } from '@/types' import type { OrderItem } from '@/types'
const route = useRoute() const route = useRoute()
@@ -50,7 +51,16 @@ async function loadOrder() {
try { try {
const data = await ordersApi.get(String(route.params.id)) const data = await ordersApi.get(String(route.params.id))
order.value = data order.value = data
items.value = (data.items ?? []).map((i) => ({ ...i })) items.value = (data.items ?? []).map((item) => ({
id: item.id,
catalogCode: item.catalogCode,
productName: item.productName,
color: item.color,
requestedMeterage: item.requestedMeterage,
confirmedMeterage: item.confirmedMeterage,
supplyPrice: item.supplyPrice,
totalAmount: item.totalAmount,
}))
shippingCost.value = data.shippingCost ?? 0 shippingCost.value = data.shippingCost ?? 0
} catch (err) { } catch (err) {
toast.error(err instanceof ApiError ? err.message : 'بارگذاری سفارش انجام نشد.') toast.error(err instanceof ApiError ? err.message : 'بارگذاری سفارش انجام نشد.')
@@ -107,7 +117,6 @@ function submitAddProduct() {
requestedMeterage: form.requestedMeterage, requestedMeterage: form.requestedMeterage,
confirmedMeterage: form.confirmedMeterage || form.requestedMeterage, confirmedMeterage: form.confirmedMeterage || form.requestedMeterage,
supplyPrice: null, supplyPrice: null,
unitDiscount: 0,
totalAmount: null, totalAmount: null,
}) })
@@ -129,13 +138,21 @@ async function savePricing() {
id: item.id, id: item.id,
confirmedMeterage: item.confirmedMeterage ?? undefined, confirmedMeterage: item.confirmedMeterage ?? undefined,
supplyPrice: item.supplyPrice ?? undefined, supplyPrice: item.supplyPrice ?? undefined,
unitDiscount: item.unitDiscount,
})), })),
shippingCost: shippingCost.value, shippingCost: shippingCost.value,
note: customerNote.value.trim() || undefined, note: customerNote.value.trim() || undefined,
}) })
order.value = updated order.value = updated
items.value = (updated.items ?? []).map((i) => ({ ...i })) items.value = (updated.items ?? []).map((item) => ({
id: item.id,
catalogCode: item.catalogCode,
productName: item.productName,
color: item.color,
requestedMeterage: item.requestedMeterage,
confirmedMeterage: item.confirmedMeterage,
supplyPrice: item.supplyPrice,
totalAmount: item.totalAmount,
}))
toast.success('قیمت‌گذاری ذخیره شد.') toast.success('قیمت‌گذاری ذخیره شد.')
} catch (err) { } catch (err) {
toast.error(err instanceof ApiError ? err.message : 'ذخیره قیمت‌گذاری انجام نشد.') toast.error(err instanceof ApiError ? err.message : 'ذخیره قیمت‌گذاری انجام نشد.')
@@ -183,12 +200,6 @@ async function savePricing() {
{{ displayOrder.totalAmount ? formatCurrency(displayOrder.totalAmount) : '—' }} {{ displayOrder.totalAmount ? formatCurrency(displayOrder.totalAmount) : '—' }}
</p> </p>
</div> </div>
<div class="admin-summary-item">
<p class="admin-summary-item__label">تخفیف کل</p>
<p class="admin-summary-item__value">
{{ formatCurrency(order?.totalDiscount ?? 0) }}
</p>
</div>
<div class="admin-summary-item"> <div class="admin-summary-item">
<p class="admin-summary-item__label">هزینه حمل</p> <p class="admin-summary-item__label">هزینه حمل</p>
<p class="admin-summary-item__value"> <p class="admin-summary-item__value">
@@ -216,7 +227,6 @@ async function savePricing() {
<th>متراژ درخواستی</th> <th>متراژ درخواستی</th>
<th>متراژ تأیید شده</th> <th>متراژ تأیید شده</th>
<th>قیمت تأمینی (واحد)</th> <th>قیمت تأمینی (واحد)</th>
<th>تخفیف (واحد)</th>
<th>مبلغ کل (تومان)</th> <th>مبلغ کل (تومان)</th>
<th>عملیات</th> <th>عملیات</th>
</tr> </tr>
@@ -236,20 +246,12 @@ async function savePricing() {
/> />
</td> </td>
<td> <td>
<input <AdminCurrencyInput
v-model.number="item.supplyPrice" v-model="item.supplyPrice"
type="number" input-class="admin-input admin-input--sm"
class="admin-input admin-input--sm"
placeholder="—" placeholder="—"
/> />
</td> </td>
<td>
<input
v-model.number="item.unitDiscount"
type="number"
class="admin-input admin-input--sm"
/>
</td>
<td>{{ item.totalAmount ? formatCurrency(item.totalAmount) : '—' }}</td> <td>{{ item.totalAmount ? formatCurrency(item.totalAmount) : '—' }}</td>
<td> <td>
<button <button
@@ -287,9 +289,12 @@ async function savePricing() {
<input v-model.number="item.confirmedMeterage" type="number" class="admin-input" /> <input v-model.number="item.confirmedMeterage" type="number" class="admin-input" />
</label> </label>
<label> <label>
تخفیف واحد قیمت تأمینی
<input v-model.number="item.unitDiscount" type="number" class="admin-input" /> <AdminCurrencyInput v-model="item.supplyPrice" placeholder="—" />
</label> </label>
<p v-if="item.totalAmount" class="pricing-page__line-total">
مبلغ کل: {{ formatCurrency(item.totalAmount) }}
</p>
</div> </div>
<div class="admin-card-item__actions"> <div class="admin-card-item__actions">
<button type="button" class="admin-btn admin-btn--outline-danger admin-btn--xs" @click="removeItem(item.id)"> <button type="button" class="admin-btn admin-btn--outline-danger admin-btn--xs" @click="removeItem(item.id)">
@@ -304,15 +309,9 @@ async function savePricing() {
</div> </div>
<div class="admin-form-grid"> <div class="admin-form-grid">
<div class="admin-form-field">
<label>اعمال تخفیف روی فاکتور</label>
<select class="admin-input">
<option>بدون اعمال</option>
</select>
</div>
<div class="admin-form-field"> <div class="admin-form-field">
<label>هزینه حمل (تومان)</label> <label>هزینه حمل (تومان)</label>
<input v-model.number="shippingCost" type="number" class="admin-input" /> <AdminCurrencyInput v-model="shippingCost" :allow-empty="false" />
</div> </div>
<div class="admin-form-field"> <div class="admin-form-field">
<label>توضیحات برای مشتری</label> <label>توضیحات برای مشتری</label>
@@ -473,4 +472,12 @@ async function savePricing() {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--color-text-muted); color: var(--color-text-muted);
} }
.pricing-page__line-total {
grid-column: 1 / -1;
margin: 0;
font-size: 0.875rem;
font-weight: 700;
color: var(--color-text);
}
</style> </style>
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import AdminIcon from '@/components/icons/AdminIcon.vue' import AdminIcon from '@/components/icons/AdminIcon.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue' import StatusBadge from '@/components/ui/StatusBadge.vue'
import { formatMeterage } from '@/utils/format' import { formatCurrency, formatMeterage, formatNumber } from '@/utils/format'
import type { Order } from '@/types' import type { Order } from '@/types'
defineProps<{ defineProps<{
@@ -68,7 +68,9 @@ defineEmits<{
</div> </div>
<div class="admin-info-row"> <div class="admin-info-row">
<span class="admin-info-row__label">مبلغ کل:</span> <span class="admin-info-row__label">مبلغ کل:</span>
<span class="admin-info-row__value">پس از قیمت‌گذاری</span> <span class="admin-info-row__value">
{{ order.totalAmount != null ? formatCurrency(order.totalAmount) : 'پس از قیمت‌گذاری' }}
</span>
</div> </div>
</div> </div>
</div> </div>
@@ -93,9 +95,9 @@ defineEmits<{
<td>{{ item.catalogCode }}</td> <td>{{ item.catalogCode }}</td>
<td>{{ item.productName }}</td> <td>{{ item.productName }}</td>
<td>{{ item.color }}</td> <td>{{ item.color }}</td>
<td>{{ item.confirmedMeterage ?? item.requestedMeterage }}</td> <td>{{ formatNumber(item.confirmedMeterage ?? item.requestedMeterage) }}</td>
<td>—</td> <td>{{ item.supplyPrice != null ? formatCurrency(item.supplyPrice) : '—' }}</td>
<td>—</td> <td>{{ item.totalAmount != null ? formatCurrency(item.totalAmount) : '—' }}</td>
<td><StatusBadge status="pending_review" /></td> <td><StatusBadge status="pending_review" /></td>
</tr> </tr>
</tbody> </tbody>
@@ -105,7 +107,9 @@ defineEmits<{
<li v-for="item in order.items" :key="item.id" class="order-detail__item"> <li v-for="item in order.items" :key="item.id" class="order-detail__item">
<strong>{{ item.productName }}</strong> <strong>{{ item.productName }}</strong>
<span>{{ item.catalogCode }} · {{ item.color }}</span> <span>{{ item.catalogCode }} · {{ item.color }}</span>
<span>{{ item.confirmedMeterage ?? item.requestedMeterage }} متر</span> <span>{{ formatMeterage(item.confirmedMeterage ?? item.requestedMeterage) }}</span>
<span v-if="item.supplyPrice != null">قیمت: {{ formatCurrency(item.supplyPrice) }}</span>
<span v-if="item.totalAmount != null">مبلغ: {{ formatCurrency(item.totalAmount) }}</span>
</li> </li>
</ul> </ul>
</div> </div>
+14 -13
View File
@@ -10,6 +10,7 @@ import { ApiError } from '@/services/http'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
import { formatCurrency, formatNumber } from '@/utils/format' import { formatCurrency, formatNumber } from '@/utils/format'
import type { Catalog, Product } from '@/types' import type { Catalog, Product } from '@/types'
import AdminCurrencyInput from '@/components/ui/AdminCurrencyInput.vue'
const productList = ref<Product[]>([]) const productList = ref<Product[]>([])
const catalogs = ref<Catalog[]>([]) const catalogs = ref<Catalog[]>([])
@@ -277,13 +278,18 @@ function clearFilters() {
<article v-for="product in filteredProducts" :key="product.id" class="admin-card-item"> <article v-for="product in filteredProducts" :key="product.id" class="admin-card-item">
<div class="admin-card-item__header"> <div class="admin-card-item__header">
<p class="admin-card-item__title">{{ product.name }}</p> <p class="admin-card-item__title">{{ product.name }}</p>
<ActiveSwitchCell <div class="admin-card-item__header-actions">
:id="product.id" <ActiveSwitchCell
v-model="product.isActive" :id="product.id"
:entity-name="product.name" v-model="product.isActive"
entity-type="محصول" :entity-name="product.name"
:persist="persistActive" entity-type="محصول"
/> :persist="persistActive"
/>
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEditModal(product)">
ویرایش
</button>
</div>
</div> </div>
<div class="admin-card-item__meta"> <div class="admin-card-item__meta">
<span>{{ product.productCode }}</span> <span>{{ product.productCode }}</span>
@@ -291,11 +297,6 @@ function clearFilters() {
<span>کالیته {{ product.catalogCode }}</span> <span>کالیته {{ product.catalogCode }}</span>
<span>{{ product.price ? formatCurrency(product.price) : 'بدون قیمت' }}</span> <span>{{ product.price ? formatCurrency(product.price) : 'بدون قیمت' }}</span>
</div> </div>
<div class="admin-card-item__actions">
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEditModal(product)">
ویرایش
</button>
</div>
</article> </article>
</div> </div>
@@ -342,7 +343,7 @@ function clearFilters() {
<div class="admin-form__row"> <div class="admin-form__row">
<div class="admin-form__field"> <div class="admin-form__field">
<label for="product-price">قیمت (تومان)</label> <label for="product-price">قیمت (تومان)</label>
<input id="product-price" v-model.number="form.price" type="number" min="0" class="admin-input" /> <AdminCurrencyInput id="product-price" v-model="form.price" />
</div> </div>
<div class="admin-form__field"> <div class="admin-form__field">
<label for="product-stock">موجودی داخلی (متر)</label> <label for="product-stock">موجودی داخلی (متر)</label>