import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { AdminBackLink, AdminEmptyState, AdminErrorState, AdminInfoRow, AdminLoadingBlock, AdminPageHeader, AdminPageShell, AdminSecondaryButton, AdminSection, AdminStatusPill, formatAdminDate, formatAdminPercent, getAdminErrorMessage, } from '@/components/admin'; import { Input } from '@/components/ui/input'; import { PCQ24_QUESTIONS, PCQ_LIKERT_OPTIONS } from '@/config/pcq24'; import { downloadAdminUserAssignmentsExport, fetchAdminUserAssessments, fetchAdminUserAssignmentDetail, fetchAdminUserAssignments, fetchAdminUserChatSessionDetail, fetchAdminUserChatSessions, fetchAdminUserDetail, fetchAdminUserOverview, type AdminAssessmentPhaseView, type AdminAssignmentDetail, type AdminAssignmentListItem, type AdminAssignmentsQuery, type AdminChatMessageView, type AdminChatSessionDetail, type AdminChatSessionListItem, type AdminLearningProgress, type AdminAssignmentsSummary, type AdminUserAssessments, type AdminUserDetail, type AdminUserOverview, } from '@/services/adminService'; import { BackendError } from '@/services/backend'; import { ROUTES } from '@/utils/constants'; import { cn } from '@/lib/utils'; const STEP_LABELS: Record = { registered: 'ثبت‌نام', pre_test_done: 'پیش‌آزمون انجام‌شده', training_in_progress: 'در حال آموزش', training_completed: 'پایان آموزش', post_test_done: 'پس‌آزمون', completed: 'تکمیل دوره', }; const ASSIGNMENT_STATUS_LABELS: Record = { NOT_STARTED: 'شروع‌نشده', ACTIVE: 'فعال', IN_PROGRESS: 'در حال انجام', REPORTED: 'گزارش‌شده', COMPLETED: 'تکمیل‌شده', }; const ASSIGNMENT_TYPE_LABELS: Record = { ONE_TIME: 'یک‌باره', DAILY: 'روزانه', ONGOING: 'پیوسته', }; const ASSIGNMENT_STATUS_OPTIONS = [ 'NOT_STARTED', 'ACTIVE', 'IN_PROGRESS', 'REPORTED', 'COMPLETED', ] as const; const ASSIGNMENT_TYPE_OPTIONS = ['ONE_TIME', 'DAILY', 'ONGOING'] as const; const ACTIVITY_TYPE_LABELS: Record = { session_completed: 'جلسه', assignment_activated: 'تخصیص تکلیف', assignment_completed: 'تکمیل تکلیف', assessment_submitted: 'آزمون', chat_started: 'گفتگو', }; const ASSESSMENT_OVERALL_LABELS: Record = { none: 'آزمونی ثبت نشده', partial: 'ناقص', complete: 'کامل', }; const DIMENSION_LABELS: Record = { efficacy: 'خودکارآمدی', hope: 'امید', resilience: 'تاب‌آوری', optimism: 'خوش‌بینی', }; const PCQ_ANSWER_LABELS: Record = Object.fromEntries( PCQ_LIKERT_OPTIONS.map((option) => [Number(option.value), option.label]), ) as Record; const PCQ_QUESTION_TEXT: Record = Object.fromEntries( PCQ24_QUESTIONS.map((question) => [question.id, question.text]), ); const filterSelectClass = 'h-9 rounded-md border border-slate-200 bg-white px-2 text-sm dark:border-slate-600 dark:bg-slate-900'; function formatScore(value: number | null | undefined): string { if (value == null) return '—'; return value.toLocaleString('fa-IR'); } function pcqAnswerLabel(rawValue: number): string { return PCQ_ANSWER_LABELS[rawValue] ?? String(rawValue); } function AssignmentStatusBadge({ status }: { status: string }) { const tone = status === 'COMPLETED' ? 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300' : status === 'IN_PROGRESS' || status === 'REPORTED' || status === 'ACTIVE' ? 'bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300' : 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300'; return ( {ASSIGNMENT_STATUS_LABELS[status] ?? status} ); } function AssessmentPhasePanel({ title, phase, }: { title: string; phase: AdminAssessmentPhaseView | null; }) { if (!phase) { return ( ); } if (!phase.exists) { return ( ); } const incomplete = phase.answerCount < phase.expectedAnswerCount || phase.scores == null; return (
ثبت: {formatAdminDate(phase.submittedAt)} {phase.answerCount.toLocaleString('fa-IR')} از{' '} {phase.expectedAnswerCount.toLocaleString('fa-IR')} پاسخ
{phase.scores ? (
{( [ ['نمره کل', phase.scores.total], ['خودکارآمدی', phase.scores.efficacy], ['امید', phase.scores.hope], ['تاب‌آوری', phase.scores.resilience], ['خوش‌بینی', phase.scores.optimism], ] as const ).map(([label, score]) => (

{label}

{formatScore(score)}

))}
) : (

امتیازی ثبت نشده است.

)} {phase.answers.length === 0 ? ( ) : (
    {phase.answers.map((answer) => { const questionText = PCQ_QUESTION_TEXT[answer.questionId]; return (
  1. سوال {answer.questionNumber.toLocaleString('fa-IR')}:{' '} {pcqAnswerLabel(answer.rawValue)}

    {questionText ? (

    {questionText}

    ) : null}

    {DIMENSION_LABELS[answer.dimension] ?? answer.dimension}

  2. ); })}
)}
); } function ChatMessageBubble({ message }: { message: AdminChatMessageView }) { const isUser = message.role === 'user'; return (
{isUser ? 'کاربر' : message.role === 'assistant' ? 'دستیار' : message.role} {formatAdminDate(message.createdAt)}
{message.content}
); } export default function AdminUserDetailPage() { const { externalId = '' } = useParams<{ externalId: string }>(); const [user, setUser] = useState(null); const [overview, setOverview] = useState(null); const [assessments, setAssessments] = useState(null); const [learningProgress, setLearningProgress] = useState(null); const [assignmentSummary, setAssignmentSummary] = useState( null, ); const [assignments, setAssignments] = useState([]); const [selectedAssignment, setSelectedAssignment] = useState( null, ); const [chatSessions, setChatSessions] = useState([]); const [chatPage, setChatPage] = useState(1); const [chatTotalPages, setChatTotalPages] = useState(0); const [chatTotal, setChatTotal] = useState(0); const [selectedChat, setSelectedChat] = useState(null); const [loading, setLoading] = useState(true); const [overviewLoading, setOverviewLoading] = useState(false); const [assessmentsLoading, setAssessmentsLoading] = useState(false); const [assignmentsLoading, setAssignmentsLoading] = useState(false); const [assignmentDetailLoading, setAssignmentDetailLoading] = useState(false); const [chatsLoading, setChatsLoading] = useState(false); const [chatDetailLoading, setChatDetailLoading] = useState(false); const [error, setError] = useState(null); const [overviewError, setOverviewError] = useState(null); const [assessmentsError, setAssessmentsError] = useState(null); const [assignmentsError, setAssignmentsError] = useState(null); const [assignmentDetailError, setAssignmentDetailError] = useState(null); const [exportLoading, setExportLoading] = useState(false); const [exportError, setExportError] = useState(null); const [chatsError, setChatsError] = useState(null); const [chatDetailError, setChatDetailError] = useState(null); const [errorCode, setErrorCode] = useState(null); const [assessmentTab, setAssessmentTab] = useState<'pre' | 'post'>('pre'); const [assignmentSearchInput, setAssignmentSearchInput] = useState(''); const [assignmentSearch, setAssignmentSearch] = useState(''); const [assignmentStatusFilter, setAssignmentStatusFilter] = useState(''); const [assignmentTypeFilter, setAssignmentTypeFilter] = useState(''); const [assignmentSortBy, setAssignmentSortBy] = useState('sessionOrder'); const [assignmentSortDir, setAssignmentSortDir] = useState('asc'); const hasAssignmentFilters = Boolean( assignmentSearch || assignmentStatusFilter || assignmentTypeFilter, ); const buildAssignmentsQuery = useCallback((): AdminAssignmentsQuery => { const query: AdminAssignmentsQuery = { sortBy: assignmentSortBy, sortDir: assignmentSortDir, }; if (assignmentStatusFilter) { query.status = assignmentStatusFilter as NonNullable; } if (assignmentTypeFilter) { query.assignmentType = assignmentTypeFilter as NonNullable; } if (assignmentSearch.trim()) { query.search = assignmentSearch.trim(); } return query; }, [ assignmentSearch, assignmentSortBy, assignmentSortDir, assignmentStatusFilter, assignmentTypeFilter, ]); useEffect(() => { const timer = window.setTimeout(() => { setAssignmentSearch(assignmentSearchInput.trim()); }, 400); return () => window.clearTimeout(timer); }, [assignmentSearchInput]); const loadAssignments = useCallback(async () => { if (!externalId) return; setAssignmentsLoading(true); setAssignmentsError(null); setExportError(null); try { const result = await fetchAdminUserAssignments(externalId, buildAssignmentsQuery()); setLearningProgress(result.learningProgress); setAssignmentSummary(result.summary); setAssignments(result.items); } catch (err) { setLearningProgress(null); setAssignmentSummary(null); setAssignments([]); setAssignmentsError(getAdminErrorMessage(err, 'دریافت برنامهٔ آموزشی ناموفق بود.')); } finally { setAssignmentsLoading(false); } }, [buildAssignmentsQuery, externalId]); const clearAssignmentFilters = useCallback(() => { setAssignmentSearchInput(''); setAssignmentSearch(''); setAssignmentStatusFilter(''); setAssignmentTypeFilter(''); setAssignmentSortBy('sessionOrder'); setAssignmentSortDir('asc'); }, []); const openAssignmentDetail = useCallback( async (assignmentId: string) => { if (!externalId) return; setAssignmentDetailLoading(true); setAssignmentDetailError(null); setSelectedAssignment(null); try { const detail = await fetchAdminUserAssignmentDetail(externalId, assignmentId); setSelectedAssignment(detail); } catch (err) { if (err instanceof BackendError && err.status === 404) { setAssignmentDetailError('تکلیف یافت نشد.'); } else { setAssignmentDetailError(getAdminErrorMessage(err, 'دریافت جزئیات تکلیف ناموفق بود.')); } } finally { setAssignmentDetailLoading(false); } }, [externalId], ); const exportAssignments = useCallback(async () => { if (!externalId || exportLoading) return; setExportLoading(true); setExportError(null); try { await downloadAdminUserAssignmentsExport(externalId, buildAssignmentsQuery()); } catch (err) { setExportError(getAdminErrorMessage(err, 'دانلود فایل Excel ناموفق بود.')); } finally { setExportLoading(false); } }, [buildAssignmentsQuery, externalId, exportLoading]); const loadChatSessions = useCallback( async (page: number) => { if (!externalId) return; setChatsLoading(true); setChatsError(null); setSelectedChat(null); setChatDetailError(null); try { const list = await fetchAdminUserChatSessions(externalId, { page, pageSize: 10, sortBy: 'startedAt', sortDir: 'desc', }); setChatSessions(list.items); setChatPage(list.pagination.page); setChatTotalPages(list.pagination.totalPages); setChatTotal(list.pagination.total); } catch (err) { setChatSessions([]); setChatsError(getAdminErrorMessage(err, 'دریافت جلسات چت ناموفق بود.')); } finally { setChatsLoading(false); } }, [externalId], ); const openChatSession = useCallback( async (sessionId: string) => { if (!externalId) return; setChatDetailLoading(true); setChatDetailError(null); setSelectedChat(null); try { const detail = await fetchAdminUserChatSessionDetail(externalId, sessionId); setSelectedChat(detail); } catch (err) { if (err instanceof BackendError && err.status === 404) { setChatDetailError('جلسه چت یافت نشد.'); } else { setChatDetailError(getAdminErrorMessage(err, 'دریافت پیام‌های جلسه ناموفق بود.')); } } finally { setChatDetailLoading(false); } }, [externalId], ); const load = useCallback(async () => { if (!externalId) { setError('شناسه کاربر نامعتبر است.'); setErrorCode('VALIDATION_ERROR'); setLoading(false); return; } setLoading(true); setAssessmentsLoading(true); setOverviewLoading(true); setError(null); setAssessmentsError(null); setOverviewError(null); setAssignmentsError(null); setExportError(null); setChatsError(null); setChatDetailError(null); setSelectedChat(null); setSelectedAssignment(null); setAssignmentDetailError(null); setOverview(null); setLearningProgress(null); setAssignmentSummary(null); setAssignments([]); setAssignmentSearchInput(''); setAssignmentSearch(''); setAssignmentStatusFilter(''); setAssignmentTypeFilter(''); setAssignmentSortBy('sessionOrder'); setAssignmentSortDir('asc'); setErrorCode(null); try { const detail = await fetchAdminUserDetail(externalId); setUser(detail); try { const nextOverview = await fetchAdminUserOverview(externalId); setOverview(nextOverview); } catch (overviewErr) { setOverview(null); setOverviewError(getAdminErrorMessage(overviewErr, 'دریافت داشبورد ناموفق بود.')); } finally { setOverviewLoading(false); } try { const nextAssessments = await fetchAdminUserAssessments(externalId); setAssessments(nextAssessments); } catch (assessErr) { setAssessments(null); setAssessmentsError(getAdminErrorMessage(assessErr, 'دریافت آزمون‌ها ناموفق بود.')); } await loadChatSessions(1); } catch (err) { setUser(null); setOverview(null); setAssessments(null); setLearningProgress(null); setAssignmentSummary(null); setAssignments([]); setSelectedAssignment(null); setChatSessions([]); setOverviewLoading(false); if (err instanceof BackendError) { setErrorCode(err.code); if (err.status === 403) { setError('دسترسی غیرمجاز. فقط ادمین می‌تواند جزئیات کاربران را ببیند.'); } else if (err.status === 404) { setError('کاربر یافت نشد.'); } else if (err.status === 422 || err.status === 400) { setError('شناسه کاربر نامعتبر است.'); } else { setError(getAdminErrorMessage(err, 'دریافت جزئیات کاربر ناموفق بود.')); } } else { setError(getAdminErrorMessage(err, 'دریافت جزئیات کاربر ناموفق بود.')); } } finally { setLoading(false); setAssessmentsLoading(false); } }, [externalId, loadChatSessions]); useEffect(() => { void load(); }, [load]); useEffect(() => { if (!externalId || !user) return; void loadAssignments(); }, [externalId, user, loadAssignments]); return (
{externalId || '—'} } actions={} /> {loading ? : null} {!loading && error ? ( void load()} /> ) : null} {!loading && user ? ( <> {user.email ?? '—'}} /> {overviewLoading ? ( ) : null} {!overviewLoading && overviewError ? ( { setOverviewLoading(true); setOverviewError(null); void fetchAdminUserOverview(externalId) .then((next) => setOverview(next)) .catch((err) => setOverviewError(getAdminErrorMessage(err, 'دریافت داشبورد ناموفق بود.')), ) .finally(() => setOverviewLoading(false)); }} /> ) : null} {!overviewLoading && !overviewError && !overview ? ( ) : null} {!overviewLoading && !overviewError && overview ? (

خلاصه تکالیف

{overview.assignments.total === 0 ? ( ) : ( <> )}

مسیر آموزشی

خلاصه آزمون‌ها

فعالیت‌های اخیر

{overview.recentActivities.length === 0 ? ( ) : (
    {overview.recentActivities.map((activity, index) => (
  • {activity.title}

    {ACTIVITY_TYPE_LABELS[activity.type] ?? activity.type} ·{' '} {formatAdminDate(activity.occurredAt)}

  • ))}
)}
) : null}
void exportAssignments()} aria-busy={exportLoading} className="text-xs" > {exportLoading ? 'در حال آماده‌سازی…' : 'خروجی Excel'} } >

