diff --git a/src/assets/styles/admin.css b/src/assets/styles/admin.css index 9262ec2..3b7de25 100644 --- a/src/assets/styles/admin.css +++ b/src/assets/styles/admin.css @@ -38,7 +38,7 @@ .admin-table th { padding: 11px 16px; - text-align: right; + text-align: center; font-weight: 600; font-size: 0.8125rem; color: var(--color-text-muted); @@ -49,6 +49,7 @@ .admin-table td { padding: 13px 16px; + text-align: center; border-bottom: 1px solid var(--color-border-light); vertical-align: middle; color: var(--color-text); @@ -69,7 +70,7 @@ .admin-table .col-numeric, .admin-table td.numeric, .admin-table th.numeric { - text-align: left; + text-align: center; font-variant-numeric: tabular-nums; } @@ -319,6 +320,19 @@ color: var(--color-text); } +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"); + background-repeat: no-repeat; + background-position: left 12px center; + padding-inline: 14px 36px; + cursor: pointer; +} + +html.dark select.admin-input { + 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='%94a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.75' d='m7 10 5 5 5-5'/%3E%3C/svg%3E"); +} + .admin-input:focus { outline: none; border-color: var(--color-primary); @@ -633,3 +647,55 @@ grid-template-columns: 1fr 1fr; gap: 12px; } + +@media print { + @page { + margin: 12mm; + } + + html.printing-order-detail body > *:not(.admin-modal-backdrop) { + display: none !important; + } + + html.printing-order-detail .admin-modal-backdrop { + position: static !important; + inset: auto !important; + z-index: auto !important; + display: block !important; + padding: 0 !important; + background: transparent !important; + } + + html.printing-order-detail .admin-modal { + width: 100% !important; + max-height: none !important; + overflow: visible !important; + border-radius: 0 !important; + box-shadow: none !important; + background: #fff !important; + color: #111 !important; + } + + html.printing-order-detail .admin-modal__header { + border-bottom-color: #ddd !important; + padding: 0 0 12px !important; + } + + html.printing-order-detail .admin-modal__close, + html.printing-order-detail .admin-modal__footer, + html.printing-order-detail .no-print, + html.printing-order-detail .orders-page__modal-breadcrumb, + html.printing-order-detail .compact-orders__modal-breadcrumb { + display: none !important; + } + + html.printing-order-detail .admin-modal__body { + overflow: visible !important; + max-height: none !important; + padding: 0 !important; + } + + html.printing-order-detail .admin-modal__title { + color: #111 !important; + } +} diff --git a/src/components/ui/AdminSelect.vue b/src/components/ui/AdminSelect.vue new file mode 100644 index 0000000..00e830b --- /dev/null +++ b/src/components/ui/AdminSelect.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/src/views/cash-payments/CashPaymentsView.vue b/src/views/cash-payments/CashPaymentsView.vue index e5fd921..792ab0f 100644 --- a/src/views/cash-payments/CashPaymentsView.vue +++ b/src/views/cash-payments/CashPaymentsView.vue @@ -2,6 +2,7 @@ import { computed, onMounted, ref } from 'vue' import { useRoute } from 'vue-router' import AdminIcon from '@/components/icons/AdminIcon.vue' +import AdminSelect from '@/components/ui/AdminSelect.vue' import { cashPaymentsApi } from '@/services/api' import { ApiError } from '@/services/http' import { @@ -19,6 +20,14 @@ const statusFilter = ref<'all' | CashPayment['status']>('all') const toast = useToast() const busyId = ref(null) +const statusFilterOptions = [ + { value: 'all', label: 'همه وضعیت‌ها' }, + { value: 'pending_review', label: 'در انتظار تأیید' }, + { value: 'approved', label: 'تأیید شده' }, + { value: 'rejected', label: 'رد شده' }, + { value: 'expired', label: 'منقضی‌شده' }, +] + const filteredPayments = computed(() => { let list = paymentList.value if (statusFilter.value !== 'all') { @@ -93,13 +102,12 @@ async function rejectPayment(payment: CashPayment) { placeholder="جستجو در شماره سفارش، مشتری، پیگیری..." /> - +
@@ -221,6 +229,12 @@ async function rejectPayment(payment: CashPayment) { background: transparent; font-size: 0.875rem; outline: none; + color: var(--color-text); +} + +.cash-payments-page__status-select { + flex: 0 1 220px; + width: 220px; } .cash-payments-page__table { diff --git a/src/views/catalogs/CatalogsView.vue b/src/views/catalogs/CatalogsView.vue index 107facc..ed15641 100644 --- a/src/views/catalogs/CatalogsView.vue +++ b/src/views/catalogs/CatalogsView.vue @@ -5,6 +5,7 @@ import BaseSwitch from '@/components/ui/BaseSwitch.vue' import BaseEmptyState from '@/components/ui/BaseEmptyState.vue' import ActiveSwitchCell from '@/components/ui/ActiveSwitchCell.vue' import AdminIcon from '@/components/icons/AdminIcon.vue' +import AdminSelect from '@/components/ui/AdminSelect.vue' import { catalogsApi, companiesApi } from '@/services/api' import { ApiError } from '@/services/http' import { useToast } from '@/composables/useToast' @@ -20,6 +21,15 @@ const formError = ref('') const saving = ref(false) const toast = useToast() +const companyFilterOptions = computed(() => [ + { value: 'all', label: 'همه شرکت‌ها' }, + ...companies.value.map((company) => ({ value: company.id, label: company.name })), +]) + +const companyFormOptions = computed(() => + companies.value.map((company) => ({ value: company.id, label: company.name })), +) + const emptyForm = () => ({ companyId: companies.value[0]?.id ?? '', code: '', @@ -173,12 +183,12 @@ function clearFilters() { placeholder="جستجو بر اساس کد یا نام..." />
- + {{ formError }}

- - + +
diff --git a/src/views/checks/ChecksListView.vue b/src/views/checks/ChecksListView.vue index 4bef4eb..ec4a755 100644 --- a/src/views/checks/ChecksListView.vue +++ b/src/views/checks/ChecksListView.vue @@ -2,6 +2,7 @@ import { computed, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import AdminIcon from '@/components/icons/AdminIcon.vue' +import AdminSelect from '@/components/ui/AdminSelect.vue' import StatusBadge from '@/components/ui/StatusBadge.vue' import { checksApi, ordersApi } from '@/services/api' import { ApiError } from '@/services/http' @@ -17,6 +18,13 @@ const searchQuery = ref('') const statusFilter = ref<'all' | Check['status']>('all') const busyId = ref(null) +const statusFilterOptions = [ + { value: 'all', label: 'همه وضعیت‌ها' }, + { value: 'pending_review', label: 'در انتظار بررسی' }, + { value: 'approved', label: 'تأیید شده' }, + { value: 'rejected', label: 'رد شده' }, +] + const filteredChecks = computed(() => { let list = checkList.value if (statusFilter.value !== 'all') { @@ -125,12 +133,12 @@ async function updateCheckStatus(check: Check, status: Check['status']) { placeholder="جستجو در شماره سفارش، مشتری، بانک، صیاد..." />
- +
@@ -293,6 +301,12 @@ async function updateCheckStatus(check: Check, status: Check['status']) { background: transparent; outline: none; font-size: 0.875rem; + color: var(--color-text); +} + +.checks-list-page__status-select { + flex: 0 1 220px; + width: 220px; } .checks-list-page__table { diff --git a/src/views/customers/CustomersView.vue b/src/views/customers/CustomersView.vue index e8ede4d..df33a69 100644 --- a/src/views/customers/CustomersView.vue +++ b/src/views/customers/CustomersView.vue @@ -5,6 +5,7 @@ import BaseSwitch from '@/components/ui/BaseSwitch.vue' import BaseEmptyState from '@/components/ui/BaseEmptyState.vue' import ActiveSwitchCell from '@/components/ui/ActiveSwitchCell.vue' import AdminIcon from '@/components/icons/AdminIcon.vue' +import AdminSelect from '@/components/ui/AdminSelect.vue' import { customersApi } from '@/services/api' import { ApiError } from '@/services/http' import { useToast } from '@/composables/useToast' @@ -20,6 +21,24 @@ const formError = ref('') const saving = ref(false) const toast = useToast() +function displayCell(value: string | number | null | undefined): string { + if (value == null) return '-' + if (typeof value === 'number') return Number.isFinite(value) ? String(value) : '-' + const text = value.trim() + if (!text || text === '—') return '-' + return text +} + +function displayMeterage(value: number | null | undefined): string { + if (value == null || !Number.isFinite(value)) return '-' + return formatMeterage(value) +} + +const customerTypeOptions = [ + { value: 'wholesale', label: 'عمده‌فروش' }, + { value: 'retail', label: 'خرده‌فروش' }, +] + const emptyForm = () => ({ name: '', phone: '', @@ -154,7 +173,7 @@ function clearSearch() { v-if="!filteredCustomers.length" class="admin-card" icon="customers" - :title="searchQuery.trim() ? 'نتیجه‌ای یافت نشد' : 'مشتری‌ای ثبت نشده'" + :title="searchQuery.trim() ? 'نتیجه‌ای یافت نشد' : 'مشتریان ثبت شده'" :description=" searchQuery.trim() ? 'عبارت جستجو را تغییر دهید یا فیلترها را پاک کنید.' @@ -188,13 +207,13 @@ function clearSearch() { - {{ customer.name }} - {{ customer.phone }} - {{ customer.nationalId }} - {{ CUSTOMER_TYPE_LABELS[customer.customerType] }} - {{ customer.companyName }} - {{ customer.orderCount }} - {{ formatMeterage(customer.totalMeterage) }} + {{ displayCell(customer.name) }} + {{ displayCell(customer.phone) }} + {{ displayCell(customer.nationalId) }} + {{ displayCell(CUSTOMER_TYPE_LABELS[customer.customerType]) }} + {{ displayCell(customer.companyName) }} + {{ displayCell(customer.orderCount) }} + {{ displayMeterage(customer.totalMeterage) }} - {{ formatDateTime(customer.registeredAt) }} + {{ displayCell(formatDateTime(customer.registeredAt)) }} - diff --git a/src/views/orders/components/OrderAddModal.vue b/src/views/orders/components/OrderAddModal.vue index 531e66b..d976a29 100644 --- a/src/views/orders/components/OrderAddModal.vue +++ b/src/views/orders/components/OrderAddModal.vue @@ -1,5 +1,7 @@