changes of UI

This commit is contained in:
2026-09-20 18:10:33 +03:30
parent 0d113611cb
commit c0c2221d91
61 changed files with 261 additions and 120 deletions
+6 -18
View File
@@ -78,7 +78,7 @@ function openSeason(id: string) {
<section>
<h2 class="block-title">انتخاب بر اساس کاربرد</h2>
<p class="block-sub">دسته موردنظر خود را انتخاب کنید</p>
<div class="usage-grid scroll-x">
<div class="usage-grid">
<button
v-for="item in usageCategories"
:key="item.id"
@@ -238,22 +238,17 @@ function openSeason(id: string) {
}
.usage-grid {
display: flex;
flex-wrap: nowrap;
gap: 8px;
margin-inline: calc(-1 * var(--page-padding));
padding-inline: var(--page-padding);
padding-bottom: 4px;
scroll-snap-type: x proximity;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.usage-card {
display: flex;
flex-direction: column;
align-items: stretch;
flex: 0 0 148px;
width: 148px;
scroll-snap-align: start;
width: 100%;
min-width: 0;
appearance: none;
-webkit-appearance: none;
border: none;
@@ -538,13 +533,6 @@ function openSeason(id: string) {
.usage-grid {
gap: 16px;
margin-inline: 0;
padding-inline: 0;
}
.usage-card {
flex-basis: 168px;
width: 168px;
}
.usage-card__body strong {
+4 -3
View File
@@ -9,9 +9,10 @@ import { fetchMySuggestions, type ProductSuggestionDto } from '@/services/sugges
import IconBell from '@/components/icons/IconBell.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import levelMedalIcon from '@/assets/images/level-medal.webp'
import quickClubCrownIcon from '@/assets/images/quick-club-crown.webp'
// import quickClubCrownIcon from '@/assets/images/quick-club-crown.webp'
import quickCategoriesGridIcon from '@/assets/images/quick-categories-grid.webp'
import quickCatalogRequestIcon from '@/assets/images/quick-catalog-request.webp'
import latestOrderStatus from '@/assets/images/latest-order-status.webp'
import quickNewProductsIcon from '@/assets/images/quick-new-products.webp'
import IconHeart from '@/components/icons/IconHeart.vue'
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
@@ -51,14 +52,14 @@ type QuickAction = {
const customerActions: QuickAction[] = [
{ id: 'qa-new', label: 'محصولات جدید', to: '/products', image: quickNewProductsIcon },
{ id: 'qa-cat', label: 'دسته بندی', to: '/categories', image: quickCategoriesGridIcon },
{ id: 'qa-club', label: 'آخرین وضعیت سفارش', to: '/orders/latest-status', image:'/' },
{ id: 'qa-club', label: 'آخرین وضعیت سفارش', to: '/orders/latest-status', image:latestOrderStatus },
{ id: 'qa-swatch', label: 'آخرین درخواست کالیته', to: '/catalog-requests/latest-status', image: quickCatalogRequestIcon },
]
const agentActions: QuickAction[] = [
{ id: 'qa-new', label: 'محصولات جدید', to: '/products', image: quickNewProductsIcon },
{ id: 'qa-cat', label: 'دسته بندی', to: '/categories', image: quickCategoriesGridIcon },
{ id: 'qa-customers', label: 'آخرین وضعیت سفارش', to: '/orders/latest-status', image: '/' },
{ id: 'qa-customers', label: 'آخرین وضعیت سفارش', to: '/orders/latest-status', image: latestOrderStatus },
{ id: 'qa-swatch', label: 'آخرین درخواست کالیته', to: '/catalog-requests/latest-status', image: quickCatalogRequestIcon },
]
+168 -38
View File
@@ -6,9 +6,17 @@ import PageSkeleton from '@/components/ui/PageSkeleton.vue'
import { useFavoritesStore } from '@/stores/favorites'
import { useCrmStore } from '@/stores/crm'
import type { CatalogProductCard } from '@/types/crm'
import IconHeart from '@/components/icons/IconHeart.vue'
import { formatNumber } from '@/utils/format'
import { colorImageAt, colorSwatchStyle } from '@/utils/colorSwatch'
import IconBookmark from '@/components/icons/IconBookmark.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
const SEASON_LABEL: Record<string, string> = {
summer: 'تابستانه',
winter: 'زمستانه',
all_season: 'چهارفصل',
}
const router = useRouter()
const favorites = useFavoritesStore()
const crm = useCrmStore()
@@ -30,6 +38,18 @@ onMounted(async () => {
}
})
function seasonLabel(season: string) {
return SEASON_LABEL[season] ?? '—'
}
function productImage(item: CatalogProductCard) {
return colorImageAt(item.colors, item.colorImages, item.imageUrls, 0, item.imageUrl)
}
function openProduct(id: string) {
router.push({ path: '/product', query: { id } })
}
async function remove(id: string) {
await favorites.toggle(id)
products.value = products.value.filter((p) => p.id !== id)
@@ -52,22 +72,50 @@ async function remove(id: string) {
<article
v-for="item in products"
:key="item.id"
class="row"
@click="router.push({ path: '/product', query: { id: item.id } })"
class="product-item"
@click="openProduct(item.id)"
>
<div class="row__body">
<strong>{{ item.name }}</strong>
<span>کد {{ item.code }}</span>
<div
class="product-item__image"
:style="
productImage(item)
? { backgroundImage: `url(${productImage(item)})` }
: colorSwatchStyle(item.colors[0] ?? '')
"
/>
<div class="product-item__body">
<div class="product-item__head">
<div class="product-item__title">
<strong>{{ item.name }}</strong>
<span v-if="item.isNew" class="new-tag">جدید</span>
</div>
<button
type="button"
class="bookmark"
aria-label="حذف از علاقه‌مندی‌ها"
@click.stop="remove(item.id)"
>
<IconBookmark :size="18" filled color="var(--color-primary)" />
</button>
</div>
<ul class="product-item__specs">
<li>عرض: {{ formatNumber(item.widthCm) }} سانتی‌متر</li>
<li>ترکیب: {{ item.composition || '—' }}</li>
<li>مناسب: {{ seasonLabel(item.season) }}</li>
</ul>
<div v-if="item.colors.length" class="swatches">
<span
v-for="(color, idx) in item.colors.slice(0, 6)"
:key="idx"
class="swatch"
:style="colorSwatchStyle(color, item.colorImages?.[idx] || item.imageUrls?.[idx])"
/>
</div>
<button type="button" class="details-link" @click.stop="openProduct(item.id)">
مشاهده جزئیات
<IconChevronLeft :size="14" />
</button>
</div>
<button
type="button"
class="bookmark"
aria-label="حذف از نشان‌شده‌ها"
@click.stop="remove(item.id)"
>
<IconHeart :size="18" filled color="#0a353c" />
</button>
<IconChevronLeft class="row__chevron" :size="16" color="#9aa3a0" />
</article>
</div>
</div>
@@ -78,47 +126,129 @@ async function remove(id: string) {
padding: 12px var(--page-padding) 24px;
display: flex;
flex-direction: column;
gap: 10px;
gap: 12px;
}
.empty {
text-align: center;
color: var(--color-text-muted);
padding: 32px 0;
}
.row {
display: flex;
align-items: center;
gap: 8px;
.product-item {
display: grid;
grid-template-columns: 112px 1fr;
align-items: stretch;
gap: 12px;
background: var(--color-surface);
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 12px;
box-shadow: var(--shadow-sm);
cursor: pointer;
}
.row__body {
flex: 1;
.product-item__image {
border-radius: 12px;
background:
linear-gradient(145deg, #2a3544, #111827);
background-size: cover;
background-position: center;
min-height: 112px;
}
.product-item__body {
display: flex;
flex-direction: column;
min-width: 0;
gap: 6px;
}
.row__body strong {
display: block;
.product-item__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
}
.row__body span {
color: var(--color-text-muted);
font-size: 0.8rem;
.product-item__title {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
flex-wrap: wrap;
}
.product-item__title strong {
font-size: 0.9375rem;
font-weight: 700;
line-height: 1.4;
color: var(--color-text);
}
.new-tag {
background: var(--color-primary);
color: #fff;
font-size: 0.65rem;
font-weight: 700;
padding: 2px 8px;
border-radius: 6px;
white-space: nowrap;
}
.bookmark {
width: 28px;
height: 28px;
margin-inline-start: auto;
border: none;
background: transparent;
cursor: pointer;
width: 36px;
height: 36px;
padding: 0;
display: grid;
place-items: center;
cursor: pointer;
flex-shrink: 0;
}
.product-item__specs {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 2px;
color: var(--color-text-muted);
font-size: 0.75rem;
line-height: 1.55;
text-align: start;
}
.swatches {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 2px;
}
.swatch {
width: 14px;
height: 14px;
border-radius: 999px;
border: 1px solid rgba(0, 0, 0, 0.08);
background-size: cover;
background-position: center;
}
.details-link {
margin-top: auto;
align-self: flex-end;
border: none;
background: transparent;
color: var(--color-info);
display: inline-flex;
align-items: center;
gap: 2px;
padding: 0;
font-size: 0.75rem;
cursor: pointer;
}
@media (min-width: 768px) {
@@ -133,7 +263,7 @@ async function remove(id: string) {
padding: 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
gap: 16px;
}
.empty {
@@ -141,13 +271,13 @@ async function remove(id: string) {
padding: 48px 0;
}
.row {
box-shadow: var(--shadow-card);
border: 1px solid var(--color-border);
.product-item {
grid-template-columns: 120px 1fr;
min-height: 140px;
}
.row__chevron {
display: none;
.product-item__image {
min-height: 116px;
}
}
@@ -13,6 +13,7 @@ import IconBell from '@/components/icons/IconBell.vue'
import IconSettings from '@/components/icons/IconSettings.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import IconCart from '@/components/icons/IconCart.vue'
import IconHeart from '@/components/icons/IconHeart.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import PageSkeleton from '@/components/ui/PageSkeleton.vue'
import UserAvatar from '@/components/ui/UserAvatar.vue'
@@ -41,21 +42,31 @@ const profile = ref<AgentProfileDto | null>(null)
const sampleProduct = ref<CatalogProductCard>(SAMPLE_PRODUCT)
const loading = ref(false)
const agentQuickActions = [
{ label: 'ثبت مشتری جدید', to: '/customers', icon: 'vuesax-linear:add' },
{ label: 'سفارش‌ها', to: '/orders', icon: 'vuesax-linear:document-text' },
{ label: 'مشتریان من', to: '/customers', icon: 'vuesax-linear:people' },
{ label: 'گزارش‌ها', to: '/reminders', icon: 'vuesax-linear:chart-2' },
{ label: 'درخواست کالیته', to: '/categories', icon: 'vuesax-linear:category' },
{ label: 'قیمت‌گذاری', to: '/categories', icon: 'vuesax-linear:money-4' },
] as const
type QuickAction = {
id: string
label: string
to: string
icon?: string
useHeart?: boolean
}
const customerQuickActions = [
{ label: 'سفارش‌ها', to: '/orders', icon: 'vuesax-linear:document-text' },
{ label: 'دسته‌بندی', to: '/categories', icon: 'vuesax-linear:category-2' },
{ label: 'درخواست کالیته', to: '/categories', icon: 'vuesax-linear:category' },
{ label: 'علاقه‌مندی‌ها', to: '/favorites', icon: 'vuesax-linear:heart' },
] as const
const agentQuickActions: QuickAction[] = [
{ id: 'add-customer', label: 'ثبت مشتری جدید', to: '/customers', icon: 'vuesax-linear:add' },
{ id: 'orders', label: 'سفارش‌ها', to: '/orders', icon: 'vuesax-linear:document-text' },
{ id: 'customers', label: 'مشتریان من', to: '/customers', icon: 'vuesax-linear:people' },
{ id: 'reports', label: 'گزارش‌ها', to: '/reminders', icon: 'vuesax-linear:chart-2' },
{ id: 'catalog', label: 'درخواست کالیته', to: '/categories', icon: 'vuesax-linear:box-1' },
{ id: 'pricing', label: 'قیمت‌گذاری', to: '/categories', icon: 'vuesax-linear:money-4' },
]
const customerQuickActions: QuickAction[] = [
{ id: 'orders', label: 'سفارش‌ها', to: '/orders', icon: 'vuesax-linear:document-text' },
{ id: 'categories', label: 'دسته‌بندی', to: '/categories', icon: 'vuesax-linear:element-3' },
{ id: 'products', label: 'محصولات', to: '/products', icon: 'vuesax-linear:box-search' },
{ id: 'favorites', label: 'علاقه‌مندی‌ها', to: '/favorites', useHeart: true },
{ id: 'order-status', label: 'وضعیت سفارش', to: '/orders/latest-status', icon: 'vuesax-linear:timer-1' },
{ id: 'catalog', label: 'درخواست کالیته', to: '/categories', icon: 'vuesax-linear:box-1' },
]
const quickActions = computed(() =>
auth.isAgent ? agentQuickActions : customerQuickActions,
@@ -281,13 +292,23 @@ onMounted(async () => {
<div class="quick-grid">
<button
v-for="item in quickActions"
:key="item.label"
:key="item.id"
type="button"
class="quick-item"
@click="router.push(item.to)"
>
<span class="quick-item__icon">
<BasicIcon :icon="item.icon" :size="22" :stroke-width="1.5" />
<IconHeart
v-if="item.useHeart"
:size="22"
:color="'var(--color-primary)'"
/>
<BasicIcon
v-else-if="item.icon"
:icon="item.icon"
:size="22"
:stroke-width="1.5"
/>
</span>
{{ item.label }}
</button>