diff --git a/src/App.tsx b/src/App.tsx index 527ce51..f3f87b0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -88,16 +88,12 @@ const ShipmentsByMethodReportPage = lazy(() => import('./pages/reports/shipment- // Product Comments Page const ProductCommentsListPage = lazy(() => import('./pages/products/comments/comments-list/ProductCommentsListPage')); -const ProtectedRoute = ({ children }: { children: any }) => { +const ProtectedRoute = ({ children }: { children: React.ReactElement }) => { const { user, isLoading } = useAuth(); if (isLoading) { return ( - -
- -
-
+ ); } @@ -208,11 +204,9 @@ const App = () => { -
- -
-
+
+ +
}> diff --git a/src/components/common/ActionButtons.tsx b/src/components/common/ActionButtons.tsx new file mode 100644 index 0000000..3a29acc --- /dev/null +++ b/src/components/common/ActionButtons.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Eye, Edit3, Trash2, LucideIcon } from 'lucide-react'; + +interface ActionButtonsProps { + onView?: () => void; + onEdit?: () => void; + onDelete?: () => void; + viewTitle?: string; + editTitle?: string; + deleteTitle?: string; + className?: string; + size?: 'sm' | 'md' | 'lg'; + showLabels?: boolean; +} + +const getSizeClasses = (size: 'sm' | 'md' | 'lg') => { + switch (size) { + case 'sm': + return 'h-3 w-3'; + case 'md': + return 'h-4 w-4'; + case 'lg': + return 'h-5 w-5'; + default: + return 'h-4 w-4'; + } +}; + +const getTextSizeClasses = (size: 'sm' | 'md' | 'lg') => { + switch (size) { + case 'sm': + return 'text-xs'; + case 'md': + return 'text-xs'; + case 'lg': + return 'text-sm'; + default: + return 'text-xs'; + } +}; + +export const ActionButtons: React.FC = ({ + onView, + onEdit, + onDelete, + viewTitle = 'مشاهده', + editTitle = 'ویرایش', + deleteTitle = 'حذف', + className = '', + size = 'md', + showLabels = false, +}) => { + const iconSize = getSizeClasses(size); + const textSize = getTextSizeClasses(size); + + return ( +
+ {onView && ( + + )} + {onEdit && ( + + )} + {onDelete && ( + + )} +
+ ); +}; + diff --git a/src/components/common/DeleteConfirmModal.tsx b/src/components/common/DeleteConfirmModal.tsx new file mode 100644 index 0000000..f4dd40e --- /dev/null +++ b/src/components/common/DeleteConfirmModal.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Modal } from '../ui/Modal'; +import { Button } from '../ui/Button'; + +interface DeleteConfirmModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title?: string; + message?: string; + warningMessage?: string; + isLoading?: boolean; + itemName?: string; +} + +export const DeleteConfirmModal: React.FC = ({ + isOpen, + onClose, + onConfirm, + title = 'حذف', + message, + warningMessage, + isLoading = false, + itemName, +}) => { + const defaultMessage = itemName + ? `آیا از حذف "${itemName}" اطمینان دارید؟ این عمل قابل بازگشت نیست.` + : 'آیا از حذف این مورد اطمینان دارید؟ این عمل قابل بازگشت نیست.'; + + return ( + +
+

+ {message || defaultMessage} +

+ {warningMessage && ( +

+ {warningMessage} +

+ )} +
+ + +
+
+
+ ); +}; + diff --git a/src/components/common/EmptyState.tsx b/src/components/common/EmptyState.tsx new file mode 100644 index 0000000..4fd692b --- /dev/null +++ b/src/components/common/EmptyState.tsx @@ -0,0 +1,46 @@ + +import React, { ReactNode } from 'react'; +import { LucideIcon } from 'lucide-react'; +import { Button } from '../ui/Button'; + +interface EmptyStateProps { + icon?: LucideIcon; + title: string; + description?: string; + actionLabel?: ReactNode; + onAction?: () => void; + className?: string; +} + +export const EmptyState: React.FC = ({ + icon: Icon, + title, + description, + actionLabel, + onAction, + className = '', +}) => { + return ( +
+ {Icon && ( + + )} +

+ {title} +

+ {description && ( +

+ {description} +

+ )} + {actionLabel && onAction && ( +
+ +
+ )} +
+ ); +}; + diff --git a/src/components/common/FiltersSection.tsx b/src/components/common/FiltersSection.tsx new file mode 100644 index 0000000..3e20ffb --- /dev/null +++ b/src/components/common/FiltersSection.tsx @@ -0,0 +1,42 @@ +import React, { ReactNode } from 'react'; + +interface FiltersSectionProps { + children: ReactNode; + isLoading?: boolean; + columns?: 1 | 2 | 3 | 4; + className?: string; +} + +export const FiltersSection: React.FC = ({ + children, + isLoading = false, + columns = 4, + className = '', +}) => { + const gridCols = { + 1: 'grid-cols-1', + 2: 'grid-cols-1 md:grid-cols-2', + 3: 'grid-cols-1 md:grid-cols-3', + 4: 'grid-cols-1 md:grid-cols-4', + }; + + return ( +
+ {isLoading ? ( +
+ {[...Array(columns)].map((_, i) => ( +
+
+
+
+ ))} +
+ ) : ( +
+ {children} +
+ )} +
+ ); +}; + diff --git a/src/components/common/TableSkeleton.tsx b/src/components/common/TableSkeleton.tsx new file mode 100644 index 0000000..ce2a647 --- /dev/null +++ b/src/components/common/TableSkeleton.tsx @@ -0,0 +1,80 @@ +import React from 'react'; + +interface TableSkeletonProps { + columns?: number; + rows?: number; + showMobileCards?: boolean; + className?: string; +} + +export const TableSkeleton: React.FC = ({ + columns = 5, + rows = 5, + showMobileCards = true, + className = '', +}) => { + return ( +
+ {/* Desktop Table Skeleton */} +
+
+ + + + {[...Array(columns)].map((_, i) => ( + + ))} + + + + {[...Array(rows)].map((_, rowIndex) => ( + + {[...Array(columns)].map((_, colIndex) => ( + + ))} + + ))} + +
+
+
+ {colIndex === columns - 1 ? ( +
+
+
+
+ ) : ( +
+ )} +
+
+
+ + {/* Mobile Cards Skeleton */} + {showMobileCards && ( +
+ {[...Array(Math.min(rows, 3))].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ )} +
+ ); +}; + diff --git a/src/components/forms/FormActions.tsx b/src/components/forms/FormActions.tsx new file mode 100644 index 0000000..19e3fe9 --- /dev/null +++ b/src/components/forms/FormActions.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button } from '../ui/Button'; + +interface FormActionsProps { + onCancel?: () => void; + cancelLabel?: string; + submitLabel?: string; + isLoading?: boolean; + isDisabled?: boolean; + className?: string; +} + +export const FormActions: React.FC = ({ + onCancel, + cancelLabel = 'انصراف', + submitLabel = 'ذخیره', + isLoading = false, + isDisabled = false, + className = '', +}) => { + return ( +
+ {onCancel && ( + + )} + +
+ ); +}; + diff --git a/src/components/forms/FormSection.tsx b/src/components/forms/FormSection.tsx new file mode 100644 index 0000000..43ca1d4 --- /dev/null +++ b/src/components/forms/FormSection.tsx @@ -0,0 +1,26 @@ +import React, { ReactNode } from 'react'; +import { SectionTitle } from '../ui/Typography'; + +interface FormSectionProps { + title: string; + children: ReactNode; + className?: string; + titleClassName?: string; +} + +export const FormSection: React.FC = ({ + title, + children, + className = '', + titleClassName = '', +}) => { + return ( +
+ + {title} + + {children} +
+ ); +}; + diff --git a/src/components/layout/PageHeader.tsx b/src/components/layout/PageHeader.tsx new file mode 100644 index 0000000..c905f96 --- /dev/null +++ b/src/components/layout/PageHeader.tsx @@ -0,0 +1,40 @@ +import React, { ReactNode } from 'react'; +import { LucideIcon } from 'lucide-react'; + +interface PageHeaderProps { + title: string; + subtitle?: string; + icon?: LucideIcon; + actions?: ReactNode; + className?: string; +} + +export const PageHeader: React.FC = ({ + title, + subtitle, + icon: Icon, + actions, + className = '', +}) => { + return ( +
+
+

+ {Icon && } + {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+ {actions && ( +
+ {actions} +
+ )} +
+ ); +}; + diff --git a/src/components/ui/StatusBadge.tsx b/src/components/ui/StatusBadge.tsx new file mode 100644 index 0000000..8707a23 --- /dev/null +++ b/src/components/ui/StatusBadge.tsx @@ -0,0 +1,215 @@ +import React from 'react'; + +export type StatusType = 'product' | 'order' | 'user' | 'discount' | 'comment' | 'generic'; + +export type ProductStatus = 'active' | 'inactive' | 'draft'; +export type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'refunded'; +export type UserStatus = 'verified' | 'unverified' | boolean; +export type DiscountStatus = 'active' | 'inactive'; +export type CommentStatus = 'approved' | 'rejected' | 'pending'; + +export type StatusValue = ProductStatus | OrderStatus | UserStatus | DiscountStatus | CommentStatus | string; + +interface StatusBadgeProps { + status: StatusValue; + type?: StatusType; + className?: string; + size?: 'sm' | 'md' | 'lg'; +} + +const getStatusConfig = (status: StatusValue, type?: StatusType) => { + // Handle boolean status (for verified/unverified) + if (typeof status === 'boolean') { + return { + color: status + ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' + : 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + text: status ? 'تأیید شده' : 'تأیید نشده', + }; + } + + const statusStr = String(status).toLowerCase(); + + switch (type) { + case 'product': + switch (statusStr) { + case 'active': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + text: 'فعال', + }; + case 'inactive': + return { + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + text: 'غیرفعال', + }; + case 'draft': + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: 'پیش‌نویس', + }; + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: statusStr, + }; + } + + case 'order': + switch (statusStr) { + case 'pending': + return { + color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + text: 'در انتظار', + }; + case 'processing': + return { + color: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', + text: 'در حال پردازش', + }; + case 'shipped': + return { + color: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200', + text: 'ارسال شده', + }; + case 'delivered': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + text: 'تحویل شده', + }; + case 'cancelled': + return { + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + text: 'لغو شده', + }; + case 'refunded': + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: 'مرجوع شده', + }; + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: statusStr, + }; + } + + case 'user': + switch (statusStr) { + case 'verified': + case 'true': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + text: 'تأیید شده', + }; + case 'unverified': + case 'false': + return { + color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + text: 'تأیید نشده', + }; + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: statusStr, + }; + } + + case 'discount': + switch (statusStr) { + case 'active': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + text: 'فعال', + }; + case 'inactive': + return { + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + text: 'غیرفعال', + }; + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: statusStr, + }; + } + + case 'comment': + switch (statusStr) { + case 'approved': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + text: 'تأیید شده', + }; + case 'rejected': + return { + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + text: 'رد شده', + }; + case 'pending': + return { + color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + text: 'در انتظار', + }; + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: statusStr, + }; + } + + default: + // Generic status handling + switch (statusStr) { + case 'active': + case 'true': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + text: 'فعال', + }; + case 'inactive': + case 'false': + return { + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', + text: 'غیرفعال', + }; + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', + text: statusStr, + }; + } + } +}; + +const getSizeClasses = (size: 'sm' | 'md' | 'lg') => { + switch (size) { + case 'sm': + return 'px-2 py-0.5 text-xs'; + case 'md': + return 'px-2.5 py-0.5 text-xs'; + case 'lg': + return 'px-3 py-1 text-sm'; + default: + return 'px-2.5 py-0.5 text-xs'; + } +}; + +export const StatusBadge: React.FC = ({ + status, + type = 'generic', + className = '', + size = 'md', +}) => { + const config = getStatusConfig(status, type); + const sizeClasses = getSizeClasses(size); + + return ( + + {config.text} + + ); +}; + diff --git a/src/pages/categories/categories-list/CategoriesListPage.tsx b/src/pages/categories/categories-list/CategoriesListPage.tsx index 1d27ff6..5891bfb 100644 --- a/src/pages/categories/categories-list/CategoriesListPage.tsx +++ b/src/pages/categories/categories-list/CategoriesListPage.tsx @@ -3,58 +3,15 @@ import { useNavigate } from 'react-router-dom'; import { useCategories, useDeleteCategory } from '../core/_hooks'; import { Category } from '../core/_models'; import { Button } from "@/components/ui/Button"; - -import { Trash2, Edit3, Plus, FolderOpen, Folder } from "lucide-react"; -import { Modal } from "@/components/ui/Modal"; -import { PageContainer, PageTitle, SectionSubtitle } from "../../../components/ui/Typography"; - -const CategoriesTableSkeleton = () => ( -
-
-
- - - - - - - - - - - {[...Array(5)].map((_, i) => ( - - - - - - - ))} - -
- نام دسته‌بندی - - توضیحات - - تاریخ ایجاد - - عملیات -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-); +import { Plus, FolderOpen, Folder } from "lucide-react"; +import { PageContainer } from "../../../components/ui/Typography"; +import { PageHeader } from "@/components/layout/PageHeader"; +import { FiltersSection } from "@/components/common/FiltersSection"; +import { TableSkeleton } from "@/components/common/TableSkeleton"; +import { EmptyState } from "@/components/common/EmptyState"; +import { DeleteConfirmModal } from "@/components/common/DeleteConfirmModal"; +import { ActionButtons } from "@/components/common/ActionButtons"; +import { formatDate } from "@/utils/formatters"; const CategoriesListPage = () => { const navigate = useNavigate(); @@ -98,48 +55,57 @@ const CategoriesListPage = () => { ); } + const createButton = ( + + ); + return ( - {/* Header */} -
+ + +
-
- - مدیریت دسته‌بندی‌ها -
-

مدیریت دسته‌بندی‌های محصولات

+ +
+
- -
- - {/* Filters */} -
-
-
- - -
-
-
- - {/* Categories Table */} {isLoading ? ( - + + ) : (!categories || categories.length === 0) ? ( +
+ + + ایجاد دسته‌بندی جدید + + } + onAction={handleCreate} + /> +
) : (
{/* Desktop Table */} @@ -177,25 +143,13 @@ const CategoriesListPage = () => {
- {new Date(category.created_at).toLocaleDateString('fa-IR')} + {formatDate(category.created_at)} -
- - -
+ handleEdit(category.id)} + onDelete={() => setDeleteCategoryId(category.id.toString())} + /> ))} @@ -220,77 +174,28 @@ const CategoriesListPage = () => {
- تاریخ ایجاد: {new Date(category.created_at).toLocaleDateString('fa-IR')} -
-
- - + تاریخ ایجاد: {formatDate(category.created_at)}
+ handleEdit(category.id)} + onDelete={() => setDeleteCategoryId(category.id.toString())} + showLabels={true} + size="sm" + /> ))} - - {/* Empty State */} - {(!categories || categories.length === 0) && !isLoading && ( -
- -

- دسته‌بندی‌ای موجود نیست -

-

- برای شروع، اولین دسته‌بندی محصولات خود را ایجاد کنید. -

-
- -
-
- )} )} - {/* Delete Confirmation Modal */} - setDeleteCategoryId(null)} + onConfirm={handleDeleteConfirm} title="حذف دسته‌بندی" - > -
-

