diff --git a/src/pages/categories/categories-list/CategoriesListPage.tsx b/src/pages/categories/categories-list/CategoriesListPage.tsx
new file mode 100644
index 0000000..ae7c366
--- /dev/null
+++ b/src/pages/categories/categories-list/CategoriesListPage.tsx
@@ -0,0 +1,295 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useCategories, useDeleteCategory } from '../core/_hooks';
+import { Category } from '../core/_models';
+import { Button } from "@/components/ui/Button";
+import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
+import { Trash2, Edit3, Plus, FolderOpen, Folder } from "lucide-react";
+import { Modal } from "@/components/ui/Modal";
+
+const CategoriesTableSkeleton = () => (
+
+
+
+
+
+
+ |
+ نام دستهبندی
+ |
+
+ توضیحات
+ |
+
+ تاریخ ایجاد
+ |
+
+ عملیات
+ |
+
+
+
+ {[...Array(5)].map((_, i) => (
+
+ |
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+
+);
+
+const CategoriesListPage = () => {
+ const navigate = useNavigate();
+ const [deleteCategoryId, setDeleteCategoryId] = useState(null);
+ const [filters, setFilters] = useState({
+ search: ''
+ });
+
+ const { data: categories, isLoading, error } = useCategories(filters);
+ const { mutate: deleteCategory, isPending: isDeleting } = useDeleteCategory();
+
+ const handleCreate = () => {
+ navigate('/categories/create');
+ };
+
+ const handleEdit = (categoryId: number) => {
+ navigate(`/categories/${categoryId}/edit`);
+ };
+
+ const handleDeleteConfirm = () => {
+ if (deleteCategoryId) {
+ deleteCategory(deleteCategoryId, {
+ onSuccess: () => {
+ setDeleteCategoryId(null);
+ }
+ });
+ }
+ };
+
+ const handleSearchChange = (e: React.ChangeEvent) => {
+ setFilters(prev => ({ ...prev, search: e.target.value }));
+ };
+
+ if (error) {
+ return (
+
+
+
خطا در بارگذاری دستهبندیها
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ مدیریت دستهبندیها
+
+
+ مدیریت دستهبندیهای محصولات
+
+
+
+
+
+ {/* Filters */}
+
+
+ {/* Categories Table */}
+ {isLoading ? (
+
+ ) : (
+
+ {/* Desktop Table */}
+
+
+
+
+
+ |
+ نام دستهبندی
+ |
+
+ توضیحات
+ |
+
+ تاریخ ایجاد
+ |
+
+ عملیات
+ |
+
+
+
+ {(categories || []).map((category: Category) => (
+
+ |
+
+
+ {category.name}
+
+ |
+
+
+ {category.description || 'بدون توضیحات'}
+
+ |
+
+ {new Date(category.created_at).toLocaleDateString('fa-IR')}
+ |
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+ {/* Mobile Cards */}
+
+ {(categories || []).map((category: Category) => (
+
+
+
+
+
+ {category.name}
+
+
+ {category.description || 'بدون توضیحات'}
+
+
+
+
+ تاریخ ایجاد: {new Date(category.created_at).toLocaleDateString('fa-IR')}
+
+
+
+
+
+
+ ))}
+
+
+ {/* Empty State */}
+ {(!categories || categories.length === 0) && !isLoading && (
+
+
+
+ دستهبندیای موجود نیست
+
+
+ برای شروع، اولین دستهبندی محصولات خود را ایجاد کنید.
+
+
+
+
+
+ )}
+
+ )}
+
+ {/* Delete Confirmation Modal */}
+
setDeleteCategoryId(null)}
+ title="حذف دستهبندی"
+ >
+
+
+ آیا از حذف این دستهبندی اطمینان دارید؟ این عمل قابل بازگشت نیست و ممکن است بر محصولاتی که در این دستهبندی قرار دارند تأثیر بگذارد.
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CategoriesListPage;
\ No newline at end of file
diff --git a/src/pages/categories/category-form/CategoryFormPage.tsx b/src/pages/categories/category-form/CategoryFormPage.tsx
new file mode 100644
index 0000000..533feee
--- /dev/null
+++ b/src/pages/categories/category-form/CategoryFormPage.tsx
@@ -0,0 +1,191 @@
+import React, { useEffect } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import { useForm } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers/yup';
+import * as yup from 'yup';
+import { useCategory, useCreateCategory, useUpdateCategory } from '../core/_hooks';
+import { CategoryFormData } from '../core/_models';
+import { Button } from "@/components/ui/Button";
+import { Input } from "@/components/ui/Input";
+import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
+import { ArrowRight, FolderOpen } from "lucide-react";
+
+const categorySchema = yup.object({
+ name: yup.string().required('نام دستهبندی الزامی است').min(2, 'نام دستهبندی باید حداقل 2 کاراکتر باشد'),
+ description: yup.string().optional(),
+});
+
+const CategoryFormPage = () => {
+ const navigate = useNavigate();
+ const { id } = useParams<{ id: string }>();
+ const isEdit = !!id;
+
+ const { data: category, isLoading: isLoadingCategory } = useCategory(id || '', isEdit);
+ const { mutate: createCategory, isPending: isCreating } = useCreateCategory();
+ const { mutate: updateCategory, isPending: isUpdating } = useUpdateCategory();
+
+ const isLoading = isCreating || isUpdating;
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isValid, isDirty },
+ setValue,
+ watch
+ } = useForm({
+ resolver: yupResolver(categorySchema) as any,
+ mode: 'onChange',
+ defaultValues: {
+ name: '',
+ description: ''
+ }
+ });
+
+ const formValues = watch();
+
+ useEffect(() => {
+ if (isEdit && category) {
+ setValue('name', category.name, { shouldValidate: true });
+ setValue('description', category.description || '', { shouldValidate: true });
+ }
+ }, [isEdit, category, setValue]);
+
+ const onSubmit = (data: CategoryFormData) => {
+ if (isEdit && id) {
+ updateCategory({
+ id: parseInt(id),
+ name: data.name,
+ description: data.description || undefined
+ }, {
+ onSuccess: () => {
+ navigate('/categories');
+ }
+ });
+ } else {
+ createCategory({
+ name: data.name,
+ description: data.description || undefined
+ }, {
+ onSuccess: () => {
+ navigate('/categories');
+ }
+ });
+ }
+ };
+
+ const handleBack = () => {
+ navigate('/categories');
+ };
+
+ if (isEdit && isLoadingCategory) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ {isEdit ? 'ویرایش دستهبندی' : 'ایجاد دستهبندی جدید'}
+
+
+ {isEdit ? 'ویرایش اطلاعات دستهبندی' : 'اطلاعات دستهبندی جدید را وارد کنید'}
+
+
+
+
+ {/* Form */}
+
+
+ {/* Help Section */}
+
+
+ راهنما
+
+
+ - • دستهبندیها برای سازماندهی محصولات استفاده میشوند
+ - • نام دستهبندی باید واضح و قابل فهم باشد
+ - • توضیحات کمک میکند تا دستهبندی بهتر شناخته شود
+ - • بعد از ایجاد، میتوانید محصولات را به این دستهبندی اختصاص دهید
+
+
+
+ );
+};
+
+export default CategoryFormPage;
\ No newline at end of file
diff --git a/src/pages/categories/core/_hooks.ts b/src/pages/categories/core/_hooks.ts
new file mode 100644
index 0000000..104a737
--- /dev/null
+++ b/src/pages/categories/core/_hooks.ts
@@ -0,0 +1,84 @@
+import { QUERY_KEYS } from "@/utils/query-key";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ getCategories,
+ getCategory,
+ createCategory,
+ updateCategory,
+ deleteCategory,
+} from "./_requests";
+import {
+ CreateCategoryRequest,
+ UpdateCategoryRequest,
+ CategoryFilters,
+} from "./_models";
+import toast from "react-hot-toast";
+
+export const useCategories = (filters?: CategoryFilters) => {
+ return useQuery({
+ queryKey: [QUERY_KEYS.GET_CATEGORIES, filters],
+ queryFn: () => getCategories(filters),
+ });
+};
+
+export const useCategory = (id: string, enabled: boolean = true) => {
+ return useQuery({
+ queryKey: [QUERY_KEYS.GET_CATEGORY, id],
+ queryFn: () => getCategory(id),
+ enabled: enabled && !!id,
+ });
+};
+
+export const useCreateCategory = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: [QUERY_KEYS.CREATE_CATEGORY],
+ mutationFn: (data: CreateCategoryRequest) => createCategory(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_CATEGORIES] });
+ toast.success("دستهبندی با موفقیت ایجاد شد");
+ },
+ onError: (error: any) => {
+ console.error("Create category error:", error);
+ toast.error(error?.message || "خطا در ایجاد دستهبندی");
+ },
+ });
+};
+
+export const useUpdateCategory = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: [QUERY_KEYS.UPDATE_CATEGORY],
+ mutationFn: (data: UpdateCategoryRequest) => updateCategory(data),
+ onSuccess: (_, variables) => {
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_CATEGORIES] });
+ queryClient.invalidateQueries({
+ queryKey: [QUERY_KEYS.GET_CATEGORY, variables.id.toString()],
+ });
+ toast.success("دستهبندی با موفقیت ویرایش شد");
+ },
+ onError: (error: any) => {
+ console.error("Update category error:", error);
+ toast.error(error?.message || "خطا در ویرایش دستهبندی");
+ },
+ });
+};
+
+export const useDeleteCategory = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: [QUERY_KEYS.DELETE_CATEGORY],
+ mutationFn: (id: string) => deleteCategory(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_CATEGORIES] });
+ toast.success("دستهبندی با موفقیت حذف شد");
+ },
+ onError: (error: any) => {
+ console.error("Delete category error:", error);
+ toast.error(error?.message || "خطا در حذف دستهبندی");
+ },
+ });
+};
diff --git a/src/pages/categories/core/_models.ts b/src/pages/categories/core/_models.ts
new file mode 100644
index 0000000..5dfa3c9
--- /dev/null
+++ b/src/pages/categories/core/_models.ts
@@ -0,0 +1,49 @@
+export interface Category {
+ id: number;
+ name: string;
+ description?: string;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface CategoryFormData {
+ name: string;
+ description: string;
+}
+
+export interface CategoryFilters {
+ search?: string;
+ page?: number;
+ limit?: number;
+}
+
+export interface CreateCategoryRequest {
+ name: string;
+ description?: string;
+}
+
+export interface UpdateCategoryRequest {
+ id: number;
+ name: string;
+ description?: string;
+}
+
+export interface CategoriesResponse {
+ categories: Category[] | null;
+}
+
+export interface CategoryResponse {
+ category: Category;
+}
+
+export interface CreateCategoryResponse {
+ category: Category;
+}
+
+export interface UpdateCategoryResponse {
+ category: Category;
+}
+
+export interface DeleteCategoryResponse {
+ message: string;
+}
diff --git a/src/pages/categories/core/_requests.ts b/src/pages/categories/core/_requests.ts
new file mode 100644
index 0000000..563faf7
--- /dev/null
+++ b/src/pages/categories/core/_requests.ts
@@ -0,0 +1,79 @@
+import {
+ httpGetRequest,
+ httpPostRequest,
+ httpPutRequest,
+ httpDeleteRequest,
+ APIUrlGenerator,
+} from "@/utils/baseHttpService";
+import { API_ROUTES } from "@/constant/routes";
+import {
+ Category,
+ CreateCategoryRequest,
+ UpdateCategoryRequest,
+ CategoriesResponse,
+ CategoryResponse,
+ CreateCategoryResponse,
+ UpdateCategoryResponse,
+ DeleteCategoryResponse,
+ CategoryFilters,
+} from "./_models";
+
+export const getCategories = async (filters?: CategoryFilters) => {
+ try {
+ const queryParams: Record = {};
+
+ if (filters?.search) queryParams.search = filters.search;
+ if (filters?.page) queryParams.page = filters.page;
+ if (filters?.limit) queryParams.limit = filters.limit;
+
+ const response = await httpGetRequest(
+ APIUrlGenerator(API_ROUTES.GET_CATEGORIES, queryParams)
+ );
+
+ console.log("Categories API Response:", response);
+
+ if (
+ response.data &&
+ response.data.categories &&
+ Array.isArray(response.data.categories)
+ ) {
+ return response.data.categories;
+ }
+
+ console.warn("Categories is null or not an array:", response.data);
+ return [];
+ } catch (error) {
+ console.error("Error fetching categories:", error);
+ return [];
+ }
+};
+
+export const getCategory = async (id: string) => {
+ const response = await httpGetRequest(
+ APIUrlGenerator(API_ROUTES.GET_CATEGORY(id))
+ );
+ return response.data.category;
+};
+
+export const createCategory = async (data: CreateCategoryRequest) => {
+ const response = await httpPostRequest(
+ APIUrlGenerator(API_ROUTES.CREATE_CATEGORY),
+ data
+ );
+ return response.data.category;
+};
+
+export const updateCategory = async (data: UpdateCategoryRequest) => {
+ const response = await httpPutRequest(
+ APIUrlGenerator(API_ROUTES.UPDATE_CATEGORY(data.id.toString())),
+ data
+ );
+ return response.data.category;
+};
+
+export const deleteCategory = async (id: string) => {
+ const response = await httpDeleteRequest(
+ APIUrlGenerator(API_ROUTES.DELETE_CATEGORY(id))
+ );
+ return response.data;
+};