catalg list

This commit is contained in:
hamid zarghami
2026-03-10 10:09:22 +03:30
parent cbd4ab181a
commit d3ad3c0178
9 changed files with 273 additions and 1 deletions
+50
View File
@@ -17,6 +17,7 @@
"i18next": "^25.6.2",
"iconsax-react": "^0.0.8",
"konva": "^10.0.9",
"moment-jalaali": "^0.10.4",
"page-flip": "^2.0.7",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -32,6 +33,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/moment-jalaali": "^0.7.9",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
@@ -1909,6 +1911,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/moment-jalaali": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/@types/moment-jalaali/-/moment-jalaali-0.7.9.tgz",
"integrity": "sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"moment": ">=2.14.0"
}
},
"node_modules/@types/node": {
"version": "24.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
@@ -3431,6 +3443,12 @@
"@types/react": "*"
}
},
"node_modules/jalaali-js": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.8.tgz",
"integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==",
"license": "MIT"
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@@ -3920,6 +3938,38 @@
"node": "*"
}
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/moment-jalaali": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz",
"integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==",
"license": "MIT",
"dependencies": {
"jalaali-js": "^1.2.7",
"moment": "^2.29.4",
"moment-timezone": "^0.5.46"
}
},
"node_modules/moment-timezone": {
"version": "0.5.48",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
"license": "MIT",
"dependencies": {
"moment": "^2.29.4"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+2
View File
@@ -19,6 +19,7 @@
"i18next": "^25.6.2",
"iconsax-react": "^0.0.8",
"konva": "^10.0.9",
"moment-jalaali": "^0.10.4",
"page-flip": "^2.0.7",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -34,6 +35,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/moment-jalaali": "^0.7.9",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
+125
View File
@@ -0,0 +1,125 @@
import { clx } from "@/helpers/utils";
import React from "react";
type GridWrapperProps = {
children: React.ReactNode;
desktop: number; // تعداد ستون‌ها در دسکتاپ
mobile: number; // تعداد ستون‌ها در موبایل
gapDesktop?: number; // فاصله بین آیتم‌ها در دسکتاپ. اگر مقدار > 12 باشد به عنوان px تفسیر می‌شود (مثلاً 24 => 24px)
gapMobile?: number; // فاصله بین آیتم‌ها در موبایل. اگر مقدار > 12 باشد به عنوان px تفسیر می‌شود
className?: string;
};
const GRID_COLS_CLASS: Record<number, string> = {
1: "grid-cols-1",
2: "grid-cols-2",
3: "grid-cols-3",
4: "grid-cols-4",
5: "grid-cols-5",
6: "grid-cols-6",
7: "grid-cols-7",
8: "grid-cols-8",
9: "grid-cols-9",
10: "grid-cols-10",
11: "grid-cols-11",
12: "grid-cols-12",
};
const MD_GRID_COLS_CLASS: Record<number, string> = {
1: "md:grid-cols-1",
2: "md:grid-cols-2",
3: "md:grid-cols-3",
4: "md:grid-cols-4",
5: "md:grid-cols-5",
6: "md:grid-cols-6",
7: "md:grid-cols-7",
8: "md:grid-cols-8",
9: "md:grid-cols-9",
10: "md:grid-cols-10",
11: "md:grid-cols-11",
12: "md:grid-cols-12",
};
// نسخه‌ی دسکتاپ با پیشوند lg: به صورت literal تا با purge مشکلی نداشته باشد
const LG_GRID_COLS_CLASS: Record<number, string> = {
1: "lg:grid-cols-1",
2: "lg:grid-cols-2",
3: "lg:grid-cols-3",
4: "lg:grid-cols-4",
5: "lg:grid-cols-5",
6: "lg:grid-cols-6",
7: "lg:grid-cols-7",
8: "lg:grid-cols-8",
9: "lg:grid-cols-9",
10: "lg:grid-cols-10",
11: "lg:grid-cols-11",
12: "lg:grid-cols-12",
};
// نگاشت Gap بر اساس scale رایج Tailwind: gap-N که هر N معادل 4px است (N * 4px)
const GAP_CLASS: Record<number, string> = {
0: "gap-0", 1: "gap-1", 2: "gap-2", 3: "gap-3", 4: "gap-4", 5: "gap-5",
6: "gap-6", 7: "gap-7", 8: "gap-8", 9: "gap-9", 10: "gap-10", 11: "gap-11",
12: "gap-12", 13: "gap-13", 14: "gap-14", 15: "gap-15", 16: "gap-16", 17: "gap-17",
18: "gap-18", 19: "gap-19", 20: "gap-20", 21: "gap-21", 22: "gap-22", 23: "gap-23", 24: "gap-24",
};
const LG_GAP_CLASS: Record<number, string> = {
0: "lg:gap-0", 1: "lg:gap-1", 2: "lg:gap-2", 3: "lg:gap-3", 4: "lg:gap-4", 5: "lg:gap-5",
6: "lg:gap-6", 7: "lg:gap-7", 8: "lg:gap-8", 9: "lg:gap-9", 10: "lg:gap-10", 11: "lg:gap-11",
12: "lg:gap-12", 13: "lg:gap-13", 14: "lg:gap-14", 15: "lg:gap-15", 16: "lg:gap-16", 17: "lg:gap-17",
18: "lg:gap-18", 19: "lg:gap-19", 20: "lg:gap-20", 21: "lg:gap-21", 22: "lg:gap-22", 23: "lg:gap-23", 24: "lg:gap-24",
};
function clampToRange(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
function getGridColsClass(count: number): string {
const safe = clampToRange(count, 1, 12);
return GRID_COLS_CLASS[safe];
}
function getLgGridColsClass(count: number): string {
const safe = clampToRange(count, 1, 12);
return LG_GRID_COLS_CLASS[safe];
}
function getMdGridColsClass(count: number): string {
const safe = clampToRange(count, 1, 12);
return MD_GRID_COLS_CLASS[safe];
}
function getGapClass(value?: number): string | undefined {
if (value === undefined) return undefined;
// اگر کاربر مقدار را به صورت px فرستاده باشد (بزرگ‌تر از 12)، آن را به scale تبدیل می‌کنیم: N = round(px/4)
const interpretedAsScale = value > 12 ? Math.round(value / 4) : value;
const safe = clampToRange(interpretedAsScale, 0, 24);
return GAP_CLASS[safe];
}
function getLgGapClass(value?: number): string | undefined {
if (value === undefined) return undefined;
const interpretedAsScale = value > 12 ? Math.round(value / 4) : value;
const safe = clampToRange(interpretedAsScale, 0, 24);
return LG_GAP_CLASS[safe];
}
export default function GridWrapper(props: GridWrapperProps) {
const { children, desktop, mobile, className, gapDesktop = 4, gapMobile = 4 } = props;
const mobileCols = getGridColsClass(mobile);
const desktopColsMd = getMdGridColsClass(desktop);
const desktopColsLg = getLgGridColsClass(desktop);
const mobileGap = getGapClass(gapMobile);
const desktopGap = getLgGapClass(gapDesktop);
return (
<div className={clx("grid", mobileCols, desktopColsMd, desktopColsLg, mobileGap, desktopGap, className)}>
{children}
</div>
);
}
+25
View File
@@ -0,0 +1,25 @@
import { type FC } from 'react'
import { useGetCatalog } from '../home/hooks/useHomeData'
import GridWrapper from '@/components/GridWrapper'
import CatalogueItem from './components/CatalogueItem'
const CatalogueList: FC = () => {
const { data } = useGetCatalog()
return (
<div className='w-full mt-5'>
<GridWrapper desktop={4} gapDesktop={40} mobile={1}>
{
data?.data?.map((item) => {
return (
<CatalogueItem key={item.id} item={item} />
)
})
}
</GridWrapper>
</div>
)
}
export default CatalogueList
@@ -0,0 +1,42 @@
import { type FC } from 'react'
import { Edit, Eye, Share, Trash } from 'iconsax-react'
import type { CatalogItemType } from '../types/Types'
import moment from 'moment-jalaali'
type Props = {
item: CatalogItemType
}
const CatalogueItem: FC<Props> = ({ item }) => {
return (
<div className='bg-white p-6 rounded-3xl w-full'>
<div className='flex gap-4 items-center'>
<div className='w-[64px] h-[89px] bg-gray-200'></div>
<div>
<h6>
{item.name}
</h6>
<div className='mt-2 text-[#999999] text-xs'>
{moment(item.createdAt).format('jYYYY/jMM/jDD')}
</div>
</div>
</div>
<div className='flex justify-end gap-1 items-center'>
<div className='size-6 bg-[#EAECF4] rounded-md flex justify-center items-center'>
<Eye size={18} color='#0047FF' />
</div>
<div className='size-6 bg-[#EAECF4] rounded-md flex justify-center items-center'>
<Edit size={18} color='#0047FF' />
</div>
<div className='size-6 bg-[#EAECF4] rounded-md flex justify-center items-center'>
<Share size={18} color='#000' />
</div>
<div className='size-6 bg-[#EAECF4] rounded-md flex justify-center items-center'>
<Trash size={18} color='#E12800' />
</div>
</div>
</div>
)
}
export default CatalogueItem
+12
View File
@@ -0,0 +1,12 @@
import type { BaseResponse } from "@/shared/types/SharedTypes";
export type CatalogItemType = {
content: string;
id: string;
name: string;
size: string;
status: string;
createdAt: string;
};
export type CatalogResponseType = BaseResponse<CatalogItemType[]>;
+2 -1
View File
@@ -1,5 +1,6 @@
import axios from "@/config/axios";
import type { CreateCatalogParamsType } from "../types/Types";
import type { CatalogResponseType } from "@/pages/catalogue/types/Types";
export const createCatalog = async (params: CreateCatalogParamsType) => {
const { data } = await axios.post("/catalogue", params);
@@ -7,7 +8,7 @@ export const createCatalog = async (params: CreateCatalogParamsType) => {
};
export const getCatalogs = async () => {
const { data } = await axios.get("/catalogue");
const { data } = await axios.get<CatalogResponseType>("/catalogue");
return data;
};
+2
View File
@@ -7,6 +7,7 @@ import { useSharedStore } from '@/shared/store/sharedStore'
import Home from '@/pages/home/Home'
import Editor from '@/pages/editor/Editor'
import Viewer from '@/pages/viewer/Viewer'
import CatalogueList from '@/pages/catalogue/List'
const MainRouter = () => {
const { hasSubMenu } = useSharedStore()
@@ -33,6 +34,7 @@ const MainRouter = () => {
<Route path={Paths.home} element={<Home />} />
<Route path={Paths.editor} element={<Editor />} />
<Route path={Paths.viewer} element={<Viewer />} />
<Route path={Paths.catalog.list} element={<CatalogueList />} />
</Routes>
</div>
</div>
+13
View File
@@ -17,3 +17,16 @@ export type PagerType = {
prevPage: boolean | null;
nextPage: string | null;
};
export interface BaseResponse<T> {
success: boolean;
statusCode: number;
status: number;
data: T;
meta?: {
limit: number;
page: number;
total: number;
totalPages: number;
};
}