- آیا از حذف این دسته‌بندی اطمینان دارید؟ این عمل قابل بازگشت نیست و ممکن است بر محصولاتی که در این دسته‌بندی قرار دارند تأثیر بگذارد. -

-
- - -
-
-
+ message="آیا از حذف این دسته‌بندی اطمینان دارید؟ این عمل قابل بازگشت نیست و ممکن است بر محصولاتی که در این دسته‌بندی قرار دارند تأثیر بگذارد." + isLoading={isDeleting} + />
); }; diff --git a/src/pages/discount-codes/discount-codes-list/DiscountCodesListPage.tsx b/src/pages/discount-codes/discount-codes-list/DiscountCodesListPage.tsx index e5b938a..e3a48e0 100644 --- a/src/pages/discount-codes/discount-codes-list/DiscountCodesListPage.tsx +++ b/src/pages/discount-codes/discount-codes-list/DiscountCodesListPage.tsx @@ -2,11 +2,17 @@ import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useDiscountCodes, useDeleteDiscountCode } from '../core/_hooks'; import { DiscountCode } from '../core/_models'; -import { Button } from "@/components/ui/Button"; -import { Modal } from "@/components/ui/Modal"; import { Table } from "@/components/ui/Table"; import { TableColumn } from "@/types"; -import { Percent, BadgePercent, Trash2, Edit3, Plus, Ticket } from 'lucide-react'; +import { BadgePercent, Plus, Ticket } from 'lucide-react'; +import { PageContainer } from "@/components/ui/Typography"; +import { PageHeader } from "@/components/layout/PageHeader"; +import { FiltersSection } from "@/components/common/FiltersSection"; +import { EmptyState } from "@/components/common/EmptyState"; +import { DeleteConfirmModal } from "@/components/common/DeleteConfirmModal"; +import { ActionButtons } from "@/components/common/ActionButtons"; +import { StatusBadge } from "@/components/ui/StatusBadge"; +import { formatDate } from "@/utils/formatters"; const DiscountCodesListPage = () => { const navigate = useNavigate(); @@ -41,18 +47,14 @@ const DiscountCodesListPage = () => { { key: 'status', label: 'وضعیت', - render: (val: string) => ( - - {val === 'active' ? 'فعال' : 'غیرفعال'} - - ) + render: (val: string) => }, { key: 'period', label: 'بازه زمانی', render: (_val, row: any) => ( - {row.valid_from ? new Date(row.valid_from).toLocaleDateString('fa-IR') : '-'} تا {row.valid_to ? new Date(row.valid_to).toLocaleDateString('fa-IR') : '-'} + {row.valid_from ? formatDate(row.valid_from) : '-'} تا {row.valid_to ? formatDate(row.valid_to) : '-'} ) }, @@ -60,22 +62,10 @@ const DiscountCodesListPage = () => { key: 'actions', label: 'عملیات', render: (_val, row: any) => ( -
- - -
+ handleEdit(row.id)} + onDelete={() => setDeleteId(row.id.toString())} + /> ) } ], [navigate]); @@ -90,28 +80,28 @@ const DiscountCodesListPage = () => { ); } - return ( -
-
-
-

- - مدیریت کدهای تخفیف -

-

ایجاد و مدیریت کدهای تخفیف

-
- -
+ const createButton = ( + + ); -
-
+ return ( + +
+ + +
{ className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" />
-
+ + + {isLoading ? ( + + ) : !discountCodes || discountCodes.length === 0 ? ( +
+ + + ایجاد کد تخفیف + + } + onAction={handleCreate} + /> +
+ ) : ( +
+ )} + + setDeleteId(null)} + onConfirm={handleDeleteConfirm} + title="حذف کد تخفیف" + message="آیا از حذف این کد تخفیف اطمینان دارید؟ این عمل قابل بازگشت نیست." + isLoading={isDeleting} + /> - - {isLoading ? ( -
- ) : !discountCodes || discountCodes.length === 0 ? ( -
-
- -

هیچ کد تخفیفی یافت نشد

-

برای شروع یک کد تخفیف ایجاد کنید

- -
-
- ) : ( -
- )} - - setDeleteId(null)} title="حذف کد تخفیف"> -
-

