-
محصولات و تعداد اقلام موردنظر را مشخص کنید.
+
+ {{ preselectId ? 'این محصول برای درخواست کالیته انتخاب شده است. در صورت نیاز اقلام دیگر را هم اضافه کنید.' : 'محصولات و تعداد اقلام موردنظر را مشخص کنید.' }}
+
{{ error }}
-
+
{{ item.name }}
کد {{ item.code }}
@@ -141,6 +151,10 @@ async function submit() {
box-shadow: var(--shadow-sm);
}
+.row--selected {
+ outline: 2px solid var(--color-primary);
+}
+
.row strong {
display: block;
font-size: 0.9rem;
diff --git a/src/views/catalogs/CatalogsHubView.vue b/src/views/catalogs/CatalogsHubView.vue
index 7dd06dd..22b07e8 100644
--- a/src/views/catalogs/CatalogsHubView.vue
+++ b/src/views/catalogs/CatalogsHubView.vue
@@ -36,7 +36,8 @@ onMounted(async () => {
function openCatalog(item: Catalog) {
router.push({
- path: `/companies/${item.companyId}/catalogs/${item.id}/products`,
+ path: '/products',
+ query: { catalogId: item.id, companyId: item.companyId },
})
}
@@ -61,9 +62,9 @@ function companyName(companyId: string) {
مشاهده دستهبندیها
انتخاب بر اساس کاربرد و فصل
-
- کالیتههای برندها
- مسیر انتخاب شرکت و کالیته
+
+ همه محصولات
+ مشاهده کاتالوگ و ثبت سفارش
diff --git a/src/views/categories/CategoriesView.vue b/src/views/categories/CategoriesView.vue
index c096134..f4f2d9f 100644
--- a/src/views/categories/CategoriesView.vue
+++ b/src/views/categories/CategoriesView.vue
@@ -95,6 +95,7 @@ function openSeason(id: string) {
>
@@ -123,10 +124,6 @@ function openSeason(id: string) {
class="season-card"
@click="openSeason(item.id)"
>
-
@@ -140,6 +137,11 @@ function openSeason(id: string) {
+
@@ -261,10 +263,22 @@ function openSeason(id: string) {
.usage-card__image {
position: relative;
- height: 72px;
+ height: 96px;
background: linear-gradient(140deg, #6b7280, #1f2937) center / cover no-repeat;
}
+.usage-card__image--suit {
+ background: linear-gradient(150deg, #2c3038, #111318) center / cover no-repeat;
+}
+
+.usage-card__image--shirt {
+ background: linear-gradient(150deg, #e8eef3, #9aa8b5) center / cover no-repeat;
+}
+
+.usage-card__image--trousers {
+ background: linear-gradient(150deg, #1a1d24, #0b0d12) center / cover no-repeat;
+}
+
.usage-card__icon {
position: absolute;
left: 50%;
@@ -308,7 +322,7 @@ function openSeason(id: string) {
.season-card {
display: grid;
- grid-template-columns: 110px 1fr;
+ grid-template-columns: 1fr 42%;
border: none;
background: var(--color-surface);
border-radius: var(--radius-lg);
@@ -320,10 +334,22 @@ function openSeason(id: string) {
}
.season-card__image {
- min-height: 100px;
+ min-height: 124px;
background: linear-gradient(160deg, #d6c3a8, #8b7355) center / cover no-repeat;
}
+.season-card__image--summer {
+ background: linear-gradient(160deg, #f3e6c8, #c4a574) center / cover no-repeat;
+}
+
+.season-card__image--winter {
+ background: linear-gradient(160deg, #cfd6de, #7d8b99) center / cover no-repeat;
+}
+
+.season-card__image--all_season {
+ background: linear-gradient(160deg, #efe6d6, #b9a48a) center / cover no-repeat;
+}
+
.season-card__body {
padding: 12px;
}
diff --git a/src/views/checkout/CheckListView.vue b/src/views/checkout/CheckListView.vue
index 15e3337..cde17fb 100644
--- a/src/views/checkout/CheckListView.vue
+++ b/src/views/checkout/CheckListView.vue
@@ -140,7 +140,7 @@ async function submitChecks() {
ثبت چک جدید
- ثبت سفارش جدید
+ ثبت سفارش جدید
diff --git a/src/views/products/ProductDetailView.vue b/src/views/products/ProductDetailView.vue
index cf7ded4..917b642 100644
--- a/src/views/products/ProductDetailView.vue
+++ b/src/views/products/ProductDetailView.vue
@@ -7,14 +7,31 @@ import { useCartStore } from '@/stores/cart'
import { useCatalogStore } from '@/stores/catalog'
import type { CatalogProductCard } from '@/types/crm'
import type { Product } from '@/types'
-import { formatMeterage, formatNumber } from '@/utils/format'
+import { formatNumber } from '@/utils/format'
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
import IconCart from '@/components/icons/IconCart.vue'
import IconCatalog from '@/components/icons/IconCatalog.vue'
import IconHeart from '@/components/icons/IconHeart.vue'
-import IconMail from '@/components/icons/IconMail.vue'
+import IconShare from '@/components/icons/IconShare.vue'
+import IconShoppingBag from '@/components/icons/IconShoppingBag.vue'
import { useGoBack } from '@/composables/useGoBack'
import PageSkeleton from '@/components/ui/PageSkeleton.vue'
+import {
+ CATALOG_SEASON_CATEGORIES,
+ CATALOG_USAGE_CATEGORIES,
+} from '@/data/catalogCategories'
+
+const USAGE_CHIP_LABEL: Record = {
+ suit: 'پارچه کتشلواری',
+ trousers: 'پارچه شلواری',
+ shirt: 'پارچه پیراهنی',
+}
+
+const SEASON_CHIP_LABEL: Record = {
+ summer: 'پارچه تابستانه',
+ winter: 'پارچه زمستانه',
+ all_season: 'پارچه چهارفصل',
+}
const route = useRoute()
const router = useRouter()
@@ -30,7 +47,6 @@ const error = ref(null)
const selectedColor = ref(0)
const rolls = ref(1)
const adding = ref(false)
-const actionMessage = ref(null)
const showAllSpecs = ref(false)
const rollMeters = computed(() => product.value?.rollMeters ?? 50)
@@ -42,6 +58,65 @@ const minOrderMeters = computed(() => {
const minRolls = computed(() =>
Math.max(1, Math.ceil(minOrderMeters.value / rollMeters.value)),
)
+const usageLabel = computed(
+ () => USAGE_CHIP_LABEL[product.value?.categoryId ?? ''] || '—',
+)
+const seasonLabel = computed(
+ () => SEASON_CHIP_LABEL[product.value?.season ?? ''] || '—',
+)
+
+const categoryChips = computed(() => {
+ const p = product.value
+ if (!p) return []
+
+ const chips: Array<{
+ key: string
+ label: string
+ to?: { path: string; query: Record }
+ }> = []
+
+ const usageId = CATALOG_USAGE_CATEGORIES.some((c) => c.id === p.categoryId)
+ ? p.categoryId
+ : ''
+ const usageLabel = usageId ? USAGE_CHIP_LABEL[usageId] : ''
+ if (usageLabel) {
+ chips.push({
+ key: `usage-${usageId}`,
+ label: usageLabel,
+ to: { path: '/products', query: { categoryId: usageId } },
+ })
+ }
+
+ const seasonId = CATALOG_SEASON_CATEGORIES.some((c) => c.id === p.season)
+ ? p.season
+ : ''
+ const seasonLabel = seasonId ? SEASON_CHIP_LABEL[seasonId] : ''
+ if (seasonLabel) {
+ chips.push({
+ key: `season-${seasonId}`,
+ label: seasonLabel,
+ to: { path: '/products', query: { season: seasonId } },
+ })
+ }
+
+ const covered = new Set(
+ chips.flatMap((c) => [c.label, c.label.replace(/^پارچه\s*/, '')]),
+ )
+ for (const tag of p.tags) {
+ const short = tag.replace(/^پارچه?های?\s*/, '').trim()
+ if (!short || covered.has(short) || chips.some((c) => c.label.includes(short))) {
+ continue
+ }
+ const label = tag.startsWith('پارچه') ? tag : `پارچه ${short}`
+ covered.add(short)
+ chips.push({
+ key: `tag-${tag}`,
+ label,
+ })
+ }
+
+ return chips
+})
async function load() {
if (!productId.value) {
@@ -51,7 +126,6 @@ async function load() {
}
loading.value = true
error.value = null
- actionMessage.value = null
try {
await Promise.all([favorites.hydrate(), catalogStore.loadCompanies()])
product.value = await crm.loadProductById(productId.value)
@@ -68,15 +142,14 @@ async function load() {
onMounted(load)
watch(productId, load)
-function toCartProduct(p: CatalogProductCard): Product | null {
- if (!p.companyId || !p.catalogId) return null
+function toCartProduct(p: CatalogProductCard): Product {
const roll = p.rollMeters || 50
const step = p.orderMultiplier && p.orderMultiplier > 0 ? p.orderMultiplier : roll
const min = p.minMeterage && p.minMeterage > 0 ? p.minMeterage : roll
return {
id: p.id,
- companyId: p.companyId,
- catalogId: p.catalogId,
+ companyId: p.companyId || '',
+ catalogId: p.catalogId || '',
name: p.name,
code: p.code,
color: p.color || p.colors[selectedColor.value] || '—',
@@ -97,32 +170,18 @@ function snapMeterage(meters: number, step: number, min: number) {
async function addToOrder() {
if (!product.value) return
const cartProduct = toCartProduct(product.value)
- if (!cartProduct) {
- actionMessage.value = 'اطلاعات شرکت/کاتالوگ محصول ناقص است.'
- return
- }
- if (!catalogStore.getCompany(cartProduct.companyId)) {
- await catalogStore.loadCompanies()
- }
adding.value = true
- actionMessage.value = null
try {
const meterage = snapMeterage(
orderMeters.value,
cartProduct.orderMultiplier,
cartProduct.minMeterage,
)
- const result = await cartStore.addItem(
+ await cartStore.addItem(
cartProduct,
product.value.catalogCode || product.value.code,
meterage,
)
- if (result.success) {
- actionMessage.value = 'به سفارش اضافه شد.'
- await router.push('/cart')
- } else {
- actionMessage.value = result.message || 'افزودن به سفارش ناموفق بود.'
- }
} finally {
adding.value = false
}
@@ -133,19 +192,15 @@ function toggleFavorite() {
void favorites.toggle(product.value.id)
}
-function adjustRolls(delta: number) {
- rolls.value = Math.max(minRolls.value, rolls.value + delta)
-}
-
function shareProduct() {
const url = window.location.href
if (navigator.share) {
void navigator.share({ title: product.value?.name, url }).catch(() => undefined)
- } else {
- void navigator.clipboard?.writeText(url)
- actionMessage.value = 'لینک محصول کپی شد.'
+ return
}
+ void navigator.clipboard?.writeText(url)
}
+
@@ -173,7 +228,7 @@ function shareProduct() {
/>
-
+
@@ -203,9 +258,10 @@ function shareProduct() {
type="button"
class="thumb"
:class="{ 'thumb--active': selectedColor === idx }"
- :style="{ background: color }"
@click="selectedColor = idx"
- />
+ >
+
+
@@ -218,36 +274,58 @@ function shareProduct() {
- عرض
- {{ formatNumber(product.widthCm) }} سانتی متر
+
+
+
+
+
+ عرض
+ {{ formatNumber(product.widthCm) }} سانتیمتر
-
جنس
+
+
+
+
+
+
جنس
{{ product.composition || '—' }}
- وزن
+
+
+
+ وزن
{{ product.weightGrams ? `${formatNumber(product.weightGrams)} گرم` : '—' }}
-
نوع بافت
+
+
+
+
+
+
+
نوع بافت
{{ product.weaveType || '—' }}
{{ showAllSpecs ? 'بستن مشخصات' : 'مشاهده همه مشخصات' }}
+
+
+
-
-
- رنگ بندی
+
+
+
رنگبندی
({{ formatNumber(product.colors.length) }} رنگ موجود)
-
+
@@ -315,7 +390,10 @@ function shareProduct() {