ui(admin-panel): responsive persons menu, XTable phone cards, full-screen form modal
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -125,3 +125,50 @@ body #app .ant-menu-item-selected {
|
||||
.content-area__content > .ant-card {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// ── Phone page chrome ────────────────────────────────────────────────
|
||||
// Opt-in per page (class="page-card" on the page's root card) as each page is
|
||||
// made responsive. Base-form modals get the same treatment for their body.
|
||||
@media (max-width: 575px) {
|
||||
.page-card,
|
||||
.base-form-modal .ant-modal-body {
|
||||
.ant-card-head {
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.ant-card-head-title {
|
||||
font-size: 14px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.ant-card-extra {
|
||||
padding: 6px 0;
|
||||
}
|
||||
.ant-card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
.mb-base {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.grid-2,
|
||||
.grid-3,
|
||||
.grid-4 {
|
||||
row-gap: 0;
|
||||
column-gap: 12px;
|
||||
}
|
||||
.ant-tabs-nav {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-submit {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
// a nested section card inside the page card: drop the double frame
|
||||
.page-card .ant-card .ant-card,
|
||||
.page-card > .ant-card-body .ant-card {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
:ok-text="okText"
|
||||
cancel-text="انصراف"
|
||||
:mask-closable="false"
|
||||
wrap-class-name="base-form-modal"
|
||||
@update:open="onUpdateOpen"
|
||||
@ok="$emit('submit')"
|
||||
@cancel="onCancel"
|
||||
@@ -74,3 +75,44 @@ function onCancel() {
|
||||
emit('cancel');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Unscoped: <a-modal> teleports to <body>; contained by wrap-class-name. -->
|
||||
<style>
|
||||
@media (max-width: 575px) {
|
||||
.base-form-modal .ant-modal {
|
||||
/* ant sets the width inline from :width */
|
||||
width: 100vw !important;
|
||||
max-width: 100vw;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.base-form-modal .ant-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(var(--vh, 1vh) * 100);
|
||||
border-radius: 0;
|
||||
padding: 14px 16px 0;
|
||||
}
|
||||
.base-form-modal .ant-modal-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.base-form-modal .ant-modal-body {
|
||||
flex: 1;
|
||||
}
|
||||
.base-form-modal .ant-modal-footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 0 -16px;
|
||||
padding: 10px 16px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
.base-form-modal .ant-modal-footer > .ant-btn {
|
||||
flex: 1;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
backend does not implement. The buttons are omitted rather than left as
|
||||
dead controls that silently fail.
|
||||
-->
|
||||
<div class="x-table">
|
||||
<div class="x-table" :class="{ 'x-table--cards': cardMode }">
|
||||
<div class="x-table__toolbar">
|
||||
<div class="x-table__search-group">
|
||||
<a-input-search
|
||||
v-if="!model._hideSearch"
|
||||
v-model:value="query.search"
|
||||
class="x-table__search"
|
||||
placeholder="جستجو"
|
||||
allow-clear
|
||||
style="max-width: 260px"
|
||||
@search="onSearch"
|
||||
/>
|
||||
|
||||
@@ -57,6 +57,18 @@
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- card view has no column headers to click, so sorting moves here -->
|
||||
<a-select
|
||||
v-if="cardMode && sortOptions.length"
|
||||
:value="sortValue"
|
||||
class="x-table__sort"
|
||||
size="small"
|
||||
placeholder="مرتبسازی"
|
||||
allow-clear
|
||||
:options="sortOptions"
|
||||
@change="onSortSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +121,52 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="cardMode" class="x-table__cards">
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!data.length && !loading" :description="locale.emptyText" />
|
||||
<div v-for="record in data" :key="String(record.id)" class="x-card">
|
||||
<div class="x-card__head">
|
||||
<div class="x-card__title">
|
||||
<span v-if="cardIdCol" class="x-card__id">#{{ cellValue(record, cardIdCol) }}</span>
|
||||
<span class="x-card__name">
|
||||
<template v-for="col in cardTitleCols" :key="col.key">
|
||||
<slot v-if="col.slot" :name="col.slot" :text="cellValue(record, col)" :record="record" />
|
||||
<template v-else>{{ displayValue(record, col) }} </template>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="cardActionCol" class="x-card__actions">
|
||||
<slot :name="cardActionCol.slot" :text="cellValue(record, cardActionCol)" :record="record" />
|
||||
</div>
|
||||
</div>
|
||||
<dl v-if="cardBodyCols.length" class="x-card__body">
|
||||
<div v-for="col in cardBodyCols" :key="col.key" class="x-card__row">
|
||||
<dt>{{ col.title }}</dt>
|
||||
<dd>
|
||||
<slot v-if="col.slot" :name="col.slot" :text="cellValue(record, col)" :record="record" />
|
||||
<template v-else>{{ displayValue(record, col) }}</template>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<div v-if="!model._hidePagination && query.total > 0" class="x-table__card-pager">
|
||||
<span class="x-table__card-total">{{ query.total }} مورد</span>
|
||||
<a-pagination
|
||||
size="small"
|
||||
show-less-items
|
||||
:current="query.page"
|
||||
:page-size="query.limit"
|
||||
:total="query.total"
|
||||
:show-size-changer="false"
|
||||
@change="onCardPage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
v-else
|
||||
:columns="antColumns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -117,7 +174,8 @@
|
||||
:locale="locale"
|
||||
:row-selection="rowSelection"
|
||||
row-key="id"
|
||||
size="middle"
|
||||
:size="layout.isMobile ? 'small' : 'middle'"
|
||||
:scroll="layout.isSmallScreen ? { x: 'max-content' } : undefined"
|
||||
bordered
|
||||
@change="onTableChange"
|
||||
>
|
||||
@@ -140,8 +198,9 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { FilterOutlined, PrinterOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
||||
import api from '../../api/client';
|
||||
import type { XTbl } from './model';
|
||||
import type { XTbl, Xtc } from './model';
|
||||
import { gridDate } from '../../utils/filters';
|
||||
import { useLayoutStore } from '../../stores/layout';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -150,10 +209,16 @@ const props = withDefaults(
|
||||
hasSelection?: boolean;
|
||||
/** legacy used v-model for the selected rows */
|
||||
modelValue?: Record<string, unknown>[];
|
||||
/** On phones, render each row as a card instead of a wide table. Opt-in per page. */
|
||||
mobileCards?: boolean;
|
||||
}>(),
|
||||
{ hasSelection: false, modelValue: () => [] },
|
||||
{ hasSelection: false, modelValue: () => [], mobileCards: false },
|
||||
);
|
||||
|
||||
const layout = useLayoutStore();
|
||||
// row selection needs the table's checkbox column, so it always keeps the table
|
||||
const cardMode = computed(() => props.mobileCards && !props.hasSelection && layout.isMobile);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'query', q: unknown): void;
|
||||
(e: 'update:modelValue', rows: Record<string, unknown>[]): void;
|
||||
@@ -234,6 +299,84 @@ function slotNameFor(column: { __slot?: string }) {
|
||||
return column.__slot;
|
||||
}
|
||||
|
||||
// ── phone card view ──────────────────────────────────────────────────
|
||||
interface CardCol {
|
||||
key: string;
|
||||
title: string;
|
||||
path: string[];
|
||||
slot?: string;
|
||||
date: boolean;
|
||||
}
|
||||
|
||||
const ACTION_KEYS = ['opt', 'op', 'action'];
|
||||
|
||||
const cardCols = computed<CardCol[]>(() =>
|
||||
visibleCols.value.map((c: Xtc) => ({
|
||||
key: c.key,
|
||||
title: c.title,
|
||||
path: c.dataIndex.split('.'),
|
||||
slot: c.scopedSlots?.customRender,
|
||||
date: c.filterType === 'date',
|
||||
})),
|
||||
);
|
||||
const cardActionCol = computed(() => cardCols.value.find((c) => ACTION_KEYS.includes(c.key) && c.slot));
|
||||
const cardIdCol = computed(() => cardCols.value.find((c) => c.key === 'id'));
|
||||
const cardTitleCols = computed<CardCol[]>(() => {
|
||||
const rest = cardCols.value.filter((c) => c !== cardActionCol.value && c !== cardIdCol.value);
|
||||
const named = rest.find((c) => c.key === 'name' || c.key === 'title');
|
||||
if (named) return [named];
|
||||
const fname = rest.find((c) => c.key === 'fname');
|
||||
if (fname) return [fname, ...rest.filter((c) => c.key === 'lname')];
|
||||
return rest.slice(0, 1);
|
||||
});
|
||||
const cardBodyCols = computed(() =>
|
||||
cardCols.value.filter(
|
||||
(c) => c !== cardActionCol.value && c !== cardIdCol.value && !cardTitleCols.value.includes(c),
|
||||
),
|
||||
);
|
||||
|
||||
function cellValue(record: Record<string, unknown>, col: CardCol): unknown {
|
||||
return col.path.reduce<unknown>(
|
||||
(v, k) => (v && typeof v === 'object' ? (v as Record<string, unknown>)[k] : undefined),
|
||||
record,
|
||||
);
|
||||
}
|
||||
|
||||
function displayValue(record: Record<string, unknown>, col: CardCol) {
|
||||
const v = cellValue(record, col);
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
return col.date ? gridDate(v) : v;
|
||||
}
|
||||
|
||||
const sortOptions = computed(() =>
|
||||
visibleCols.value
|
||||
.filter((c) => c.__sorter)
|
||||
.flatMap((c) => [
|
||||
{ value: `${c.key}|asc`, label: `${c.title} ↑` },
|
||||
{ value: `${c.key}|desc`, label: `${c.title} ↓` },
|
||||
]),
|
||||
);
|
||||
const sortValue = computed(() => (sort.value ? `${sort.value}|${sortDirection.value}` : undefined));
|
||||
|
||||
function onSortSelect(value?: string) {
|
||||
if (value) {
|
||||
const [key, dir] = value.split('|');
|
||||
sort.value = key;
|
||||
sortDirection.value = dir as 'asc' | 'desc';
|
||||
} else {
|
||||
sort.value = undefined;
|
||||
}
|
||||
query.page = 1;
|
||||
fetch();
|
||||
}
|
||||
|
||||
function onCardPage(page: number, pageSize: number) {
|
||||
query.page = page;
|
||||
query.limit = pageSize;
|
||||
fetch();
|
||||
document.getElementById('content-area')?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
/** Resolves a filter row's column back to its Xtc definition. */
|
||||
function colForField(field?: string) {
|
||||
if (!field) return undefined;
|
||||
@@ -401,4 +544,147 @@ defineExpose({ fetch });
|
||||
.refresh-btn {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.x-table__search {
|
||||
max-width: 260px;
|
||||
}
|
||||
.x-table__sort {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
/* ── phone card view ── */
|
||||
.x-table__cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.x-table__cards :deep(.ant-spin-container) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-height: 80px;
|
||||
}
|
||||
.x-card {
|
||||
border: 1px solid #eef0f3;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.x-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: #fafbfc;
|
||||
border-bottom: 1px solid #eef0f3;
|
||||
}
|
||||
.x-card__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.x-card__id {
|
||||
flex-shrink: 0;
|
||||
font-size: 11px;
|
||||
color: #8a909c;
|
||||
background: #eef0f3;
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
direction: ltr;
|
||||
}
|
||||
.x-card__name {
|
||||
font-weight: 600;
|
||||
color: #1c2d56;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.x-card__actions {
|
||||
flex-shrink: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.x-card__body {
|
||||
margin: 0;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
.x-card__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 7px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.x-card__row + .x-card__row {
|
||||
border-top: 1px dashed #f0f0f0;
|
||||
}
|
||||
.x-card__row dt {
|
||||
color: #8a909c;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.x-card__row dd {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
min-width: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
.x-card__row dd :deep(.ant-tag) {
|
||||
margin: 0;
|
||||
}
|
||||
.x-table__card-pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.x-table__card-total {
|
||||
font-size: 12px;
|
||||
color: #8a909c;
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.x-table__toolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* flatten so search takes row one and actions + add share row two */
|
||||
.x-table__search-group {
|
||||
display: contents;
|
||||
}
|
||||
.x-table__search {
|
||||
max-width: none;
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.x-table__actions {
|
||||
gap: 2px;
|
||||
}
|
||||
.x-table__sort {
|
||||
width: 130px;
|
||||
}
|
||||
.x-table__add {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
.x-table__filters {
|
||||
padding: 10px;
|
||||
}
|
||||
.x-table__filter-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #e8e8e8;
|
||||
}
|
||||
/* the filter inputs carry fixed inline widths for desktop */
|
||||
.x-table__filter-row > * {
|
||||
width: 100% !important;
|
||||
}
|
||||
.x-table__filter-row > :nth-child(n + 3) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.x-table__filter-actions > * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
-->
|
||||
<div id="page-user-view">
|
||||
<div id="user-data">
|
||||
<a-card class="mb-base">
|
||||
<a-card class="mb-base page-card">
|
||||
<a-tabs v-if="hasPermission('persons.create')">
|
||||
<a-tab-pane v-if="hasPermission('persons.create.client')" key="omde" tab="مشتری عمده">
|
||||
<BulkCustomer />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
a 19-line wrapper whose entire template is <Customer /> (bulkCustomer).
|
||||
bulkCustomer reads the :id route param to switch between create and edit.
|
||||
-->
|
||||
<BulkCustomer />
|
||||
<BulkCustomer class="page-card" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Ported from legacy henza_admin/src/views/apps/customer/EditReagent.vue — a
|
||||
19-line wrapper over <ReagentCustomer />, which reads the :id route param.
|
||||
-->
|
||||
<ReagentCustomer />
|
||||
<ReagentCustomer class="page-card" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
— a 16-line wrapper over <Customer /> (singleCustomer), which reads the :id
|
||||
route param to switch between create and edit.
|
||||
-->
|
||||
<SingleCustomerForm />
|
||||
<SingleCustomerForm class="page-card" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
</a-form-item>
|
||||
</a-card>
|
||||
|
||||
<a-button type="primary" @click="handleSubmit">ثبت اطلاعات</a-button>
|
||||
<a-button type="primary" class="form-submit" @click="handleSubmit">ثبت اطلاعات</a-button>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
@@ -295,4 +295,10 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
/* single-column grid on phones: a 2-row span would only leave a gap */
|
||||
@media (max-width: 575px) {
|
||||
.avatar-cell {
|
||||
grid-row: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
</a-card>
|
||||
|
||||
<div>
|
||||
<a-button type="primary" class="mr-4" @click="handleSubmitOmde">ثبت اطلاعات</a-button>
|
||||
<a-button type="primary" class="mr-4 form-submit" @click="handleSubmitOmde">ثبت اطلاعات</a-button>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-form>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-button type="primary" @click="handleSubmit">ثبت اطلاعات</a-button>
|
||||
<a-button type="primary" class="form-submit" @click="handleSubmit">ثبت اطلاعات</a-button>
|
||||
</a-card>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
- the retail screen adds a wallet panel (legacy single/wallet/{id})
|
||||
-->
|
||||
<a-spin :spinning="loading">
|
||||
<a-card :title="title">
|
||||
<a-descriptions bordered size="small" :column="3">
|
||||
<a-card :title="title" class="page-card">
|
||||
<a-descriptions bordered size="small" :column="{ xxl: 3, xl: 3, lg: 3, md: 2, sm: 2, xs: 1 }">
|
||||
<a-descriptions-item label="نام">{{ customer?.name ?? '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="موبایل">{{ customer?.mobile ?? '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="کد ملی">
|
||||
@@ -47,7 +47,7 @@
|
||||
<!-- D27 — link and balance in the accounting software. -->
|
||||
<CustomerAccountingCard :customer-id="id" />
|
||||
|
||||
<a-tabs class="mt-4" :default-active-key="defaultTab">
|
||||
<a-tabs class="mt-4 detail-tabs" :default-active-key="defaultTab">
|
||||
<a-tab-pane key="orders" tab="سفارش ها">
|
||||
<a-table
|
||||
size="small"
|
||||
@@ -208,9 +208,28 @@ onMounted(async () => {
|
||||
<style scoped>
|
||||
.stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.mt-4 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.stats :deep(.ant-tag) {
|
||||
margin: 0;
|
||||
}
|
||||
.detail-tabs :deep(.ant-tabs-nav) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.detail-tabs :deep(.ant-table-content) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.detail-tabs :deep(.ant-table-content > table) {
|
||||
min-width: max-content;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
the implementation lives once, parameterised by props.
|
||||
-->
|
||||
<div id="page-user-list">
|
||||
<a-card :title="title">
|
||||
<a-card :title="title" class="page-card">
|
||||
<template v-if="accountingSync" #extra>
|
||||
<AccountingSyncButton endpoint="admin/customers-omde/accounting-sync" @done="tbl?.fetch()" />
|
||||
</template>
|
||||
<div v-if="query && query.count && query.total">
|
||||
<div v-if="query && query.count && query.total" class="list-count">
|
||||
{{ query.total }} رکورد یافت شد -
|
||||
<span class="text-danger">{{ query.count }} رکورد غیر فعال</span>
|
||||
</div>
|
||||
|
||||
<XTable ref="tbl" :model="model" @query="setCount">
|
||||
<XTable ref="tbl" :model="model" mobile-cards @query="setCount">
|
||||
<template v-if="showAddButton" #add>
|
||||
<ListPageHeader rid="persons.create.client" add-label="افزودن مشتری عمده" @add="openAdd" />
|
||||
</template>
|
||||
@@ -503,3 +503,12 @@ function viewCash(record: Record<string, any>) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@media (max-width: 575px) {
|
||||
.list-count {
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
those customers' orders instead.
|
||||
-->
|
||||
<a-spin :spinning="loading">
|
||||
<a-card title="مشاهده اطلاعات کارشناس فروش">
|
||||
<a-descriptions bordered size="small" :column="3">
|
||||
<a-card title="مشاهده اطلاعات کارشناس فروش" class="page-card">
|
||||
<a-descriptions bordered size="small" :column="{ xxl: 3, xl: 3, lg: 3, md: 2, sm: 2, xs: 1 }">
|
||||
<a-descriptions-item label="نام">{{ agent?.name ?? '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="موبایل">{{ agent?.mobile ?? '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="شعبه">{{ agent?.branch?.name ?? '-' }}</a-descriptions-item>
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-tabs class="mt-4" default-active-key="referred">
|
||||
<a-tabs class="mt-4 detail-tabs" default-active-key="referred">
|
||||
<a-tab-pane key="referred" tab="مشتریان معرفیشده">
|
||||
<a-table
|
||||
size="small"
|
||||
@@ -118,9 +118,28 @@ onMounted(async () => {
|
||||
<style scoped>
|
||||
.stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.mt-4 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.stats :deep(.ant-tag) {
|
||||
margin: 0;
|
||||
}
|
||||
.detail-tabs :deep(.ant-tabs-nav) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.detail-tabs :deep(.ant-table-content) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.detail-tabs :deep(.ant-table-content > table) {
|
||||
min-width: max-content;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
endpoints it used to hit (which would 404/target the wrong row entirely).
|
||||
-->
|
||||
<div id="page-user-list">
|
||||
<a-card title="لیست کارشناسان فروش">
|
||||
<XTable ref="tbl" :model="model">
|
||||
<a-card title="لیست کارشناسان فروش" class="page-card">
|
||||
<XTable ref="tbl" :model="model" mobile-cards>
|
||||
<template #add>
|
||||
<ListPageHeader
|
||||
v-if="hasPermission('persons.create.seller')"
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
including legacy's quirk that the "مشاهده اطلاعات" icon is gated on the
|
||||
`wallet.charge` rid rather than a view rid.
|
||||
-->
|
||||
<a-card :title="is_branch ? 'لیست مشتریان' : 'لیست مشتریان تک فروشی'">
|
||||
<XTable ref="tbl" :model="model">
|
||||
<a-card :title="is_branch ? 'لیست مشتریان' : 'لیست مشتریان تک فروشی'" class="page-card">
|
||||
<XTable ref="tbl" :model="model" mobile-cards>
|
||||
<template #op="{ record }">
|
||||
<a-tooltip title="ویرایش">
|
||||
<EditOutlined class="cursor-pointer mx-1 text-primary" @click="toEdit(record.id)" />
|
||||
|
||||
Reference in New Issue
Block a user