آیا از حذف این کد تخفیف اطمینان دارید؟ این عمل قابل بازگشت نیست.

-
- - -
-
-
- + ); }; diff --git a/src/pages/orders/orders-list/OrdersListPage.tsx b/src/pages/orders/orders-list/OrdersListPage.tsx index 7606489..c6d3010 100644 --- a/src/pages/orders/orders-list/OrdersListPage.tsx +++ b/src/pages/orders/orders-list/OrdersListPage.tsx @@ -6,7 +6,7 @@ import { OrderFilters, OrderStatus } from '../core/_models'; import { Button } from "@/components/ui/Button"; import { Modal } from "@/components/ui/Modal"; import { Pagination } from "@/components/ui/Pagination"; -import { PageContainer, PageTitle } from "@/components/ui/Typography"; +import { PageContainer } from "@/components/ui/Typography"; import { Table } from "@/components/ui/Table"; import { TableColumn } from "@/types"; import { StatsCard } from '@/components/dashboard/StatsCard'; @@ -20,46 +20,16 @@ import { Clock, Search, Filter, - Eye, Edit3, TrendingUp } from 'lucide-react'; +import { PageHeader } from '@/components/layout/PageHeader'; +import { FiltersSection } from '@/components/common/FiltersSection'; +import { EmptyState } from '@/components/common/EmptyState'; +import { ActionButtons } from '@/components/common/ActionButtons'; +import { StatusBadge } from '@/components/ui/StatusBadge'; +import { formatCurrency, formatDate } from '@/utils/formatters'; -const getStatusColor = (status: OrderStatus) => { - const colors = { - pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', - processing: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', - shipped: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200', - delivered: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', - cancelled: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', - refunded: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', - }; - return colors[status] || colors.pending; -}; - -const getStatusText = (status: OrderStatus) => { - const text = { - pending: 'در انتظار', - processing: 'در حال پردازش', - shipped: 'ارسال شده', - delivered: 'تحویل شده', - cancelled: 'لغو شده', - refunded: 'مرجوع شده', - }; - return text[status] || status; -}; - -const formatCurrency = (amount: number) => { - return new Intl.NumberFormat('fa-IR').format(amount) + ' تومان'; -}; - -const formatDate = (dateString: string) => { - return new Date(dateString).toLocaleDateString('fa-IR'); -}; - -const ListSkeleton = () => ( -
-); const getDefaultFilters = (): OrderFilters => ({ page: 1, @@ -219,7 +189,7 @@ const OrdersListPage = () => { ) }, { key: 'final_total', label: 'مبلغ نهایی', sortable: true, align: 'right', render: (v: number, row: any) => formatCurrency(row.final_total || row.total_amount || 0) }, - { key: 'status', label: 'وضعیت', align: 'right', render: (v: OrderStatus) => ({getStatusText(v)}) }, + { key: 'status', label: 'وضعیت', align: 'right', render: (v: OrderStatus) => }, { key: 'created_at', label: 'تاریخ', sortable: true, align: 'right', render: (v: string) => formatDate(v) }, { key: 'actions', @@ -227,13 +197,9 @@ const OrdersListPage = () => { align: 'right', render: (_val, row: any) => (
- + handleViewOrder(row.id)} + />
) : !ordersData?.orders || ordersData.orders.length === 0 ? (
-
- -

هیچ سفارشی یافت نشد

-

با تغییر فیلترها جستجو کنید

-
+
) : ( <> diff --git a/src/pages/products/product-form/ProductFormPage.tsx b/src/pages/products/product-form/ProductFormPage.tsx index ea33299..182a363 100644 --- a/src/pages/products/product-form/ProductFormPage.tsx +++ b/src/pages/products/product-form/ProductFormPage.tsx @@ -14,7 +14,9 @@ import { Input } from "@/components/ui/Input"; import { FileUploader } from "@/components/ui/FileUploader"; import { VariantManager } from "@/components/ui/VariantManager"; import { ArrowRight, X } from "lucide-react"; -import { FormHeader, PageContainer, SectionTitle, Label } from '../../../components/ui/Typography'; +import { FormHeader, PageContainer, Label } from '../../../components/ui/Typography'; +import { FormSection } from '@/components/forms/FormSection'; +import { FormActions } from '@/components/forms/FormActions'; import { createNumberTransform, createOptionalNumberTransform, convertPersianNumbersInObject } from '../../../utils/numberUtils'; import { API_GATE_WAY, API_ROUTES } from '@/constant/routes'; import { toast } from "react-hot-toast"; @@ -471,11 +473,7 @@ const ProductFormPage = () => { {/* Form */}
- {/* Basic Information */} -
-

- اطلاعات پایه -

+
{ )}
-
+ - {/* Categories and Product Options */} -
-

- دسته‌بندی و گزینه‌ها -

+
{ )}
-
+ - {/* Images */} -
-

- تصاویر محصول -

+ {
)} - + -
-

- فایل‌های Explorer -

+ {
)} - + - {/* Variants Management */} -
+ setValue('variants', variants, { shouldValidate: true, shouldDirty: true })} productOptions={productOptionOptions} variantAttributeName={watch('variant_attribute_name')} /> -
- - + {/* Preview */} {formValues.name && ( @@ -797,24 +781,13 @@ const ProductFormPage = () => { )} - {/* Submit Buttons */} -
- - -
+ diff --git a/src/pages/products/products-list/ProductsListPage.tsx b/src/pages/products/products-list/ProductsListPage.tsx index 486dd9a..946bddc 100644 --- a/src/pages/products/products-list/ProductsListPage.tsx +++ b/src/pages/products/products-list/ProductsListPage.tsx @@ -5,64 +5,17 @@ import { useCategories } from '../../categories/core/_hooks'; import { Product } from '../core/_models'; import { Button } from "@/components/ui/Button"; import { PageContainer } from "@/components/ui/Typography"; -import { Trash2, Edit3, Plus, Package, Eye, Image } from "lucide-react"; -import { Modal } from "@/components/ui/Modal"; +import { Plus, Package, Image } from "lucide-react"; import { persianToEnglish } from '../../../utils/numberUtils'; import { Pagination } from "@/components/ui/Pagination"; - -const ProductsTableSkeleton = () => ( -
-
-
-
- - - - - - - - - - - {[...Array(5)].map((_, i) => ( - - - - - - - - ))} - -
- محصول - - قیمت - - دسته‌بندی - - وضعیت - - عملیات -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-); +import { PageHeader } from "@/components/layout/PageHeader"; +import { FiltersSection } from "@/components/common/FiltersSection"; +import { TableSkeleton } from "@/components/common/TableSkeleton"; +import { EmptyState } from "@/components/common/EmptyState"; +import { DeleteConfirmModal } from "@/components/common/DeleteConfirmModal"; +import { ActionButtons } from "@/components/common/ActionButtons"; +import { StatusBadge } from "@/components/ui/StatusBadge"; +import { formatPrice } from "@/utils/formatters"; const ProductsListPage = () => { const navigate = useNavigate(); @@ -122,29 +75,12 @@ const ProductsListPage = () => { setFilters(prev => ({ ...prev, status: e.target.value, page: 1 })); }; - const formatPrice = (price: number) => { - return new Intl.NumberFormat('fa-IR').format(price) + ' تومان'; - }; - const getFirstImageUrl = (p: any): string | null => { if (p.file_ids && p.file_ids.length > 0) return p.file_ids[0].url; if (p.files && p.files.length > 0) return p.files[0].url; return null; }; - const getStatusBadge = (status: string) => { - switch (status) { - case 'active': - return فعال; - case 'inactive': - return غیرفعال; - case 'draft': - return پیش‌نویس; - default: - return {status}; - } - }; - const total = productsData?.total || 0; const currentPage = productsData?.page || filters.page; const perPage = productsData?.per_page || filters.limit; @@ -160,129 +96,119 @@ const ProductsListPage = () => { ); } + const createButton = ( + + ); + return (
- {/* Header */} -
+ + +
-

- - مدیریت محصولات -

-

- مدیریت محصولات، قیمت‌ها و موجودی -

+ +
- -
- - {/* Filters */} -
- {isLoading ? ( -
- {[...Array(4)].map((_, i) => ( -
-
- {i === 3 ? ( -
-
-
-
- ) : ( -
- )} -
+
+ + +
+
+ + +
+
+ +
+ { + const converted = persianToEnglish(e.target.value); + setFilters(prev => ({ ...prev, min_price: converted, page: 1 })); + }} + className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200" + /> + { + const converted = persianToEnglish(e.target.value); + setFilters(prev => ({ ...prev, max_price: converted, page: 1 })); + }} + className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200" + />
- ) : ( -
-
- - -
-
- - -
-
- - -
-
- -
- { - const converted = persianToEnglish(e.target.value); - setFilters(prev => ({ ...prev, min_price: converted, page: 1 })); - }} - className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200" - /> - { - const converted = persianToEnglish(e.target.value); - setFilters(prev => ({ ...prev, max_price: converted, page: 1 })); - }} - className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200" - /> -
-
-
- )} -
+
+ - {/* Products Table */} {isLoading ? ( - + + ) : products.length === 0 ? ( +
+ + + ایجاد محصول جدید + + } + onAction={handleCreate} + /> +
) : (
{/* Desktop Table */} @@ -345,32 +271,14 @@ const ProductsListPage = () => { {product.category?.name || 'بدون دسته‌بندی'} - {getStatusBadge(product.status || '')} + -
- - - -
+ handleView(product.id)} + onEdit={() => handleEdit(product.id)} + onDelete={() => setDeleteProductId(product.id.toString())} + /> ))} @@ -405,7 +313,7 @@ const ProductsListPage = () => { {formatPrice(product.price || 0)}

- {getStatusBadge(product.status || '')} + {product.category && ( {product.category.name} @@ -414,51 +322,16 @@ const ProductsListPage = () => {
-
- - - -
+ handleView(product.id)} + onEdit={() => handleEdit(product.id)} + onDelete={() => setDeleteProductId(product.id.toString())} + showLabels={true} + size="sm" + />
))} - - {/* Empty State */} - {(!products || products.length === 0) && !isLoading && ( -
- -

- محصولی موجود نیست -

-

- برای شروع، اولین محصول خود را ایجاد کنید. -

-
- -
-
- )} )} @@ -471,34 +344,14 @@ const ProductsListPage = () => { onPageChange={(page) => setFilters(prev => ({ ...prev, page }))} /> - {/* Delete Confirmation Modal */} - setDeleteProductId(null)} + onConfirm={handleDeleteConfirm} title="حذف محصول" - > -
-

