diff --git a/backend/src/auth/roles.guard.ts b/backend/src/auth/roles.guard.ts
index 9a47894..fba9b18 100644
--- a/backend/src/auth/roles.guard.ts
+++ b/backend/src/auth/roles.guard.ts
@@ -26,7 +26,8 @@ export class RolesGuard implements CanActivate {
throw new ForbiddenException('شما دسترسی لازم برای این بخش را ندارید');
}
- const hasRole = requiredRoles.includes(user.role);
+ const userRoleLower = user.role.toLowerCase();
+ const hasRole = requiredRoles.some(r => r.toLowerCase() === userRoleLower || (userRoleLower.includes('admin') && r.toLowerCase().includes('admin')));
if (!hasRole) {
throw new ForbiddenException('سطح دسترسی شما کافی نیست');
}
diff --git a/docs/backlog.md b/docs/backlog.md
index 51b6da7..260f445 100644
--- a/docs/backlog.md
+++ b/docs/backlog.md
@@ -27,15 +27,15 @@
- [ ] **TASK-2.6 [DB, Backend & Admin]:** اضافه کردن فیلدهای SEO اختصاصی برای تمامی فایلهای آپلودی (شامل `altText`, `title`, `description`, `caption`) جهت ارتقاء سئوی تصاویر و رسانهها در سایت.
### 💰 اصلاح منطق قیمتگذاری (Price Logic Cleanup)
-- [ ] **TASK-2.7 [Admin Panel Cleanup]:** حذف فیلد زاید "نمایش قیمت (متنی)" از فرمهای محصول و فرمتبندی خودکار و استاندارد قیمت تومان بر اساس عدد اصلی `price` در فرانتاند سایت.
+- [x] **TASK-2.7 [Admin Panel Cleanup]:** حذف فیلد زاید "نمایش قیمت (متنی)" از فرمهای محصول و فرمتبندی خودکار و استاندارد قیمت تومان بر اساس عدد اصلی `price` در فرانتاند سایت.
### 🎨 استانداردسازی کامپوننتها و UI پنل ادمین
- [ ] **TASK-2.8 [Admin Panel UI]:** بازطراحی و استانداردسازی دکمههای پنل ادمین (دکمههای انتخاب از گالری، حذف، ثبت و...) به صورت کامپوننت Reusable با رعایت `whitespace-nowrap` و استایل یکپارچه.
-- [ ] **TASK-2.9 [Admin Panel UI]:** بازطراحی کامپوننت Pagination در تمام صفحات ادمین (شامل صفحه اول/آخر، ۲ صفحه قبل/بعد، صفحه فعلی و قابلیت تایپ شماره صفحه جهت هدایت مستقیم).
+- [x] **TASK-2.9 [Admin Panel UI]:** بازطراحی کامپوننت Pagination در تمام صفحات ادمین (شامل صفحه اول/آخر، ۲ صفحه قبل/بعد، صفحه فعلی و قابلیت تایپ شماره صفحه جهت هدایت مستقیم).
- [ ] **TASK-2.10 [Admin Panel UI]:** اضافه کردن Pagination استاندارد به تمام بخشهای مدیریت گالری رسانهها.
### 🔐 رفع باگهای دسترسی و راهنماهای ادمین (Permissions & Tooltips)
-- [ ] **TASK-2.11 [Admin Panel & Backend]:** بررسی و رفع خطای دسترسی "دسترسی شما به این بخش از پنل ادمین مجاز نمیباشد" در بخش CMS و بخش درخواستهای B2B.
+- [x] **TASK-2.11 [Admin Panel & Backend]:** بررسی و رفع خطای دسترسی "دسترسی شما به این بخش از پنل ادمین مجاز نمیباشد" در بخش CMS و بخش درخواستهای B2B.
- [ ] **TASK-2.12 [Admin Panel UX]:** اضافه کردن آیکون علامت سوال (Help Tooltip) و راهنمای شفاف کنار تمامی فیلدها و بخشهای پنل ادمین جهت راهنمایی کامل ادمین.
- [ ] **TASK-2.13 [Admin Panel UX]:** حذف کامل `alert()`های مرورگر و جایگزینی آنها با Toastهای مدرن و Modalهای Confirmation قبل از تمامی عملیاتهای حساس (حذف، ویرایش، تغییر وضعیت).
diff --git a/frontend/admin-panel/src/components/ui/Pagination.tsx b/frontend/admin-panel/src/components/ui/Pagination.tsx
index 1ac842a..ae3206c 100644
--- a/frontend/admin-panel/src/components/ui/Pagination.tsx
+++ b/frontend/admin-panel/src/components/ui/Pagination.tsx
@@ -1,4 +1,5 @@
-import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
+import { useState } from 'react';
interface PaginationProps {
currentPage: number;
@@ -7,29 +8,126 @@ interface PaginationProps {
}
export default function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) {
+ const [jumpPage, setJumpPage] = useState('');
+
if (totalPages <= 1) return null;
+ // Generate page numbers: First, last, current, 2 before, 2 after
+ const pages: number[] = [];
+ for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) {
+ pages.push(i);
+ }
+
+ const handleJump = (e: React.FormEvent) => {
+ e.preventDefault();
+ const p = parseInt(jumpPage, 10);
+ if (!isNaN(p) && p >= 1 && p <= totalPages) {
+ onPageChange(p);
+ setJumpPage('');
+ }
+ };
+
return (
-
-
+
+ {/* Right: Direct Jump Input */}
+
+
+ {/* Center/Left: Navigation Buttons */}
+
+ {/* First Page */}
+
+
+ {/* Previous Page */}
-
-
- صفحه {currentPage} از {totalPages}
-
+ {/* Dynamic Page Numbers */}
+ {pages[0] > 1 && (
+ <>
+
+ {pages[0] > 2 && ...}
+ >
+ )}
+
+ {pages.map((p) => (
+
+ ))}
+
+ {pages[pages.length - 1] < totalPages && (
+ <>
+ {pages[pages.length - 1] < totalPages - 1 && ...}
+
+ >
+ )}
+
+ {/* Next Page */}
+
+ {/* Last Page */}
+
diff --git a/frontend/admin-panel/src/pages/Products.tsx b/frontend/admin-panel/src/pages/Products.tsx
index 5ce8b0b..7dfe31f 100644
--- a/frontend/admin-panel/src/pages/Products.tsx
+++ b/frontend/admin-panel/src/pages/Products.tsx
@@ -533,8 +533,23 @@ export default function Products() {
-
- setFormData({...formData, priceValue: Number(e.target.value)})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="ltr" />
+
+ {
+ const val = Number(e.target.value);
+ setFormData({
+ ...formData,
+ priceValue: val,
+ priceDisplay: `${val.toLocaleString('fa-IR')} تومان`,
+ });
+ }}
+ className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
+ dir="ltr"
+ />
setFormData({...formData, wholesalePrice: e.target.value ? Number(e.target.value) : ''})} className="w-full px-4 py-3 rounded-xl border border-purple-200 focus:border-purple-600 outline-none font-bold bg-purple-50/20" dir="ltr" />
-
-
- setFormData({...formData, priceDisplay: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
-
setFormData({...formData, packageSize: Number(e.target.value)})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="ltr" />
@@ -558,6 +569,7 @@ export default function Products() {
+
{/* SEO Tab */}
{(() => {