feat(admin-panel): responsive branches, workshops, products and orders

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-13 09:14:28 +03:30
co-authored by Cursor
parent 0a2466ca0c
commit c1b3e90b9b
13 changed files with 194 additions and 39 deletions
@@ -124,9 +124,31 @@
<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">
<a-checkbox
v-if="hasSelection && data.length"
class="x-table__select-all"
:checked="allOnPageSelected"
:indeterminate="someOnPageSelected && !allOnPageSelected"
@change="(e: any) => selectPage(e.target.checked)"
>
انتخاب همه در این صفحه
</a-checkbox>
<div
v-for="record in data"
:key="String(record.id)"
class="x-card"
:class="{ 'x-card--selected': hasSelection && isSelected(record) }"
>
<div class="x-card__head">
<div class="x-card__title">
<a-checkbox
v-if="hasSelection"
:checked="isSelected(record)"
@change="(e: any) => toggleRow(record, e.target.checked)"
/>
<span v-if="cardMediaCol" class="x-card__media">
<slot :name="cardMediaCol.slot" :text="cellValue(record, cardMediaCol)" :record="record" />
</span>
<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">
@@ -216,8 +238,7 @@ const props = withDefaults(
);
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 cardMode = computed(() => props.mobileCards && layout.isMobile);
const emit = defineEmits<{
(e: 'query', q: unknown): void;
@@ -321,8 +342,11 @@ const cardCols = computed<CardCol[]>(() =>
);
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 cardMediaCol = computed(() => cardCols.value.find((c) => c.slot === 'image'));
const cardTitleCols = computed<CardCol[]>(() => {
const rest = cardCols.value.filter((c) => c !== cardActionCol.value && c !== cardIdCol.value);
const rest = cardCols.value.filter(
(c) => c !== cardActionCol.value && c !== cardIdCol.value && c !== cardMediaCol.value,
);
const named = rest.find((c) => c.key === 'name' || c.key === 'title');
if (named) return [named];
const fname = rest.find((c) => c.key === 'fname');
@@ -331,10 +355,30 @@ const cardTitleCols = computed<CardCol[]>(() => {
});
const cardBodyCols = computed(() =>
cardCols.value.filter(
(c) => c !== cardActionCol.value && c !== cardIdCol.value && !cardTitleCols.value.includes(c),
(c) =>
c !== cardActionCol.value &&
c !== cardIdCol.value &&
c !== cardMediaCol.value &&
!cardTitleCols.value.includes(c),
),
);
// card-mode selection, same v-model contract as the table's rowSelection
function isSelected(record: Record<string, unknown>) {
return props.modelValue.some((r) => r.id === record.id);
}
function toggleRow(record: Record<string, unknown>, checked: boolean) {
const rest = props.modelValue.filter((r) => r.id !== record.id);
emit('update:modelValue', checked ? [...rest, record] : rest);
}
const allOnPageSelected = computed(() => data.value.length > 0 && data.value.every(isSelected));
const someOnPageSelected = computed(() => data.value.some(isSelected));
function selectPage(checked: boolean) {
const pageIds = new Set(data.value.map((r) => r.id));
const rest = props.modelValue.filter((r) => !pageIds.has(r.id));
emit('update:modelValue', checked ? [...rest, ...data.value] : rest);
}
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),
@@ -604,6 +648,26 @@ defineExpose({ fetch });
flex-shrink: 0;
font-size: 16px;
}
.x-card--selected {
border-color: #91caff;
}
.x-card--selected .x-card__head {
background: #f0f7ff;
}
.x-card__media {
flex-shrink: 0;
display: inline-flex;
}
.x-card__media :deep(img) {
width: 44px !important;
height: 44px !important;
object-fit: cover !important;
border-radius: 6px;
}
.x-table__select-all {
font-size: 12px;
color: #8a909c;
}
.x-card__body {
margin: 0;
padding: 4px 12px;