- آیا از حذف این محصول اطمینان دارید؟ این عمل قابل بازگشت نیست و تمام اطلاعات مربوط به محصول از جمله نسخه‌ها و تصاویر حذف خواهد شد. -

-
- - -
-
-
+ message="آیا از حذف این محصول اطمینان دارید؟ این عمل قابل بازگشت نیست و تمام اطلاعات مربوط به محصول از جمله نسخه‌ها و تصاویر حذف خواهد شد." + isLoading={isDeleting} + />
); diff --git a/src/pages/roles/role-detail/RoleDetailPage.tsx b/src/pages/roles/role-detail/RoleDetailPage.tsx index 885c9d6..69fe3b5 100644 --- a/src/pages/roles/role-detail/RoleDetailPage.tsx +++ b/src/pages/roles/role-detail/RoleDetailPage.tsx @@ -59,7 +59,7 @@ const RoleDetailPage = () => { if (!role) return
نقش یافت نشد
; return ( -
+
diff --git a/src/pages/roles/roles-list/RolesListPage.tsx b/src/pages/roles/roles-list/RolesListPage.tsx index 821af7f..3d420bc 100644 --- a/src/pages/roles/roles-list/RolesListPage.tsx +++ b/src/pages/roles/roles-list/RolesListPage.tsx @@ -3,79 +3,15 @@ import { useNavigate } from 'react-router-dom'; import { useRoles, useDeleteRole } from '../core/_hooks'; import { Role } from '@/types/auth'; import { Button } from "@/components/ui/Button"; - -import { Trash2, Edit3, Plus, UserCog, Shield, Eye, Settings } from "lucide-react"; -import { Modal } from "@/components/ui/Modal"; -import { PageContainer, PageTitle, SectionSubtitle } from '../../../components/ui/Typography'; - -// Skeleton Loading Component -const RolesTableSkeleton = () => ( -
- {/* Desktop Table Skeleton */} -
-
- - - - - - - - - - - {[...Array(5)].map((_, index) => ( - - - - - - - ))} - -
- نام نقش - - توضیحات - - تاریخ ایجاد - - عملیات -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - {/* Mobile Cards Skeleton */} -
- {[...Array(3)].map((_, index) => ( -
-
-
-
-
-
-
-
-
-
-
-
- ))} -
-
-); +import { Plus, Shield, Eye, Settings } from "lucide-react"; +import { PageContainer } from '../../../components/ui/Typography'; +import { PageHeader } from '@/components/layout/PageHeader'; +import { FiltersSection } from '@/components/common/FiltersSection'; +import { TableSkeleton } from '@/components/common/TableSkeleton'; +import { EmptyState } from '@/components/common/EmptyState'; +import { DeleteConfirmModal } from '@/components/common/DeleteConfirmModal'; +import { ActionButtons } from '@/components/common/ActionButtons'; +import { formatDate } from '@/utils/formatters'; const RolesListPage = () => { const navigate = useNavigate(); @@ -135,66 +71,58 @@ const RolesListPage = () => { ); } + const createButton = ( + + ); + return ( - {/* Header */} -
+ + +
-
- - مدیریت نقش‌ها -
-

مدیریت نقش‌ها و دسترسی‌های سیستم

+ +
+
- -
- - {/* Filters */} -
-
-
- - -
-
-
- - {/* Roles Table */} {isLoading ? ( - + ) : (roles || []).length === 0 ? (
-
- -

- هیچ نقش یافت نشد -

-

- {filters.search - ? "نتیجه‌ای برای جستجوی شما یافت نشد" - : "شما هنوز هیچ نقش ایجاد نکرده‌اید" - } -

- -
+ + + اولین نقش را ایجاد کنید + + } + onAction={handleCreate} + />
) : (
@@ -228,24 +156,15 @@ const RolesListPage = () => { {role.description} - {new Date(role.created_at).toLocaleDateString('fa-IR')} + {formatDate(role.created_at)}
- - + handleView(role.id)} + onEdit={() => handleEdit(role.id)} + onDelete={() => setDeleteRoleId(role.id.toString())} + /> -
@@ -284,23 +196,16 @@ const RolesListPage = () => {
- تاریخ ایجاد: {new Date(role.created_at).toLocaleDateString('fa-IR')} + تاریخ ایجاد: {formatDate(role.created_at)}
- - + handleView(role.id)} + onEdit={() => handleEdit(role.id)} + onDelete={() => setDeleteRoleId(role.id.toString())} + showLabels={true} + size="sm" + /> -
))} @@ -322,31 +220,14 @@ const RolesListPage = () => {
)} - {/* Delete Confirmation Modal */} - -
-