فیلتر تکالیف

setAssignmentSearchInput(event.target.value)} placeholder="جست‌وجو در عنوان، کلید یا جلسه…" className="h-9 text-sm" maxLength={100} aria-label="جست‌وجوی تکالیف" />
{hasAssignmentFilters || assignmentSortBy !== 'sessionOrder' || assignmentSortDir !== 'asc' ? ( پاک‌کردن فیلترها ) : null}
{exportError ? ( ) : null} {assignmentsLoading ? ( ) : null} {!assignmentsLoading && assignmentsError ? ( void loadAssignments()} /> ) : null} {!assignmentsLoading && !assignmentsError && learningProgress ? (

خلاصهٔ پیشرفت

{assignmentSummary ? ( <> ) : null}
) : null} {!assignmentsLoading && !assignmentsError && assignments.length === 0 ? ( پاک‌کردن فیلترها ) : undefined } /> ) : null} {!assignmentsLoading && !assignmentsError && assignments.length > 0 ? (

فهرست تکالیف

    {assignments.map((item) => (
  • {item.definitionTitle}

    جلسه {item.sessionOrder.toLocaleString('fa-IR')} {item.sessionTitle ? `: ${item.sessionTitle}` : ''} ·{' '} {ASSIGNMENT_TYPE_LABELS[item.assignmentType] ?? item.assignmentType}

    تخصیص: {formatAdminDate(item.assignedAt)} شروع: {formatAdminDate(item.startedAt)} تکمیل: {formatAdminDate(item.completedAt)} پیشرفت:{' '} {item.progressPercent != null ? formatAdminPercent(item.progressPercent) : item.expectedDurationDays != null ? `${item.doneDayCount.toLocaleString('fa-IR')} / ${item.expectedDurationDays.toLocaleString('fa-IR')} روز` : '—'}
    void openAssignmentDetail(item.assignmentId)} className="text-xs" aria-label={`جزئیات تکلیف ${item.definitionTitle}`} > جزئیات
  • ))}
) : null} {assignmentDetailLoading ? ( ) : null} {assignmentDetailError ? ( ) : null} {selectedAssignment && !assignmentDetailLoading ? (

{selectedAssignment.assignment.definitionTitle}

{selectedAssignment.assignment.definitionKey} ·{' '} {selectedAssignment.assignment.id}

{ setSelectedAssignment(null); setAssignmentDetailError(null); }} aria-label="بستن جزئیات تکلیف" > بستن
{selectedAssignment.activity ? ( <> ) : ( )}
) : null}
{assessmentsLoading ? ( ) : null} {!assessmentsLoading && assessmentsError ? ( { setAssessmentsLoading(true); setAssessmentsError(null); void fetchAdminUserAssessments(externalId) .then((next) => setAssessments(next)) .catch((err) => setAssessmentsError( getAdminErrorMessage(err, 'دریافت آزمون‌ها ناموفق بود.'), ), ) .finally(() => setAssessmentsLoading(false)); }} /> ) : null} {!assessmentsLoading && !assessmentsError ? ( ) : null}
{chatsLoading ? ( ) : null} {!chatsLoading && chatsError ? ( void loadChatSessions(chatPage || 1)} /> ) : null} {!chatsLoading && !chatsError && chatSessions.length === 0 ? ( ) : null} {!chatsLoading && !chatsError && chatSessions.length > 0 ? ( <>

{chatTotal.toLocaleString('fa-IR')} جلسه · صفحه{' '} {chatPage.toLocaleString('fa-IR')} {chatTotalPages > 0 ? ` از ${chatTotalPages.toLocaleString('fa-IR')}` : ''}

    {chatSessions.map((session) => (
  • {session.title}

    {session.status === 'ended' ? 'پایان‌یافته' : 'باز'} ·{' '} {session.messageCount.toLocaleString('fa-IR')} پیام · شروع:{' '} {formatAdminDate(session.startedAt)} · فعالیت:{' '} {formatAdminDate(session.lastActivityAt)}

    void openChatSession(session.id)} className="text-xs" aria-label={`مشاهده جلسه ${session.title}`} > مشاهده
  • ))}
void loadChatSessions(chatPage - 1)} className="text-xs" aria-label="صفحه قبلی جلسات چت" > قبلی = chatTotalPages || chatsLoading } onClick={() => void loadChatSessions(chatPage + 1)} className="text-xs" aria-label="صفحه بعدی جلسات چت" > بعدی
) : null} {chatDetailLoading ? ( ) : null} {chatDetailError ? ( ) : null} {selectedChat && !chatDetailLoading ? (

{selectedChat.session.title}

{selectedChat.session.messageCount.toLocaleString('fa-IR')} پیام ·{' '} {selectedChat.session.status === 'ended' ? 'پایان‌یافته' : 'باز'}

{ setSelectedChat(null); setChatDetailError(null); }} aria-label="بستن پیام‌های جلسه" > بستن
{selectedChat.messages.length === 0 ? ( ) : (
{selectedChat.messages.map((message) => ( ))}
)}
) : null}
) : null}
); }