import { useCallback, useEffect, useMemo, useState } from 'react'; import { getSikshyaApi, getWpApi, SIKSHYA_ENDPOINTS } from '../api'; import { ApiErrorPanel } from '../components/shared/ApiErrorPanel'; import { ListPanel } from '../components/shared/list/ListPanel'; import { ListEmptyState } from '../components/shared/list/ListEmptyState'; import { StatusBadge } from '../components/shared/list/StatusBadge'; import { RowActionsMenu, type RowActionItem } from '../components/shared/list/RowActionsMenu'; import { ButtonPrimary, ButtonSecondary } from '../components/shared/buttons'; import { EmbeddableShell } from '../components/shared/EmbeddableShell'; import { Modal } from '../components/shared/Modal'; import { SingleCoursePicker } from '../components/shared/SingleCoursePicker'; import { appViewHref } from '../lib/appUrl'; import { formatPostDate } from '../lib/formatPostDate'; import { useDebouncedValue } from '../hooks/useDebouncedValue'; import { useAsyncData } from '../hooks/useAsyncData'; import { term, termLower } from '../lib/terminology'; import type { SikshyaReactConfig } from '../types'; import { __ } from '../lib/i18n'; type EnrollmentRow = { id: number; user_id: number; course_id: number; status: string; enrolled_date: string; learner_name: string; learner_email: string; course_title: string; }; type ListResponse = { success?: boolean; enrollments?: EnrollmentRow[]; total?: number; pages?: number; page?: number; per_page?: number; table_missing?: boolean; }; type UserOpt = { id: number; name: string; email?: string }; export function EnrollmentsPage(props: { embedded?: boolean; config: SikshyaReactConfig; title: string }) { const { config, title } = props; const adminBase = config.adminUrl.replace(/\/?$/, '/'); const course = term(config, 'course'); const courseLower = termLower(config, 'course'); const coursesLower = termLower(config, 'courses'); const student = term(config, 'student'); const studentLower = termLower(config, 'student'); const enrollment = term(config, 'enrollment'); const enrollmentsLower = termLower(config, 'enrollments'); const [page, setPage] = useState(1); const [status, setStatus] = useState(''); const [search, setSearch] = useState(''); const debouncedSearch = useDebouncedValue(search, 320); const [manualOpen, setManualOpen] = useState(false); const [manualSaving, setManualSaving] = useState(false); const [manualMsg, setManualMsg] = useState(null); const [pickedUserId, setPickedUserId] = useState(null); const [pickedCourseId, setPickedCourseId] = useState(null); const [userQuery, setUserQuery] = useState(''); const [userDropdownOpen, setUserDropdownOpen] = useState(false); const debouncedUserQuery = useDebouncedValue(userQuery, 240); const queryKey = useMemo(() => `${page}|${status}|${debouncedSearch}`, [page, status, debouncedSearch]); const loader = useCallback(async () => { const q = new URLSearchParams({ page: String(page), per_page: '20', }); if (status) { q.set('status', status); } if (debouncedSearch.trim()) { q.set('search', debouncedSearch.trim()); } return getSikshyaApi().get(`${SIKSHYA_ENDPOINTS.admin.enrollments}?${q.toString()}`); }, [page, status, debouncedSearch]); const { loading, data, error, refetch } = useAsyncData(loader, [queryKey]); const rows = data?.enrollments ?? []; const total = data?.total ?? 0; const pages = data?.pages ?? 0; const tableMissing = Boolean(data?.table_missing); const userSearch = useAsyncData( async () => { if (!manualOpen) return { data: [] as UserOpt[] }; if (!userDropdownOpen) return { data: [] as UserOpt[] }; const params = new URLSearchParams({ per_page: '20', page: '1', context: 'edit', orderby: 'name', order: 'asc', }); const q = debouncedUserQuery.trim(); if (q) params.set('search', q); const r = await getWpApi().getWithTotal>(`/users?${params.toString()}`); const out = Array.isArray(r.data) ? r.data : []; return { data: out.map((u) => ({ id: u.id, name: u.name, email: u.email })) }; }, [manualOpen, userDropdownOpen, debouncedUserQuery] ); const pickedUserLabel = useMemo(() => { if (!pickedUserId) return null; const hit = (userSearch.data?.data || []).find((u) => u.id === pickedUserId); return hit ? (hit.email ? `${hit.name} (${hit.email})` : hit.name) : `User #${pickedUserId}`; }, [pickedUserId, userSearch.data?.data]); useEffect(() => { if (!manualOpen) { return; } const onDoc = (e: MouseEvent) => { const t = e.target as HTMLElement | null; if (!t) return; if (!t.closest('[data-manual-enroll-user="1"]')) setUserDropdownOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [manualOpen]); const resetManual = () => { setManualMsg(null); setManualSaving(false); setPickedUserId(null); setPickedCourseId(null); setUserQuery(''); setUserDropdownOpen(false); }; const submitManualEnroll = async () => { setManualMsg(null); const uid = pickedUserId || 0; const cid = pickedCourseId || 0; if (uid <= 0 || cid <= 0) { setManualMsg(`Pick a ${studentLower} and a ${courseLower}.`); return; } setManualSaving(true); try { const r = await getSikshyaApi().post<{ ok?: boolean; message?: string }>(SIKSHYA_ENDPOINTS.admin.enrollmentsManual, { user_id: uid, course_id: cid, }); setManualMsg(r?.message || `${student} enrolled.`); setManualOpen(false); resetManual(); refetch(); } catch (err) { setManualMsg(err instanceof Error ? err.message : 'Request failed'); } finally { setManualSaving(false); } }; return ( { setManualOpen(true); setManualMsg(null); }} > Manual enroll refetch()}> Refresh } > { setManualOpen(false); resetManual(); }} size="lg" footer={
{ setManualOpen(false); resetManual(); }} > Cancel
void submitManualEnroll()}> {manualSaving ? 'Enrolling…' : `Enroll ${studentLower}`}
} >
{manualMsg ? (
{manualMsg}
) : null}
{ setUserQuery(e.target.value); setPickedUserId(null); }} onFocus={() => setUserDropdownOpen(true)} placeholder={__('Search by name or email…', 'sikshya')} className="mt-1 w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800 dark:text-white" /> {pickedUserLabel ? (
Selected: {pickedUserLabel}
) : null} {userDropdownOpen ? (
{userSearch.loading ? (
{__('Searching…', 'sikshya')}
) : (userSearch.data?.data || []).length === 0 ? (
{__('No users found.', 'sikshya')}
) : (
    {(userSearch.data?.data || []).map((u) => (
  • ))}
)}
) : null}
setPickedCourseId(id > 0 ? id : null)} placeholder={`Click to choose a ${courseLower}…`} hint={`Pick the ${courseLower} this ${studentLower} should be enrolled in.`} />
{ setSearch(e.target.value); setPage(1); }} placeholder={`${student} name, email, or ${courseLower} title…`} className="mt-1 w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800 dark:text-white" />
{error ? (
refetch()} />
) : null} {tableMissing ? (
The enrollments database table is not installed yet. Run plugin activation or database updates to create it.
) : null} {loading ? (
Loading {enrollmentsLower}…
) : rows.length === 0 ? ( ) : ( <>
{rows.map((r) => { const rowActions: RowActionItem[] = [ { key: 'view', label: __('View details', 'sikshya'), href: appViewHref(config, 'enrollment-detail', { id: String(r.id) }), }, ]; if (r.course_id > 0) { rowActions.push({ key: 'course', label: __('Open course', 'sikshya'), href: appViewHref(config, 'add-course', { course_id: String(r.course_id) }), }); } rowActions.push({ key: 'user', label: __('Open WP user', 'sikshya'), href: `${adminBase}user-edit.php?user_id=${r.user_id}`, external: true, }); return ( ); })}
{student} {course} {__('Status', 'sikshya')} {__('Enrolled', 'sikshya')} {__('Actions', 'sikshya')}
{r.learner_name || `User #${r.user_id}`}
{r.learner_email || '—'}
{r.course_id > 0 ? ( {r.course_title || `${course} #${r.course_id}`} ) : ( '—' )} {formatPostDate(r.enrolled_date)}
{pages > 1 ? (

Page {page} of {pages} · {total} total

) : null} )}
); }