+
+
{product.images?.[0] ? (
) : null}
+
+ {product.title}
+
)
}
diff --git a/src/pages/home/components/Services.tsx b/src/pages/home/components/Services.tsx
index 4d5fa6b..a0b7f3b 100644
--- a/src/pages/home/components/Services.tsx
+++ b/src/pages/home/components/Services.tsx
@@ -1,41 +1,121 @@
import { t } from '@/locale'
-import { type FC } from 'react'
-import { Swiper, SwiperSlide } from 'swiper/react'
+import {
+ useGetCategories,
+ useGetProducts,
+} from '@/pages/request/hooks/useRequestData'
+import type { CategoryType } from '@/pages/request/type/Types'
+import {
+ buildPathToFirstLeaf,
+ extractList,
+ getLevelsToShow,
+} from '@/pages/request/utils/categoryUtils'
+import { type FC, useEffect, useMemo, useState } from 'react'
+import CategoryTabs from './CategoryTabs'
+import HomeSection from './HomeSection'
import ServiceItem from './ServiceItem'
-import { useGetProducts } from '@/pages/request/hooks/useRequestData'
+const SKELETON_COUNT = 6
+
+const CategoryTabsSkeleton: FC = () => (
+
+ {Array.from({ length: 4 }).map((_, index) => (
+
+ ))}
+
+)
+
+const ServicesSkeleton: FC = () => (
+
+ {Array.from({ length: SKELETON_COUNT }).map((_, index) => (
+
+ ))}
+
+)
const Services: FC = () => {
- const { data } = useGetProducts();
+ const { data: categoriesData, isLoading: isCategoriesLoading } =
+ useGetCategories()
+ const categories = useMemo(
+ () => extractList
(categoriesData),
+ [categoriesData],
+ )
+
+ const [selectedPath, setSelectedPath] = useState([])
+
+ useEffect(() => {
+ if (!categories.length || selectedPath.length) return
+
+ setSelectedPath(
+ buildPathToFirstLeaf(categories, categories[0].id),
+ )
+ }, [categories, selectedPath.length])
+
+ const levelsToShow = useMemo(
+ () => getLevelsToShow(categories, selectedPath),
+ [categories, selectedPath],
+ )
+
+ const activeCategoryId = selectedPath.at(-1) ?? ''
+
+ const { data: productsData, isLoading: isProductsLoading } = useGetProducts(
+ activeCategoryId || undefined,
+ { enabled: !!activeCategoryId },
+ )
+ const products = productsData?.data ?? []
+
+ const handleCategorySelect = (level: number, categoryId: string) => {
+ setSelectedPath(
+ buildPathToFirstLeaf(
+ categories,
+ categoryId,
+ selectedPath.slice(0, level),
+ ),
+ )
+ }
+
+ const isLoading = isCategoriesLoading || isProductsLoading
+
return (
-
-
- {t('home.services')}
-
+
+
+ {isCategoriesLoading ? (
+
+ ) : categories.length > 0 ? (
+
+ ) : null}
-
-
- {
- data?.data.map((item) => (
-
-
-
- ))
- }
-
+
+ {isLoading ? (
+
+ ) : products.length === 0 ? (
+
+ {t('home.noProducts')}
+
+ ) : (
+
+ {products.map((item) => (
+
+ ))}
+
+ )}
+
-
-
+
)
}
-export default Services
\ No newline at end of file
+export default Services
diff --git a/src/pages/home/components/SpecialSolutionCard.tsx b/src/pages/home/components/SpecialSolutionCard.tsx
new file mode 100644
index 0000000..073ae1e
--- /dev/null
+++ b/src/pages/home/components/SpecialSolutionCard.tsx
@@ -0,0 +1,63 @@
+import SupportImage from '@/assets/images/support1.png'
+import Button from '@/components/Button'
+import { COLORS } from '@/constants/colors'
+import { t } from '@/locale'
+import { MessageQuestion } from 'iconsax-react'
+import { type FC } from 'react'
+import HomeSection from './HomeSection'
+
+const FEATURE_KEYS = [
+ 'home.specialSolutionFeature1',
+ 'home.specialSolutionFeature2',
+ 'home.specialSolutionFeature3',
+] as const
+
+const SpecialSolutionCard: FC = () => {
+ return (
+
+
+ {t('home.specialSolution')}
+
+
+ {t('home.specialSolutionDescription')}
+
+
+
+ {FEATURE_KEYS.map((key) => (
+ -
+
+ {t(key)}
+
+ ))}
+
+
+
+
+
+

+
+
+
+
+
+ {t('home.contactUs')}
+
+
+
+ {t('home.phoneNumber')}
+
+
+
+
+
+ )
+}
+
+export default SpecialSolutionCard
diff --git a/src/pages/home/components/StatCard.tsx b/src/pages/home/components/StatCard.tsx
index af2909c..e338954 100644
--- a/src/pages/home/components/StatCard.tsx
+++ b/src/pages/home/components/StatCard.tsx
@@ -4,34 +4,35 @@ import { type FC, type ReactNode } from 'react'
import { Link } from 'react-router-dom'
type Props = {
- icon: ReactNode,
- count: number | undefined,
- description: string,
- to: string,
+ icon: ReactNode
+ count: number | undefined
+ description: string
+ to: string
}
-const StatCard: FC
= (props) => {
- const { icon, count, description, to } = props
+const StatCard: FC = ({ icon, count, description, to }) => {
return (
-
+
{icon}
-
-
- {count}
+
+ {count ?? 0}
-
- {description}
-
+
{description}
)
}
diff --git a/src/pages/home/components/Stats.tsx b/src/pages/home/components/Stats.tsx
index 4b71c18..c5cd47e 100644
--- a/src/pages/home/components/Stats.tsx
+++ b/src/pages/home/components/Stats.tsx
@@ -19,7 +19,7 @@ const Stats: FC = () => {
mobile={2}
gapDesktop={24}
gapMobile={12}
- className='mt-5'
+ className='mt-4 md:mt-5'
>
({
+ id: order.id,
+ number: String(order.orderNumber ?? order.id),
+ title: order.title,
+ creationDate: {
+ gregorian: moment(order.createdAt).format('YYYY-MM-DD'),
+ persian: moment(order.createdAt).format('jYYYY/jMM/jDD'),
+ },
+ status: orderStatusLabels[order.status] ?? order.status,
+ unreadMessages: 0,
+})
+
+export const orderColumns: ColumnType[] = [
+ {
+ title: 'شماره',
+ key: 'number',
+ render: (order) => ,
+ },
+ {
+ title: 'عنوان سفارش',
+ key: 'title',
+ render: (order) => ,
+ },
+ {
+ title: 'تاریخ ایجاد',
+ key: 'creationDate',
+ render: (order) => ,
+ },
+ {
+ title: 'وضعیت سفارش',
+ key: 'status',
+ render: (order) => ,
+ },
+ {
+ title: 'پیام ها',
+ key: 'unreadMessages',
+ render: (order) => ,
+ },
+ {
+ title: '',
+ key: 'actions',
+ render: (order) => ,
+ width: '60px',
+ },
+]
diff --git a/src/pages/request/components/ProductsSelect.tsx b/src/pages/request/components/ProductsSelect.tsx
index b780939..0afc5b6 100644
--- a/src/pages/request/components/ProductsSelect.tsx
+++ b/src/pages/request/components/ProductsSelect.tsx
@@ -2,67 +2,18 @@ import { type ChangeEvent, type FC, type SelectHTMLAttributes, useEffect, useMem
import { useGetCategories, useGetProducts } from '../hooks/useRequestData'
import Select from '@/components/Select'
import type { CategoryType, ProductType } from '../type/Types'
+import {
+ extractList,
+ findCategoryById,
+ findCategoryPath,
+ getCategoriesAtLevel,
+} from '../utils/categoryUtils'
type Props = {
error_text?: string,
onProductSelect?: (product: ProductType | undefined) => void,
} & SelectHTMLAttributes
-const findCategoryById = (categories: CategoryType[], targetId: string): CategoryType | null => {
- for (const category of categories) {
- if (category.id === targetId) {
- return category
- }
-
- if (category.children?.length) {
- const found = findCategoryById(category.children, targetId)
- if (found) return found
- }
- }
-
- return null
-}
-
-const findCategoryPath = (categories: CategoryType[], targetId: string): string[] | null => {
- for (const category of categories) {
- if (category.id === targetId) {
- return [category.id]
- }
-
- if (category.children?.length) {
- const childPath = findCategoryPath(category.children, targetId)
- if (childPath) {
- return [category.id, ...childPath]
- }
- }
- }
-
- return null
-}
-
-const getCategoriesAtLevel = (
- categories: CategoryType[],
- level: number,
- selectedPath: string[],
-): CategoryType[] => {
- if (level === 0) return categories
-
- const parentId = selectedPath[level - 1]
- if (!parentId) return []
-
- const parent = findCategoryById(categories, parentId)
- return parent?.children ?? []
-}
-
-const extractList = (payload: unknown): T[] => {
- if (Array.isArray(payload)) return payload
- if (payload && typeof payload === 'object' && 'data' in payload) {
- const maybeData = (payload as { data?: unknown }).data
- return Array.isArray(maybeData) ? (maybeData as T[]) : []
- }
- return []
-}
-
const ProductsSelect: FC = (props) => {
const { error_text, onProductSelect, value, onChange, ...rest } = props
const [selectedPath, setSelectedPath] = useState([])
diff --git a/src/pages/request/utils/categoryUtils.ts b/src/pages/request/utils/categoryUtils.ts
new file mode 100644
index 0000000..d5424b3
--- /dev/null
+++ b/src/pages/request/utils/categoryUtils.ts
@@ -0,0 +1,94 @@
+import type { CategoryType } from '../type/Types'
+
+export const extractList = (payload: unknown): T[] => {
+ if (Array.isArray(payload)) return payload
+ if (payload && typeof payload === 'object' && 'data' in payload) {
+ const maybeData = (payload as { data?: unknown }).data
+ return Array.isArray(maybeData) ? (maybeData as T[]) : []
+ }
+ return []
+}
+
+export const findCategoryById = (
+ categories: CategoryType[],
+ targetId: string,
+): CategoryType | null => {
+ for (const category of categories) {
+ if (category.id === targetId) {
+ return category
+ }
+
+ if (category.children?.length) {
+ const found = findCategoryById(category.children, targetId)
+ if (found) return found
+ }
+ }
+
+ return null
+}
+
+export const findCategoryPath = (
+ categories: CategoryType[],
+ targetId: string,
+): string[] | null => {
+ for (const category of categories) {
+ if (category.id === targetId) {
+ return [category.id]
+ }
+
+ if (category.children?.length) {
+ const childPath = findCategoryPath(category.children, targetId)
+ if (childPath) {
+ return [category.id, ...childPath]
+ }
+ }
+ }
+
+ return null
+}
+
+export const getCategoriesAtLevel = (
+ categories: CategoryType[],
+ level: number,
+ selectedPath: string[],
+): CategoryType[] => {
+ if (level === 0) return categories
+
+ const parentId = selectedPath[level - 1]
+ if (!parentId) return []
+
+ const parent = findCategoryById(categories, parentId)
+ return parent?.children ?? []
+}
+
+export const getLevelsToShow = (
+ categories: CategoryType[],
+ selectedPath: string[],
+): number[] => {
+ const levels = [0]
+
+ for (let level = 0; level < selectedPath.length; level++) {
+ const category = findCategoryById(categories, selectedPath[level])
+ if (category?.children?.length) {
+ levels.push(level + 1)
+ }
+ }
+
+ return levels
+}
+
+export const buildPathToFirstLeaf = (
+ categories: CategoryType[],
+ categoryId: string,
+ prefixPath: string[] = [],
+): string[] => {
+ const path = [...prefixPath, categoryId]
+ let current = findCategoryById(categories, categoryId)
+
+ while (current?.children?.length) {
+ path.push(current.children[0].id)
+ current = current.children[0]
+ }
+
+ return path
+}