import { useCallback, useMemo, useState } from 'react'; import { getSikshyaApi, getWpApi, SIKSHYA_ENDPOINTS } from '../api'; import { getErrorSummary } from '../api/errors'; import { EmbeddableShell } from '../components/shared/EmbeddableShell'; import { GatedFeatureWorkspace } from '../components/GatedFeatureWorkspace'; import { ApiErrorPanel } from '../components/shared/ApiErrorPanel'; import { ButtonPrimary, ButtonSecondary, LinkButtonSecondary } from '../components/shared/buttons'; import { DataTable, type Column } from '../components/shared/DataTable'; import { HorizontalEditorTabs } from '../components/shared/HorizontalEditorTabs'; import { TermEntityListView } from '../components/shared/list/TermEntityListView'; import { useAsyncData } from '../hooks/useAsyncData'; import { useAddonEnabled } from '../hooks/useAddons'; import { appViewHref } from '../lib/appUrl'; import { isFeatureEnabled, resolveGatedWorkspaceMode } from '../lib/licensing'; import { AddonSettingsPage } from './AddonSettingsPage'; import type { SikshyaReactConfig, WpTerm } from '../types'; import { __ } from '../lib/i18n'; type BankTerm = { slug: string; name: string; count: number }; type TermsResp = { ok?: boolean; terms?: BankTerm[] }; type PreviewResp = { ok?: boolean; tag?: string; meta_count?: number; taxonomy_count?: number; combined_count?: number; sample_question_ids?: number[]; message?: string; }; const QB_TAXONOMY = 'sikshya_qbank'; function slugifyBankName(name: string): string { return name .toLowerCase() .trim() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } export function QuizAdvancedWorkspacePage(props: { config: SikshyaReactConfig; title: string; embedded?: boolean; }) { const { config, title, embedded } = props; const featureOk = isFeatureEnabled(config, 'quiz_advanced'); const addon = useAddonEnabled('quiz_advanced'); const mode = resolveGatedWorkspaceMode(featureOk, addon.enabled, addon.loading); const enabled = mode === 'full'; const termsLoader = useCallback(async () => { if (!enabled) return { ok: true, terms: [] as BankTerm[] }; return getSikshyaApi().get(SIKSHYA_ENDPOINTS.pro.quizAdvancedBankTerms); }, [enabled]); const termsState = useAsyncData(termsLoader, [enabled]); const terms = termsState.data?.terms ?? []; const [tagInput, setTagInput] = useState(''); const [preview, setPreview] = useState(null); const [previewErr, setPreviewErr] = useState(null); const [previewBusy, setPreviewBusy] = useState(false); const [workspaceTab, setWorkspaceTab] = useState<'overview' | 'terms'>('overview'); const [termListNonce, setTermListNonce] = useState(0); const [newBankName, setNewBankName] = useState(''); const [newBankSlug, setNewBankSlug] = useState(''); const [createBusy, setCreateBusy] = useState(false); const [createErr, setCreateErr] = useState(null); const [createOk, setCreateOk] = useState(null); const questionsReactHref = useMemo(() => appViewHref(config, 'content-library', { tab: 'questions' }), [config]); const runPreview = async () => { const t = tagInput.trim(); if (!enabled || !t) return; setPreviewBusy(true); setPreviewErr(null); setPreview(null); try { const r = await getSikshyaApi().get(SIKSHYA_ENDPOINTS.pro.quizAdvancedPoolPreview(t)); setPreview(r); } catch (e) { setPreviewErr(e); } finally { setPreviewBusy(false); } }; const columns: Column[] = useMemo( () => [ { id: 'name', header: 'Bank', render: (r) => r.name }, { id: 'slug', header: 'Slug (pool tag)', render: (r) => {r.slug}, }, { id: 'count', header: 'Questions', headerClassName: 'text-right', cellClassName: 'text-right tabular-nums', render: (r) => String(r.count), }, ], [], ); const termColumns: Column[] = useMemo( () => [ { id: 'name', header: 'Bank name', render: (r) => r.name }, { id: 'slug', header: 'Slug (pool tag)', render: (r) => {r.slug}, }, { id: 'count', header: 'Questions', headerClassName: 'text-right', cellClassName: 'text-right tabular-nums', render: (r) => String(r.count ?? 0), }, ], [] ); const createBankTerm = async () => { const name = newBankName.trim(); if (!name) { setCreateErr('Enter a bank name.'); return; } setCreateBusy(true); setCreateErr(null); setCreateOk(null); try { const slugRaw = newBankSlug.trim(); const slug = slugRaw || slugifyBankName(name) || 'bank'; await getWpApi().post(`/${QB_TAXONOMY}`, { name, slug }); setNewBankName(''); setNewBankSlug(''); setCreateOk('Bank created.'); setTermListNonce((n) => n + 1); termsState.refetch(); } catch (e) { setCreateErr(getErrorSummary(e)); } finally { setCreateBusy(false); } }; const inner = ( addon.enable()} addonError={addon.error} > {enabled ? (
setWorkspaceTab(id as 'overview' | 'terms')} />
{workspaceTab === 'overview' ? (

{__('Question banks (taxonomy)', 'sikshya')}

Assign banks in the block editor or under {__('Content library → Questions', 'sikshya')}. Use the same slug in a quiz’s {__('Pool tag', 'sikshya')} field (or legacy pool meta on questions) for random draws. Create and edit taxonomy banks in the {__('Bank terms', 'sikshya')} tab—no classic WordPress tags screen required.

{__('Browse all questions', 'sikshya')} setWorkspaceTab('terms')}> Manage bank terms
{termsState.loading ? (

{__('Loading banks…', 'sikshya')}

) : termsState.error ? (
termsState.refetch()} />
) : terms.length === 0 ? (

No taxonomy banks yet. Open {__('Bank terms', 'sikshya')} to add a bank, then assign it to published questions to see counts here.

) : (
columns={columns} rows={terms} rowKey={(r) => r.slug} wrapInCard={false} />
)}

{__('Pool preview', 'sikshya')}

Enter a pool slug to see how many published questions match via meta tag, via taxonomy, and combined (deduplicated).

setTagInput(e.target.value)} placeholder={__('e.g. unit-1', 'sikshya')} />
void runPreview()}> {previewBusy ? __('Checking…', 'sikshya') : __('Preview', 'sikshya')}
{previewErr ? (
) : null} {preview && preview.ok ? (
  • {__('Meta tag matches:', 'sikshya')} {preview.meta_count ?? 0}
  • {__('Taxonomy matches:', 'sikshya')} {preview.taxonomy_count ?? 0}
  • {__('Combined (unique):', 'sikshya')} {preview.combined_count ?? 0}
) : null} {preview && preview.ok && (preview.sample_question_ids?.length ?? 0) > 0 ? (

Sample IDs: {(preview.sample_question_ids ?? []).join(', ')} {(preview.combined_count ?? 0) > 25 ? ' …' : ''}

) : null} {preview && !preview.ok ? (

{preview.message || 'Invalid request.'}

) : null}
) : (

{__('Bank terms (REST)', 'sikshya')}

These terms are the taxonomy behind question banks. The slug is what you enter as a quiz {__('Pool tag', 'sikshya')} . Assign banks to individual questions from the question editor sidebar or the Questions list.

setNewBankName(e.target.value)} placeholder={__('e.g. Unit 1 review', 'sikshya')} />
setNewBankSlug(e.target.value)} placeholder={__('Defaults from name', 'sikshya')} />
void createBankTerm()}> {createBusy ? __('Saving…', 'sikshya') : __('Add bank', 'sikshya')}
{createErr ? (

{createErr}

) : null} {createOk ? (

{createOk}

) : null}
{__('Open questions', 'sikshya')}} />
)}
) : null}
); if (embedded) { return ( {inner} ); } return inner; }