import { useCallback, useMemo, useState } from 'react'; import { getSikshyaApi, SIKSHYA_ENDPOINTS } from '../api'; import { AddonSettingsPage } from './AddonSettingsPage'; import { EmbeddableShell } from '../components/shared/EmbeddableShell'; import { GatedFeatureWorkspace } from '../components/GatedFeatureWorkspace'; import { DataTable, type Column } from '../components/shared/DataTable'; import { ApiErrorPanel } from '../components/shared/ApiErrorPanel'; import { ListPanel } from '../components/shared/list/ListPanel'; import { ListEmptyState } from '../components/shared/list/ListEmptyState'; import { ListPaginationBar, DEFAULT_LIST_PER_PAGE } from '../components/shared/list/ListPaginationBar'; import { DataTableSkeleton } from '../components/shared/Skeleton'; import { ButtonPrimary, ButtonSecondary } from '../components/shared/buttons'; import { SingleCoursePicker } from '../components/shared/SingleCoursePicker'; import { useAsyncData } from '../hooks/useAsyncData'; import { useAddonEnabled } from '../hooks/useAddons'; import { isFeatureEnabled, resolveGatedWorkspaceMode } from '../lib/licensing'; import { appViewHref } from '../lib/appUrl'; import type { SikshyaReactConfig } from '../types'; import { __ } from '../lib/i18n'; type LogRow = { id?: number; user_id?: number; user_name?: string; user_email?: string; course_id?: number; course_title?: string; action?: string; action_label?: string; object_type?: string; object_id?: number; meta?: string | null; created_at?: string; }; type Resp = { ok?: boolean; rows?: LogRow[]; total?: number; page?: number; per_page?: number; pages?: number; table_missing?: boolean; }; const ACTION_OPTIONS: { value: string; label: string }[] = [ { value: '', label: 'All actions' }, { value: 'enrolled', label: 'Enrolled' }, { value: 'unenrolled', label: 'Unenrolled' }, { value: 'course_completed', label: 'Course completed' }, { value: 'lesson_completed', label: 'Lesson completed' }, { value: 'quiz_completed', label: 'Quiz completed' }, { value: 'assignment_submitted', label: 'Assignment submitted' }, { value: 'order_fulfilled', label: 'Order fulfilled' }, { value: 'certificate_issued', label: 'Certificate issued' }, ]; export function ActivityLogPage(props: { config: SikshyaReactConfig; title: string; embedded?: boolean }) { const { config, title, embedded } = props; const adminBase = config.adminUrl.replace(/\/?$/, '/'); const featureOk = isFeatureEnabled(config, 'activity_log'); const addon = useAddonEnabled('activity_log'); const mode = resolveGatedWorkspaceMode(featureOk, addon.enabled, addon.loading); const enabled = mode === 'full'; const [page, setPage] = useState(1); const perPage = DEFAULT_LIST_PER_PAGE; const [workspaceTab, setWorkspaceTab] = useState<'timeline' | 'settings'>('timeline'); const [filterCourseId, setFilterCourseId] = useState(0); const [filterUserId, setFilterUserId] = useState(''); const [filterAction, setFilterAction] = useState(''); const [filterSearch, setFilterSearch] = useState(''); const [filterDateFrom, setFilterDateFrom] = useState(''); const [filterDateTo, setFilterDateTo] = useState(''); const loader = useCallback(async () => { if (!enabled) { return { ok: true, rows: [] as LogRow[], total: 0, page: 1, per_page: perPage, pages: 0, table_missing: false, }; } const uid = parseInt(String(filterUserId).trim(), 10); const path = SIKSHYA_ENDPOINTS.pro.activityLog({ per_page: perPage, page, course_id: filterCourseId > 0 ? filterCourseId : undefined, user_id: Number.isFinite(uid) && uid > 0 ? uid : undefined, action: filterAction || undefined, search: filterSearch.trim() || undefined, date_from: filterDateFrom.trim() || undefined, date_to: filterDateTo.trim() || undefined, }); return getSikshyaApi().get(path); }, [ enabled, page, perPage, filterCourseId, filterUserId, filterAction, filterSearch, filterDateFrom, filterDateTo, ]); const { loading, data, error, refetch } = useAsyncData(loader, [ page, enabled, perPage, filterCourseId, filterUserId, filterAction, filterSearch, filterDateFrom, filterDateTo, ]); const rows = data?.rows ?? []; const total = data?.total ?? null; const totalPages = data?.pages ?? null; const tableMissing = Boolean(data?.table_missing); const columns: Column[] = useMemo( () => [ { id: 'time', header: 'Time', render: (r) => ( {r.created_at || '—'} ), }, { id: 'user', header: 'Learner', render: (r) => { const uid = r.user_id ?? 0; if (uid <= 0) { return ; } const label = (r.user_name || '').trim() || `User #${uid}`; return (
{label} {r.user_email ?
{r.user_email}
: null}
); }, }, { id: 'action', header: 'Action', render: (r) => ( {r.action_label || r.action || '—'} ), }, { id: 'course', header: 'Course', render: (r) => { const cid = r.course_id ?? 0; if (cid <= 0) { return {__('Site-wide', 'sikshya')}; } const t = (r.course_title || '').trim() || `Course #${cid}`; return ( {t} ); }, }, { id: 'object', header: 'Object', render: (r) => ( {(r.object_type || '—') + (r.object_id != null ? ` #${r.object_id}` : '')} ), }, { id: 'meta', header: 'Meta', cellClassName: 'max-w-[min(20rem,40vw)]', render: (r) => ( {r.meta || '—'} ), }, ], [adminBase, config] ); const emptyContent = ( ); const resetFilters = () => { setFilterCourseId(0); setFilterUserId(''); setFilterAction(''); setFilterSearch(''); setFilterDateFrom(''); setFilterDateTo(''); setPage(1); }; return ( setWorkspaceTab('settings')}> Activity log settings void refetch()}> {loading ? __('Refreshing…', 'sikshya') : __('Refresh', 'sikshya')} ) : null } > addon.enable()} addonError={addon.error} > {enabled ? (
) : null} {enabled && workspaceTab === 'settings' ? ( ) : null} {enabled && workspaceTab === 'timeline' && error ? ( void refetch()} /> ) : null} {enabled && workspaceTab === 'timeline' && tableMissing ? (
The activity log table is not installed yet. Activate Sikshya Pro or run database updates so Pro migrations can create it.
) : null} {enabled && workspaceTab === 'timeline' && !error && !tableMissing ? (

Filters apply immediately. Use learner WordPress ID for the user field. Instructors only see courses they author (plus co-instructor courses when Multi-instructor is on).

{ setFilterCourseId(id); setPage(1); }} placeholder={__('All courses', 'sikshya')} hint="Optional — limit events to one course." className="w-full max-w-full" />
resetFilters()}> Reset filters
{loading ? ( ) : ( <>
{total != null && total > 0 ? ( Showing {rows.length} row{rows.length === 1 ? '' : 's'} · {total} total events ) : ( {__('Learner and course events captured by Sikshya Pro', 'sikshya')} )}
setPage(p)} disabled={loading} /> columns={columns} rows={rows} rowKey={(r) => String(r.id ?? `${r.created_at}-${r.user_id}-${r.action}-${r.object_id}`)} emptyContent={rows.length === 0 ? emptyContent : undefined} emptyMessage={__('No rows to display.', 'sikshya')} wrapInCard={false} /> setPage(p)} disabled={loading} /> )}
) : null}
); }