Files
nademitex_user/src/views/catalogs/CatalogsHubView.vue
T
2026-09-13 16:43:38 +03:30

494 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import SearchField from '@/components/ui/SearchField.vue'
import IconSearch from '@/components/icons/IconSearch.vue'
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
import AuthThemeToggle from '@/components/ui/AuthThemeToggle.vue'
import PageSkeleton from '@/components/ui/PageSkeleton.vue'
import { useCatalogStore } from '@/stores/catalog'
import { useCrmStore } from '@/stores/crm'
import { useGoBack } from '@/composables/useGoBack'
import type { Catalog } from '@/types'
import type { CatalogProductCard } from '@/types/crm'
import {
buildCatalogGallery,
filterCatalogGallery,
type CatalogGalleryFilter,
} from '@/utils/catalogGallery'
import { formatNumber } from '@/utils/format'
const route = useRoute()
const router = useRouter()
const { goBack } = useGoBack('/')
const catalogStore = useCatalogStore()
const crm = useCrmStore()
const catalogs = ref<Catalog[]>([])
const products = ref<CatalogProductCard[]>([])
const loading = ref(true)
const searchOpen = ref(false)
const search = ref('')
const filter = ref<CatalogGalleryFilter>('all')
const filters: { id: CatalogGalleryFilter; label: string }[] = [
{ id: 'all', label: 'همه' },
{ id: 'newest', label: 'جدیدترین' },
{ id: 'popular', label: 'محبوب‌ترین' },
{ id: 'season', label: 'بر اساس فصل' },
]
const gallery = computed(() => buildCatalogGallery(catalogs.value, products.value))
const cards = computed(() => filterCatalogGallery(gallery.value, filter.value, search.value))
onMounted(async () => {
try {
const companies = await catalogStore.loadCompanies()
const [lists, browse] = await Promise.all([
Promise.all(companies.map((company) => catalogStore.loadCatalogs(company.id))),
crm.loadBrowseProducts().catch(() => [] as CatalogProductCard[]),
])
catalogs.value = lists.flat()
products.value = browse
const focusId = String(route.query.id ?? '')
if (focusId && catalogs.value.some((item) => item.id === focusId)) {
router.replace({ name: 'catalog-detail', params: { catalogId: focusId } })
}
} catch {
catalogs.value = []
} finally {
loading.value = false
}
})
function openCatalog(id: string) {
router.push({ name: 'catalog-detail', params: { catalogId: id } })
}
function toggleSearch() {
searchOpen.value = !searchOpen.value
if (!searchOpen.value) search.value = ''
}
function metaLine(card: (typeof gallery.value)[number]) {
const extra =
card.season === 'winter'
? 'مناسب زمستان'
: card.season === 'summer'
? 'مناسب تابستان'
: 'مناسب چهار فصل'
return `${formatNumber(card.patternCount)} طرح • ${extra}`
}
</script>
<template>
<div class="page calite-hub">
<header class="calite-hub__nav">
<button type="button" class="icon-btn mobile-only" aria-label="بازگشت" @click="goBack">
<IconChevronBack :size="20" />
</button>
<h1>کالیته‌ها</h1>
<div class="calite-hub__nav-end">
<AuthThemeToggle variant="inline" class="mobile-only" />
<button
type="button"
class="icon-btn"
:aria-label="searchOpen ? 'بستن جستجو' : 'جستجو'"
:aria-pressed="searchOpen"
@click="toggleSearch"
>
<IconSearch :size="20" />
</button>
</div>
</header>
<div class="page__scroll page__scroll--with-nav calite-hub__scroll">
<p class="calite-hub__lead">مجموعه‌ای از بهترین پارچه‌ها، در یک نگاه</p>
<SearchField
v-if="searchOpen"
:model-value="search"
placeholder="جستجوی نام کالیته..."
@update:model-value="search = $event"
/>
<div class="calite-hub__chips" role="tablist" aria-label="مرتب‌سازی کالیته‌ها">
<button
v-for="item in filters"
:key="item.id"
type="button"
class="calite-chip"
:class="{ 'is-on': filter === item.id }"
@click="filter = item.id"
>
{{ item.label }}
</button>
</div>
<PageSkeleton v-if="loading" variant="cards" :rows="4" />
<p v-else-if="!cards.length" class="calite-hub__empty">کالیته‌ای یافت نشد.</p>
<div v-else class="calite-hub__list">
<button
v-for="card in cards"
:key="card.id"
type="button"
class="calite-card"
dir="ltr"
@click="openCatalog(card.id)"
>
<span
class="calite-card__photo"
:style="card.imageUrl ? { backgroundImage: `url(${card.imageUrl})` } : undefined"
/>
<span v-if="card.badge" class="calite-card__badge">
{{ card.badge === 'new' ? 'جدید' : 'پرفروش' }}
</span>
<div class="calite-card__copy" dir="rtl">
<svg class="calite-card__arrow" width="12" height="16" viewBox="0 0 8 12" fill="none"
aria-hidden="true">
<path
d="M1.5 1.5 6 6l-4.5 4.5"
stroke="#fff"
stroke-width="1.4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<div class="mr-4 text-white">
<div class="text-lg font-bold">{{ card.title }}</div>
<div class="text-md">{{ card.subtitle }}</div>
<div class="text-xs mt-4">{{ metaLine(card) }}</div>
</div>
</div>
</button>
</div>
</div>
</div>
</template>
<style scoped>
.calite-hub {
background: #f4f0ed;
color: #2a2622;
}
.calite-hub__nav {
display: grid;
grid-template-columns: 40px 1fr auto;
align-items: center;
padding: 6px 12px 0;
background: #f4f0ed;
}
.calite-hub__nav-end {
display: flex;
align-items: center;
}
.calite-hub__nav h1 {
margin: 0;
text-align: center;
font-size: 1.125rem;
font-weight: 700;
}
.calite-hub__scroll {
padding: 2px 16px 28px;
display: flex;
flex-direction: column;
gap: 12px;
}
.calite-hub__lead {
margin: 0;
text-align: center;
color: #8a847c;
font-size: 0.78rem;
line-height: 1.7;
}
.icon-btn {
width: 40px;
height: 40px;
border: none;
background: transparent;
color: inherit;
display: grid;
place-items: center;
cursor: pointer;
}
.calite-hub__chips {
display: flex;
gap: 8px;
padding: 2px 0 4px;
scrollbar-width: none;
}
.calite-hub__chips::-webkit-scrollbar {
display: none;
}
.calite-chip {
flex-shrink: 0;
height: 36px;
padding: 0 16px;
border: 1px solid #eae6e2;
border-radius: 999px;
background: #eae6e2;
color: #615f5c;
font: inherit;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
}
.calite-chip.is-on {
border-color: var(--color-primary);
background: var(--color-primary);
color: #fff;
}
.calite-hub__list {
display: flex;
flex-direction: column;
gap: 12px;
}
.calite-card {
position: relative;
display: block;
width: 100%;
min-height: 132px;
padding: 0;
overflow: hidden;
border: none;
border-radius: 22px;
background: #e7ddd4;
cursor: pointer;
text-align: start;
color: #2c2824;
}
.calite-card__photo {
position: absolute;
inset: 0;
z-index: 0;
background: linear-gradient(160deg, #c4b7a8, #8a7b6c);
background-size: cover;
background-position: center;
}
.calite-card__photo::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent 28%, rgba(231, 221, 212, 0.42) 58%, rgba(231, 221, 212, 0.88) 100%);
}
.calite-card__badge {
position: absolute;
z-index: 2;
top: 12px;
left: 12px;
padding: 5px 12px;
border-radius: 999px;
background: #e7ddd4;
color: #000000;
font-size: 1rem;
font-weight: 500;
line-height: 1.2;
}
.calite-card__copy {
position: relative;
align-items: center;
z-index: 1;
display: flex;
gap: 4px;
min-height: 132px;
padding: 16px 16px 16px 42%;
}
.calite-card__copy strong {
font-size: 1.0625rem;
font-weight: 700;
line-height: 1.4;
color: #2c2824;
}
.calite-card__sub {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 2px;
color: #5c5752;
}
.calite-card__arrow {
flex-shrink: 0;
display: block;
}
.calite-card__copy em {
font-style: normal;
font-size: 0.75rem;
font-weight: 500;
line-height: 1.45;
color: #5c5752;
}
.calite-card__copy small {
font-size: 0.6875rem;
font-weight: 500;
line-height: 1.45;
color: #7a746c;
}
.calite-hub__empty {
margin: 16px 0 0;
text-align: center;
color: #8a847c;
}
html.dark .calite-hub,
html.dark .calite-hub__nav {
background: #0b0f14;
color: #eef2f8;
}
html.dark .calite-hub__lead,
html.dark .calite-hub__empty {
color: #9aa8bc;
}
html.dark .calite-chip {
border-color: #2a3444;
background: #1a2230;
color: #c5d0de;
}
html.dark .calite-chip.is-on {
border-color: #e8e4df;
background: #e8e4df;
color: #121821;
}
html.dark .calite-card {
background: #1a2230;
color: #eef2f8;
}
html.dark .calite-card__photo {
background: linear-gradient(160deg, #3a4454, #151b24);
}
html.dark .calite-card__photo::after {
background: linear-gradient(
90deg,
transparent 22%,
rgba(11, 15, 20, 0.42) 55%,
rgba(11, 15, 20, 0.88) 100%
);
}
html.dark .calite-card__badge {
background: #e8e4df;
color: #121821;
}
html.dark .calite-card__copy strong,
html.dark .calite-card__sub,
html.dark .calite-card__copy em,
html.dark .calite-card__copy small {
color: #eef2f8;
}
html.dark .calite-card__copy small {
color: #c5d0de;
}
@media (min-width: 768px) {
.calite-hub {
padding: 0;
max-width: none;
margin: 0;
overflow: visible;
min-height: auto;
background: #f4f0ed;
}
.calite-hub__scroll {
padding: 8px 28px 40px;
max-width: 1120px;
margin-inline: auto;
width: 100%;
gap: 16px;
}
.calite-hub__nav {
grid-template-columns: 1fr auto;
max-width: 1120px;
width: 100%;
margin-inline: auto;
padding: 28px 28px 0;
}
.calite-hub__nav h1 {
text-align: start;
font-size: 1.5rem;
}
.calite-hub__lead {
font-size: 0.9375rem;
text-align: start;
}
.calite-hub__chips {
flex-wrap: wrap;
}
.calite-chip {
height: 40px;
padding: 0 18px;
font-size: 0.8125rem;
}
.calite-hub__list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.calite-card,
.calite-card__copy {
min-height: 176px;
}
.calite-card__copy {
padding: 20px 20px 20px 38%;
}
}
@media (min-width: 1200px) {
.calite-hub__nav,
.calite-hub__scroll {
padding-inline: 40px;
}
.calite-hub__scroll {
padding-bottom: 48px;
}
.calite-hub__list {
gap: 20px;
}
.calite-card,
.calite-card__copy {
min-height: 200px;
}
}
</style>