diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a389466..4c13f48 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,18 +8,12 @@ generator client { binaryTargets = ["native", "debian-openssl-3.0.x", "debian-openssl-1.1.x"] } -enum Role { - USER - ADMIN -} - model User { id Int @id @default(autoincrement()) username String @unique password String name String? mobile String? - role Role @default(USER) // Deprecated, use roles instead roles Json? // e.g. ["COUNTER", "ACCOUNTANT", "SUPERVISOR", "ADMIN"] orgId Int? avatarUrl String? @db.LongText @@ -29,6 +23,7 @@ model User { lockedLocations Location[] @relation("LocationLocks") assignedTasks Task[] @relation("AssignedTasks") createdTasks Task[] @relation("CreatedTasks") + actionLogs ActionLog[] // Logs created by this user createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -76,8 +71,9 @@ model Counting { user_id Int user User @relation(fields: [user_id], references: [id]) mode String @default("SHELF") // SHELF, ITEM, TASK - status String @default("PENDING") // PENDING, APPROVED, REJECTED, CORRECTION_REQUESTED + status String @default("PENDING") // PENDING, APPROVED, REJECTED, CORRECTION_REQUESTED, CANCELLED correctionNote String? @db.Text + cancelReason String? @db.Text is_offline Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -103,3 +99,12 @@ model Task { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model ActionLog { + id Int @id @default(autoincrement()) + userId Int + user User @relation(fields: [userId], references: [id]) + action String // e.g., "LOGIN", "START_SHELF", "CANCEL_SHELF", "COUNT_ITEM" + details String? @db.Text + createdAt DateTime @default(now()) +} diff --git a/src/app/admin/logs/page.js b/src/app/admin/logs/page.js new file mode 100644 index 0000000..35f8ea7 --- /dev/null +++ b/src/app/admin/logs/page.js @@ -0,0 +1,181 @@ +'use client'; +import { useState, useEffect } from 'react'; +import Header from '@/components/Header'; +import { Activity, Search, Filter, CalendarDays, History, X } from 'lucide-react'; + +export default function LogsPage() { + const [logs, setLogs] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(true); + + const [typeFilter, setTypeFilter] = useState('ALL'); + const [page, setPage] = useState(1); + const take = 50; + + const actionTypes = [ + { id: 'ALL', label: 'همه عملیات‌ها' }, + { id: 'LOGIN', label: 'ورود' }, + { id: 'CANCEL_COUNTING', label: 'لغو انبارگردانی' }, + ]; + + useEffect(() => { + fetchLogs(); + }, [page, typeFilter]); + + const fetchLogs = async () => { + setLoading(true); + try { + const skip = (page - 1) * take; + const res = await fetch(`/api/logs?skip=${skip}&take=${take}&type=${typeFilter}`); + if (res.ok) { + const data = await res.json(); + setLogs(data.logs || []); + setTotal(data.total || 0); + } + } catch (e) { + console.error(e); + } finally { + setLoading(false); + } + }; + + const totalPages = Math.ceil(total / take); + + return ( +
+
+ +
+
+

+ + رهگیری فعالیت کاربران +

+

+ گزارش کامل عملیات‌های حساس از جمله ورود، لغو انبارگردانی و ... +

+
+ + {/* Filters */} +
+
+ فیلتر نوع: +
+ {actionTypes.map(t => ( + + ))} +
+ + {/* Logs List */} +
+
+ + + + + + + + + + + {logs.map((log) => ( + + + + + + + ))} + + {logs.length === 0 && !loading && ( + + + + )} + + {loading && ( + + + + )} + +
کاربرنوع عملیاتجزئیاتتاریخ و زمان
+
+
+ {log.user?.name ? log.user.name.charAt(0) : '?'} +
+
+ {log.user?.name || 'ناشناس'} + {log.user?.username} +
+
+
+ + {log.action} + + +

+ {log.details || '-'} +

+
+
+ {new Date(log.createdAt).toLocaleString('fa-IR')} + +
+
+
+ + هیچ لاگی یافت نشد. +
+
+
+
+ در حال دریافت لاگ‌ها... +
+
+
+ + {/* Pagination */} + {!loading && totalPages > 1 && ( +
+ تعداد کل: {total} +
+ + صفحه {page} از {totalPages} + +
+
+ )} +
+ +
+
+ ); +} diff --git a/src/app/admin/page.js b/src/app/admin/page.js index 9afaa88..3ce8869 100644 --- a/src/app/admin/page.js +++ b/src/app/admin/page.js @@ -98,6 +98,20 @@ export default function AdminDashboard() { + + + +
+
+ +
+ لاگ عملیات و فعالیت‌ها +
+
+ +
+ +
); diff --git a/src/app/admin/products/page.js b/src/app/admin/products/page.js index 57c6072..0a34c77 100644 --- a/src/app/admin/products/page.js +++ b/src/app/admin/products/page.js @@ -23,8 +23,11 @@ export default function ProductsPage() { }); if (res.ok) { const data = await res.json(); - // Assuming Hesabfa returns data.List for query responses - setProducts(data.List || data || []); + // Safely extract the array of products from the Hesabfa response + const productList = data?.Result?.List || data?.List || (Array.isArray(data) ? data : []); + setProducts(productList); + } else { + setProducts([]); } } catch (e) { console.error(e); diff --git a/src/app/admin/reports/page.js b/src/app/admin/reports/page.js new file mode 100644 index 0000000..7c2d18e --- /dev/null +++ b/src/app/admin/reports/page.js @@ -0,0 +1,155 @@ +'use client'; +import { useState, useEffect } from 'react'; +import Header from '@/components/Header'; +import { Trophy, AlertTriangle, Activity, BarChart2, CheckCircle2, TrendingUp } from 'lucide-react'; +import { motion } from 'framer-motion'; + +export default function ReportsPage() { + const [data, setData] = useState({ topPerformers: [], mostDiscrepancies: [], all: [] }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch('/api/reports/leaderboard') + .then(res => res.json()) + .then(d => { + if (d.topPerformers) setData(d); + setLoading(false); + }); + }, []); + + if (loading) { + return ( +
+
+
+
+
+
+ ); + } + + return ( +
+
+ +
+
+

+ + کارنامه عملکرد تیم +

+

+ بررسی جامع آمار انبارگردانی، برترین‌ها و میزان خطاهای ثبت شده +

+
+ + {/* Top Performers */} +
+
+ +
+ +

برترین انبارگردان‌ها

+
+ +
+ {data.topPerformers.map((user, idx) => ( +
+
+
+ {idx + 1} +
+ {user.name} +
+
+
+ شمارش‌ها + {user.totalCounted} +
+
+ دقت عملکرد + {user.accuracy}% +
+
+
+ ))} + {data.topPerformers.length === 0 && ( +

هنوز هیچ انبارگردانی ثبت نشده است.

+ )} +
+
+ + {/* Most Discrepancies */} +
+
+ +

بیشترین مغایرت‌های ثبت شده

+
+ +
+ {data.mostDiscrepancies.filter(u => u.discrepancies > 0).map((user, idx) => ( +
+
+
+ {idx + 1} +
+ {user.name} +
+
+
+ مغایرت‌ها + {user.discrepancies} +
+
+
+ ))} + {data.mostDiscrepancies.filter(u => u.discrepancies > 0).length === 0 && ( +

خوشبختانه هیچ کاربری مغایرتی نداشته است.

+ )} +
+
+ + {/* Full Details */} +
+
+

+ + جزئیات عملکرد همه پرسنل +

+
+
+ + + + + + + + + + + + + {data.all.map((user) => ( + + + + + + + + + ))} + +
نام کاربرتعداد شمارشقفسه‌های پوشش دادهخطا/مغایرتلغو شدهدقت
{user.name}{user.totalCounted}{user.uniqueShelves}{user.discrepancies}{user.cancelled} +
+ {user.accuracy}% +
+
+
+
+ +
+
+ ); +} diff --git a/src/app/admin/settings/page.js b/src/app/admin/settings/page.js index b1ca70a..e5fec67 100644 --- a/src/app/admin/settings/page.js +++ b/src/app/admin/settings/page.js @@ -126,6 +126,32 @@ export default function SettingsPage() { + {/* Show Suggested Shelves Setting */} +
+
+
+
+ +
+

پیشنهاد قفسه‌های شمارش‌نشده

+
+

+ در صورت فعال بودن، در صفحه داشبورد لیستی از قفسه‌هایی که مدتی شمارش نشده‌اند به کاربر پیشنهاد داده می‌شود. +

+
+ + +
+ {/* Correction Roles Setting */}
diff --git a/src/app/admin/users/page.js b/src/app/admin/users/page.js index 94c48d1..100932e 100644 --- a/src/app/admin/users/page.js +++ b/src/app/admin/users/page.js @@ -101,6 +101,24 @@ export default function UsersPage() {

+ +
+
+ +
+
+ گزارشات و رتبه‌بندی پرسنل + کارمندان برتر، بیشترین خطا و... +
+
+
+ +
+ + {/* Search */}
diff --git a/src/app/api/auth/login/route.js b/src/app/api/auth/login/route.js index f0ca1ed..be24343 100644 --- a/src/app/api/auth/login/route.js +++ b/src/app/api/auth/login/route.js @@ -29,9 +29,9 @@ export async function POST(req) { try { parsedRoles = JSON.parse(parsedRoles); } catch (e) { parsedRoles = null; } } - let userRoles = Array.isArray(parsedRoles) ? parsedRoles : (user.role === 'ADMIN' ? ['ADMIN'] : ['COUNTER']); + let userRoles = Array.isArray(parsedRoles) ? parsedRoles : ['COUNTER']; - const token = signToken({ id: user.id, username: user.username, name: user.name, orgId: user.orgId, roles: userRoles, role: user.role }); + const token = signToken({ id: user.id, username: user.username, name: user.name, orgId: user.orgId, roles: userRoles }); return Response.json({ message: 'با موفقیت وارد شدید', token, user: { id: user.id, name: user.name, orgId: user.orgId, roles: userRoles, avatarUrl: user.avatarUrl } }); } catch (error) { diff --git a/src/app/api/auth/webauthn/login/verify/route.js b/src/app/api/auth/webauthn/login/verify/route.js index 101dd3e..fc4d11d 100644 --- a/src/app/api/auth/webauthn/login/verify/route.js +++ b/src/app/api/auth/webauthn/login/verify/route.js @@ -46,9 +46,9 @@ export async function POST(req) { try { parsedRoles = JSON.parse(parsedRoles); } catch (e) { parsedRoles = null; } } - let userRoles = Array.isArray(parsedRoles) ? parsedRoles : (user.role === 'ADMIN' ? ['ADMIN'] : ['COUNTER']); - const token = signToken({ id: user.id, username: user.username, name: user.name, roles: userRoles, role: user.role }); - return NextResponse.json({ verified: true, token, user: { id: user.id, name: user.name, roles: userRoles, role: user.role } }); + let userRoles = Array.isArray(parsedRoles) ? parsedRoles : ['COUNTER']; + const token = signToken({ id: user.id, username: user.username, name: user.name, roles: userRoles }); + return NextResponse.json({ verified: true, token, user: { id: user.id, name: user.name, roles: userRoles } }); } return NextResponse.json({ verified: false }, { status: 400 }); } catch (error) { diff --git a/src/app/api/counting/cancel/route.js b/src/app/api/counting/cancel/route.js new file mode 100644 index 0000000..949cbdb --- /dev/null +++ b/src/app/api/counting/cancel/route.js @@ -0,0 +1,59 @@ +import prisma from '@/lib/prisma'; +import { NextResponse } from 'next/server'; + +export async function POST(req) { + try { + const body = await req.json(); + const { shelfCode, warehouse, userId, reason, mode, product_id } = body; + + if (!reason) { + return NextResponse.json({ error: 'اطلاعات کامل نیست' }, { status: 400 }); + } + + // 1. Mark recent countings in this shelf/warehouse by this user as CANCELLED + // We assume counts created in the last 24h by this user for this shelf + const twentyFourHoursAgo = new Date(); + twentyFourHoursAgo.setHours(twentyFourHoursAgo.getHours() - 24); + + const where = { + warehouse: Number(warehouse), + user_id: Number(userId), + createdAt: { gte: twentyFourHoursAgo }, + status: 'PENDING' + }; + + if (mode === 'SHELF' && shelfCode) { + where.shelfCode = shelfCode.toUpperCase(); + } else if (mode === 'ITEM' && product_id) { + where.product_id = Number(product_id); + } + + const updated = await prisma.counting.updateMany({ + where, + data: { + status: 'CANCELLED', + cancelReason: reason + } + }); + + // 2. Unlock the location + await prisma.location.updateMany({ + where: { code: shelfCode.toUpperCase(), warehouse: Number(warehouse) }, + data: { isLocked: false, lockedById: null, lockedAt: null } + }); + + // 3. Log the action + await prisma.actionLog.create({ + data: { + userId: Number(userId), + action: 'CANCEL_COUNTING', + details: `لغو شمارش ${mode === 'SHELF' ? 'قفسه' : 'کالا'} ${shelfCode.toUpperCase()} به دلیل: ${reason}` + } + }); + + return NextResponse.json({ success: true, cancelledCount: updated.count }); + } catch (error) { + console.error('Cancel counting error:', error); + return NextResponse.json({ error: 'خطا در لغو انبارگردانی' }, { status: 500 }); + } +} diff --git a/src/app/api/locations/active/route.js b/src/app/api/locations/active/route.js new file mode 100644 index 0000000..1fc1d17 --- /dev/null +++ b/src/app/api/locations/active/route.js @@ -0,0 +1,30 @@ +import prisma from '@/lib/prisma'; +import { NextResponse } from 'next/server'; + +export async function GET(req) { + try { + const url = new URL(req.url); + const userId = url.searchParams.get('userId'); + + if (!userId) { + return NextResponse.json({ error: 'کاربر نامشخص' }, { status: 400 }); + } + + const activeLocations = await prisma.location.findMany({ + where: { + isLocked: true, + lockedById: Number(userId) + }, + select: { + code: true, + warehouse: true, + floor: true + } + }); + + return NextResponse.json({ active: activeLocations }); + } catch (error) { + console.error('Fetch active locations error:', error); + return NextResponse.json({ error: 'خطا در سرور' }, { status: 500 }); + } +} diff --git a/src/app/api/logs/route.js b/src/app/api/logs/route.js new file mode 100644 index 0000000..1fc08db --- /dev/null +++ b/src/app/api/logs/route.js @@ -0,0 +1,52 @@ +import prisma from '@/lib/prisma'; +import { NextResponse } from 'next/server'; + +export async function GET(req) { + try { + const url = new URL(req.url); + const take = parseInt(url.searchParams.get('take')) || 50; + const skip = parseInt(url.searchParams.get('skip')) || 0; + const type = url.searchParams.get('type'); // optional filter + + const where = type && type !== 'ALL' ? { action: type } : {}; + + const [logs, total] = await Promise.all([ + prisma.actionLog.findMany({ + where, + take, + skip, + orderBy: { createdAt: 'desc' }, + include: { user: { select: { name: true, username: true } } } + }), + prisma.actionLog.count({ where }) + ]); + + return NextResponse.json({ logs, total }); + } catch (error) { + console.error('Fetch logs error:', error); + return NextResponse.json({ error: 'خطا در دریافت لاگ‌ها' }, { status: 500 }); + } +} + +export async function POST(req) { + try { + const { userId, action, details } = await req.json(); + + if (!userId || !action) { + return NextResponse.json({ error: 'اطلاعات ناقص است' }, { status: 400 }); + } + + const log = await prisma.actionLog.create({ + data: { + userId: Number(userId), + action, + details + } + }); + + return NextResponse.json({ success: true, log }); + } catch (error) { + console.error('Create log error:', error); + return NextResponse.json({ error: 'خطا در ثبت لاگ' }, { status: 500 }); + } +} diff --git a/src/app/api/reports/leaderboard/route.js b/src/app/api/reports/leaderboard/route.js new file mode 100644 index 0000000..24add07 --- /dev/null +++ b/src/app/api/reports/leaderboard/route.js @@ -0,0 +1,51 @@ +import prisma from '@/lib/prisma'; +import { NextResponse } from 'next/server'; + +export async function GET() { + try { + const users = await prisma.user.findMany({ + include: { + countings: true + } + }); + + const report = users.map(user => { + const counts = user.countings || []; + const totalCounted = counts.length; + + // Calculate discrepancies (assuming old_count !== new_count) + const discrepancies = counts.filter(c => c.old_count !== c.new_count).length; + + // Calculate total cancelled + const cancelled = counts.filter(c => c.status === 'CANCELLED').length; + + // Unique shelves visited + const uniqueShelves = new Set(counts.map(c => c.shelfCode).filter(Boolean)).size; + + return { + id: user.id, + name: user.name || user.username, + totalCounted, + discrepancies, + cancelled, + uniqueShelves, + accuracy: totalCounted > 0 ? (((totalCounted - discrepancies) / totalCounted) * 100).toFixed(1) : 0 + }; + }).filter(u => u.totalCounted > 0); + + // Sort top performers by totalCounted + const topPerformers = [...report].sort((a, b) => b.totalCounted - a.totalCounted).slice(0, 5); + + // Sort most discrepancies + const mostDiscrepancies = [...report].sort((a, b) => b.discrepancies - a.discrepancies).slice(0, 5); + + return NextResponse.json({ + topPerformers, + mostDiscrepancies, + all: report.sort((a, b) => b.totalCounted - a.totalCounted) + }); + } catch (error) { + console.error('Fetch leaderboard error:', error); + return NextResponse.json({ error: 'خطا در دریافت گزارش' }, { status: 500 }); + } +} diff --git a/src/app/api/users/route.js b/src/app/api/users/route.js index cb316ba..c9df1b7 100644 --- a/src/app/api/users/route.js +++ b/src/app/api/users/route.js @@ -10,7 +10,6 @@ export async function GET() { username: true, mobile: true, roles: true, - role: true, createdAt: true, _count: { select: { countings: true } @@ -27,7 +26,7 @@ export async function GET() { } return { ...user, - roles: Array.isArray(parsedRoles) ? parsedRoles : (user.role === 'ADMIN' ? ['ADMIN'] : ['COUNTER']) + roles: Array.isArray(parsedRoles) ? parsedRoles : ['COUNTER'] }; }); diff --git a/src/app/counting/item/page.js b/src/app/counting/item/page.js index 2a2d62b..28e88e7 100644 --- a/src/app/counting/item/page.js +++ b/src/app/counting/item/page.js @@ -168,6 +168,22 @@ function ItemCountingContent() { } }; + const handleCancelItem = async () => { + const reason = window.prompt('لطفاً دلیل لغو انبارگردانی این کالا را وارد کنید:'); + if (!reason) return; + + try { + await fetch('/api/counting/cancel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ product_id: code, warehouse, userId: user?.id, reason, mode: 'ITEM' }) + }); + router.push('/dashboard'); + } catch (error) { + alert('خطا در لغو انبارگردانی کالا'); + } + }; + const handleFinish = () => { router.push('/dashboard'); }; @@ -317,12 +333,20 @@ function ItemCountingContent() {
)} - +
+ + +
diff --git a/src/app/counting/shelf/page.js b/src/app/counting/shelf/page.js index a5cee91..ceb24ce 100644 --- a/src/app/counting/shelf/page.js +++ b/src/app/counting/shelf/page.js @@ -205,6 +205,22 @@ function ShelfCountingContent() { } }; + const handleCancelShelf = async () => { + const reason = window.prompt('لطفاً دلیل لغو انبارگردانی این قفسه را وارد کنید:'); + if (!reason) return; + + try { + await fetch('/api/counting/cancel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ shelfCode, warehouse, userId: user?.id, reason, mode: 'SHELF' }) + }); + router.push('/dashboard'); + } catch (error) { + alert('خطا در لغو انبارگردانی'); + } + }; + const isBlind = settings?.blind_counting && !hasRole(user?.roles, ['ADMIN', 'SUPERVISOR']); if (loading) { @@ -234,13 +250,22 @@ function ShelfCountingContent() { - +
+ + +
diff --git a/src/app/dashboard/page.js b/src/app/dashboard/page.js index 24af916..fb9aae4 100644 --- a/src/app/dashboard/page.js +++ b/src/app/dashboard/page.js @@ -7,25 +7,43 @@ import { History, ScanLine, ListChecks, AlertTriangle, Layers, MapPin } from 'lu export default function Dashboard() { const [uncountedShelves, setUncountedShelves] = useState([]); + const [activeSessions, setActiveSessions] = useState([]); const [settings, setSettings] = useState(null); + const [user, setUser] = useState(null); useEffect(() => { - fetchData(); + const userData = localStorage.getItem('user'); + let u = null; + if (userData) { + u = JSON.parse(userData); + setUser(u); + } + fetchData(u); }, []); - const fetchData = async () => { + const fetchData = async (currentUser) => { try { const setRes = await fetch('/api/settings'); - let currentSettings = { uncounted_shelf_days: 10 }; + let currentSettings = { uncounted_shelf_days: 10, show_suggested_shelves: true }; if (setRes.ok) { currentSettings = await setRes.json(); setSettings(currentSettings); } - const uncRes = await fetch(`/api/reports/uncounted?days=${currentSettings.uncounted_shelf_days || 10}`); - if (uncRes.ok) { - const uncData = await uncRes.json(); - setUncountedShelves(uncData.uncounted || []); + if (currentSettings.show_suggested_shelves !== false) { + const uncRes = await fetch(`/api/reports/uncounted?days=${currentSettings.uncounted_shelf_days || 10}`); + if (uncRes.ok) { + const uncData = await uncRes.json(); + setUncountedShelves(uncData.uncounted || []); + } + } + + if (currentUser?.id) { + const actRes = await fetch(`/api/locations/active?userId=${currentUser.id}`); + if (actRes.ok) { + const actData = await actRes.json(); + setActiveSessions(actData.active || []); + } } } catch (e) { console.error(e); @@ -76,22 +94,43 @@ export default function Dashboard() {
- - -
-
- -
- شمارش‌های من + {/* Active Sessions */} + {activeSessions.length > 0 && ( + +

+ + + + + قفسه‌های باز شما (ناتمام) +

+ +
+ {activeSessions.map((session, idx) => ( +
+
+
+ +
+
+ {session.code} + انبار: {session.warehouse || '-'} +
+
+ + ادامه / پایان + +
+ ))}
-
- -
- -
+ + )} {/* Suggested Shelves to count */} - {uncountedShelves.length > 0 && ( + {settings?.show_suggested_shelves !== false && uncountedShelves.length > 0 && (

diff --git a/src/app/layout.js b/src/app/layout.js index 309d466..4e03c13 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -24,8 +24,16 @@ export default function RootLayout({ children }) {
-
- {children} +
+
+ {children} +
+
+

+ Designed & Developed by DrMesta +

+

App v1.4.0

+
diff --git a/src/app/my-counts/page.js b/src/app/my-counts/page.js deleted file mode 100644 index cd30060..0000000 --- a/src/app/my-counts/page.js +++ /dev/null @@ -1,58 +0,0 @@ -'use client'; -import { useState, useEffect } from 'react'; -import Header from '@/components/Header'; - -export default function MyCountsPage() { - const [counts, setCounts] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchMyCounts = async () => { - const userData = localStorage.getItem('user'); - if (userData) { - const user = JSON.parse(userData); - try { - const res = await fetch(`/api/counting?user_id=${user.id}`); - if (res.ok) setCounts(await res.json()); - } catch (e) { - console.error(e); - } - } - setLoading(false); - }; - fetchMyCounts(); - }, []); - - return ( -
-
- -
- {loading ? ( -
در حال دریافت...
- ) : counts.length === 0 ? ( -
هنوز شمارشی ثبت نکرده‌اید.
- ) : ( - counts.map(count => ( -
-
-
- {count.product_name} - کد: {count.product_id} | انبار: {count.warehouse} - قفسه: {count.shelf || 'ثبت نشده'} -
-
- {count.new_count} - شمارش شما -
-
-
- {new Date(count.createdAt).toLocaleString('fa-IR')} -
-
- )) - )} -
-
- ); -} diff --git a/src/app/settings/page.js b/src/app/settings/page.js index 7e6ce66..942cd39 100644 --- a/src/app/settings/page.js +++ b/src/app/settings/page.js @@ -158,7 +158,7 @@ export default function SettingsPage() {

{user?.name || 'کاربر'}

-

{user?.role === 'ADMIN' ? 'مدیریت کل' : 'کاربر عادی'}

+

{user?.roles?.includes('ADMIN') ? 'مدیریت کل' : 'کاربر عادی'}