- آیا از حذف این نقش اطمینان دارید؟ این عمل قابل بازگشت نیست. -

-
- - -
-
-
+ message="آیا از حذف این نقش اطمینان دارید؟ این عمل قابل بازگشت نیست." + isLoading={isDeleting} + />
); }; diff --git a/src/pages/shipping-methods/shipping-method-form/ShippingMethodFormPage.tsx b/src/pages/shipping-methods/shipping-method-form/ShippingMethodFormPage.tsx index 0bccc97..e42bf8e 100644 --- a/src/pages/shipping-methods/shipping-method-form/ShippingMethodFormPage.tsx +++ b/src/pages/shipping-methods/shipping-method-form/ShippingMethodFormPage.tsx @@ -5,6 +5,7 @@ import { ShippingOpenHour } from '../core/_models'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { TagInput } from '@/components/ui/TagInput'; +import { PageContainer } from '@/components/ui/Typography'; import { Truck } from 'lucide-react'; import { formatWithThousands, parseFormattedNumber } from '@/utils/numberUtils'; diff --git a/src/pages/users-admin/users-admin-list/UsersAdminListPage.tsx b/src/pages/users-admin/users-admin-list/UsersAdminListPage.tsx index 70c8d1a..13c2c4a 100644 --- a/src/pages/users-admin/users-admin-list/UsersAdminListPage.tsx +++ b/src/pages/users-admin/users-admin-list/UsersAdminListPage.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Users, Plus, Search, Filter, UserCheck, UserX, Edit, Trash2, Eye, User as UserIcon } from 'lucide-react'; +import { Users, Plus, Search, Filter, UserCheck, UserX, Eye, Edit, Trash2, User as UserIcon } from 'lucide-react'; import { useSearchUsers, useUserStats, useVerifyUser, useUnverifyUser, useDeleteUser } from '../core/_hooks'; import { User, UserFilters } from '../core/_models'; import { PageContainer } from '../../../components/ui/Typography'; @@ -12,6 +12,10 @@ import { StatsCard } from '../../../components/dashboard/StatsCard'; import { Table } from '../../../components/ui/Table'; import { TableColumn } from '../../../types'; import { englishToPersian, persianToEnglish } from '../../../utils/numberUtils'; +import { PageHeader } from '@/components/layout/PageHeader'; +import { FiltersSection } from '@/components/common/FiltersSection'; +import { DeleteConfirmModal } from '@/components/common/DeleteConfirmModal'; +import { StatusBadge } from '@/components/ui/StatusBadge'; const UsersAdminListPage: React.FC = () => { const navigate = useNavigate(); @@ -163,14 +167,7 @@ const UsersAdminListPage: React.FC = () => { key: 'verified', label: 'وضعیت', align: 'center', - render: (v: boolean) => ( - - {v ? 'تأیید شده' : 'تأیید نشده'} - - ) + render: (v: boolean) => }, { key: 'actions', @@ -231,17 +228,11 @@ const UsersAdminListPage: React.FC = () => { return (
- {/* Header */} -
-
-

- - مدیریت کاربران -

-

مشاهده و مدیریت کاربران سیستم

-
- -
+ {/* Stats Cards */} {stats && ( @@ -273,47 +264,44 @@ const UsersAdminListPage: React.FC = () => {
)} - {/* Filters */} -
-
- setSearchTerm(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleSearch()} - data-testid="search-users-input" - /> - setSearchTerm(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSearch()} + data-testid="search-users-input" + /> + +
+ - -
+ + جستجو + +
-
+ {/* Users Table */}
@@ -346,36 +334,15 @@ const UsersAdminListPage: React.FC = () => { /> )} - {/* Delete Confirmation Modal */} - setDeleteModal({ isOpen: false, user: null })} + onConfirm={handleDeleteConfirm} title="حذف کاربر" - > -
-

- آیا از حذف کاربر "{deleteModal.user?.first_name} {deleteModal.user?.last_name}" اطمینان دارید؟ -

-

- این عمل غیرقابل بازگشت است. -

-
- - -
-
-
+ message={`آیا از حذف کاربر "${deleteModal.user?.first_name} ${deleteModal.user?.last_name}" اطمینان دارید؟`} + warningMessage="این عمل غیرقابل بازگشت است." + isLoading={deleteUserMutation.isPending} + /> {/* Verify/Unverify Confirmation Modal */} { + if (price === null || price === undefined || isNaN(price)) { + return '0 تومان'; + } + return new Intl.NumberFormat('fa-IR').format(price) + ' تومان'; +}; + +/** + * Format currency amount with Persian number formatting and تومان suffix + */ +export const formatCurrency = (amount: number): string => { + if (amount === null || amount === undefined || isNaN(amount)) { + return '0 تومان'; + } + return new Intl.NumberFormat('fa-IR').format(amount) + ' تومان'; +}; + +/** + * Format date string to Persian locale + */ +export const formatDate = (dateString: string | Date): string => { + if (!dateString) return '-'; + + try { + const date = typeof dateString === 'string' ? new Date(dateString) : dateString; + if (isNaN(date.getTime())) return '-'; + + return date.toLocaleDateString('fa-IR'); + } catch { + return '-'; + } +}; + +/** + * Format date and time string to Persian locale + */ +export const formatDateTime = (dateString: string | Date): string => { + if (!dateString) return '-'; + + try { + const date = typeof dateString === 'string' ? new Date(dateString) : dateString; + if (isNaN(date.getTime())) return '-'; + + return date.toLocaleDateString('fa-IR', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + } catch { + return '-'; + } +}; + +/** + * Format number with thousands separator (Persian) + */ +export const formatNumber = (num: number): string => { + if (num === null || num === undefined || isNaN(num)) { + return '0'; + } + return new Intl.NumberFormat('fa-IR').format(num); +}; + diff --git a/src/utils/statusUtils.ts b/src/utils/statusUtils.ts new file mode 100644 index 0000000..897c123 --- /dev/null +++ b/src/utils/statusUtils.ts @@ -0,0 +1,185 @@ +import React from 'react'; +import { StatusBadge as StatusBadgeComponent, StatusType, StatusValue } from '../components/ui/StatusBadge'; + +/** + * Get status badge color classes + */ +export const getStatusColor = (status: string, type?: StatusType): string => { + const statusStr = String(status).toLowerCase(); + + switch (type) { + case 'order': + switch (statusStr) { + case 'pending': + return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'; + case 'processing': + return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'; + case 'shipped': + return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'; + case 'delivered': + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; + case 'cancelled': + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; + case 'refunded': + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + } + + case 'product': + switch (statusStr) { + case 'active': + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; + case 'inactive': + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; + case 'draft': + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + } + + case 'user': + switch (statusStr) { + case 'verified': + case 'true': + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; + case 'unverified': + case 'false': + return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + } + + case 'discount': + switch (statusStr) { + case 'active': + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; + case 'inactive': + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + } + + case 'comment': + switch (statusStr) { + case 'approved': + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; + case 'rejected': + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; + case 'pending': + return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + } + + default: + switch (statusStr) { + case 'active': + case 'true': + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; + case 'inactive': + case 'false': + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'; + } + } +}; + +/** + * Get status text in Persian + */ +export const getStatusText = (status: string, type?: StatusType): string => { + const statusStr = String(status).toLowerCase(); + + switch (type) { + case 'order': + switch (statusStr) { + case 'pending': + return 'در انتظار'; + case 'processing': + return 'در حال پردازش'; + case 'shipped': + return 'ارسال شده'; + case 'delivered': + return 'تحویل شده'; + case 'cancelled': + return 'لغو شده'; + case 'refunded': + return 'مرجوع شده'; + default: + return statusStr; + } + + case 'product': + switch (statusStr) { + case 'active': + return 'فعال'; + case 'inactive': + return 'غیرفعال'; + case 'draft': + return 'پیش‌نویس'; + default: + return statusStr; + } + + case 'user': + switch (statusStr) { + case 'verified': + case 'true': + return 'تأیید شده'; + case 'unverified': + case 'false': + return 'تأیید نشده'; + default: + return statusStr; + } + + case 'discount': + switch (statusStr) { + case 'active': + return 'فعال'; + case 'inactive': + return 'غیرفعال'; + default: + return statusStr; + } + + case 'comment': + switch (statusStr) { + case 'approved': + return 'تأیید شده'; + case 'rejected': + return 'رد شده'; + case 'pending': + return 'در انتظار'; + default: + return statusStr; + } + + default: + switch (statusStr) { + case 'active': + case 'true': + return 'فعال'; + case 'inactive': + case 'false': + return 'غیرفعال'; + default: + return statusStr; + } + } +}; + +/** + * Get status badge React component + */ +export const getStatusBadge = ( + status: StatusValue, + type?: StatusType, + className?: string, + size?: 'sm' | 'md' | 'lg' +): React.ReactElement => { + return React.createElement(StatusBadgeComponent, { status, type, className, size }); +}; +