Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { useCallback, useEffect, useState } from 'react';
- import { useTranslation } from 'react-i18next';
- import { ChevronLeft, ChevronRight } from 'lucide-react';
- import { useNavigate, useParams } from 'react-router-dom';
- import {
- useGetQuestionPaperQuery,
- useGetQuestionsByIdsQuery,
- useEditQuestionPaperMutation,
- useDeleteQuestionPaperMutation,
- useEditQuestionPaperStatusMutation,
- useGetUsersByRoleNamesQuery,
- useCreateQuestionPaperMutation,
- useSearchQuestionsByFieldsQuery,
- } from '../../store/api/assessmentApi';
- import { useUserRoles } from '../../service/auth/role';
- import { useActivityTracker } from '../../hooks/useActivityTracker';
- import { useDirection } from '../../contexts/DirectionContext';
- import { Button } from '@/components/ui/button';
- import { Card } from '@/components/ui/card';
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
- import { Input } from '@/components/ui/input';
- import { Label } from '@/components/ui/label';
- import { Checkbox } from '@/components/ui/checkbox';
- import Title from '@/components/shared/title';
- import SpinnerWithText from '@/components/shared/SpinnerWithText';
- import { Badge } from '@/components/ui/badge';
- import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
- import { DIFFICULTY_OPTIONS } from '@/lib/constants';
- import Questions from './components/questions';
- import CollapsibleCheckboxList from '@/components/shared/CollapsibleCheckboxList';
- import FeedbackDialog from '@/components/shared/feedback-dialog';
- import { debounce } from 'lodash';
- import { getUsersByRole } from '@/lib/utils';
- const difficultyOptions = DIFFICULTY_OPTIONS.map((difficulty) => ({
- key: difficulty.value,
- value: difficulty.label,
- }));
- export type PaperData = {
- sectionId: string;
- questions: {
- type: string;
- id: string;
- status: string;
- feedback: [];
- }[];
- actualQuestionDetails: {
- type: string;
- questionDetailId: string;
- totalEasy: number;
- totalMedium: number;
- totalHard: number;
- totalQuestions: number;
- marksPerQuestion: number;
- }[];
- };
- function CreateQuestionPaper() {
- const { t } = useTranslation();
- const { id } = useParams();
- const navigate = useNavigate();
- // Debug logging
- console.log('CreateQuestionPaper component - id:', id);
- const { trackActivity } = useActivityTracker();
- const { direction } = useDirection();
- const { hasPermission } = useUserRoles();
- const [editQuestionPaperMutation] = useEditQuestionPaperMutation();
- const [editQuestionPaperStatusMutation] = useEditQuestionPaperStatusMutation();
- const [deleteQuestionPaperMutation] = useDeleteQuestionPaperMutation();
- const [createQuestionPaperMutation] = useCreateQuestionPaperMutation();
- // Handle "new" case - show assessment schema selection
- if (id === 'new') {
- return (
- <div className="space-y-4">
- <Title title="Create Question Paper" />
- <Card className="p-6">
- <div className="text-center">
- <h2 className="text-xl font-semibold mb-4">Select Assessment Schema</h2>
- <p className="text-gray-600 mb-6">
- To create a question paper, you need to select an assessment schema first.
- </p>
- <div className="space-y-4">
- <Button
- onClick={() => navigate('/assessment-schema')}
- className="bg-blue-600 hover:bg-blue-700"
- >
- View Assessment Schemas
- </Button>
- <Button
- onClick={() => navigate('/assessments')}
- variant="outline"
- >
- Back to Question Papers
- </Button>
- </div>
- </div>
- </Card>
- </div>
- );
- }
- // Queries
- const {
- data: usersData,
- isLoading: usersLoading,
- error: usersError,
- } = useGetUsersByRoleNamesQuery({
- roleNames: ['Content Creator', 'Reviewer'],
- }, {
- skip: true // Skip this query for now since it's not available on the server
- });
- const {
- data,
- isLoading,
- error: paperError,
- refetch: paperRefetch,
- } = useGetQuestionPaperQuery({ id: id || '' }, {
- skip: !id // Only fetch if we have an ID (existing question paper)
- });
- // Auto-create question paper if it doesn't exist (matching certify-combo behavior)
- useEffect(() => {
- if (id && !isLoading && !data && !paperError) {
- console.log('[CreateQuestionPaper] Question paper not found, creating automatically...');
- const createQuestionPaper = async () => {
- try {
- const response = await createQuestionPaperMutation({ assessmentId: id }).unwrap();
- console.log('[CreateQuestionPaper] Question paper created:', response);
- // Refetch to get the newly created question paper
- paperRefetch();
- } catch (error) {
- console.error('[CreateQuestionPaper] Error creating question paper:', error);
- }
- };
- createQuestionPaper();
- }
- }, [id, isLoading, data, paperError, createQuestionPaperMutation, paperRefetch]);
- // Build filters from searchQuery state
- const buildFilters = () => {
- const filters: any[] = [];
- // Add subject filter
- if (selectedSubject) {
- filters.push({
- field: 'metadata.subject',
- operator: 'in',
- value: [selectedSubject]
- });
- }
- // Add chapter filter
- if (selectedChapter) {
- filters.push({
- field: 'metadata.chapter',
- operator: 'in',
- value: [selectedChapter]
- });
- }
- // Add difficulty filter
- if (searchQuery['metadata.difficulty'] && searchQuery['metadata.difficulty'].length > 0) {
- filters.push({
- field: 'metadata.difficulty',
- operator: 'in',
- value: searchQuery['metadata.difficulty']
- });
- }
- // Add type filter
- if (searchQuery['type'] && searchQuery['type'].length > 0) {
- filters.push({
- field: 'type',
- operator: 'in',
- value: searchQuery['type']
- });
- }
- // Add topic filter
- if (searchQuery['metadata.topic'] && searchQuery['metadata.topic'].length > 0) {
- filters.push({
- field: 'metadata.topic',
- operator: 'in',
- value: searchQuery['metadata.topic']
- });
- }
- // Add subtopic filter
- if (searchQuery['metadata.subTopic'] && searchQuery['metadata.subTopic'].length > 0) {
- filters.push({
- field: 'metadata.subTopic',
- operator: 'in',
- value: searchQuery['metadata.subTopic']
- });
- }
- console.log('[CreateQuestionPaper] Built filters:', filters);
- console.log('[CreateQuestionPaper] searchQuery state:', searchQuery);
- console.log('[CreateQuestionPaper] selectedSubject:', selectedSubject);
- console.log('[CreateQuestionPaper] selectedChapter:', selectedChapter);
- return filters;
- };
- const { data: difficultyLevelFilter, refetch } = useSearchQuestionsByFieldsQuery({
- filters: buildFilters(),
- });
- console.log('[CreateQuestionPaper] difficultyLevelFilter:', difficultyLevelFilter);
- console.log('[CreateQuestionPaper] Questions count:', difficultyLevelFilter?.searchQuestionsByFields?.questions?.length || 0);
- // States
- const [selectedSection, setSelectedSection] = useState('');
- const [selectedSubject, setSelectedSubject] = useState('');
- const [selectedChapter, setSelectedChapter] = useState('');
- const [searchQuery, setSearchQuery] = useState<Record<string, string[] | null>>({});
- const [resetFilter, setResetFilter] = useState(false);
- const [checkedSubTopics, setCheckedSubTopics] = useState<Record<string, string[]>>({});
- const [currentIndex, setCurrentIndex] = useState(0);
- const [paperData, setPaperData] = useState<PaperData | null>(null);
- const [next, setNext] = useState(false);
- const [replace, setReplace] = useState(false);
- const [showFeedback, setShowFeedback] = useState<string | null>(null);
- const [underReview, setUnderReview] = useState(false);
- const [revisionRequest, setRevisionRequest] = useState(false);
- const [reviewComplete, setReviewComplete] = useState(false);
- const [currSelectedQuestions, setCurrSelectedQuestions] = useState<Set<string>>(new Set());
- const visibleCards = 3;
- const questionIds = revisionRequest
- ? paperData?.questions
- ?.filter(
- (e) => e.status !== 'approved' || (e.status === 'pending' && e.type === 'replaced'),
- )
- ?.map((e: { id: string }) => e.id) ?? []
- : paperData?.questions?.map((e: { id: string }) => e.id) ?? [];
- const { data: finalQuestions } = useGetQuestionsByIdsQuery(questionIds, {
- skip: !questionIds.length
- });
- const handlePrev = () => {
- if (currentIndex > 0) setCurrentIndex((prev) => prev - visibleCards);
- };
- const handleNext = () => {
- if (
- currentIndex + visibleCards <
- (data?.sections?.find(
- (e: { sectionId: string }) => e.sectionId === selectedSection,
- )?.actualQuestionDetails.length || 0)
- )
- setCurrentIndex((prev) => prev + visibleCards);
- };
- const onDifficultyUpdate = (checkedOptions: string[]) => {
- setSearchQuery((prev) => ({
- ...prev,
- 'metadata.difficulty': checkedOptions,
- }));
- };
- const onTopicUpdate = (topicName: string, checkedOptions: string[]) => {
- setCheckedSubTopics((prev) => ({
- ...prev,
- [topicName]: checkedOptions,
- }));
- };
- const onTypeUpdate = (checkedOptions: string[]) => {
- setSearchQuery((prev) => ({
- ...prev,
- type: checkedOptions,
- }));
- };
- const handleSelectQuestion = (id: string, difficulty: string, type: string) => {
- setPaperData((prev) => {
- if (!prev) return prev;
- let isSelected;
- let questionDetailId;
- const updatedActualQuestionDetails = prev.actualQuestionDetails.map((q) => {
- if (q.type === type) {
- questionDetailId = q.questionDetailId;
- const updatedCounts = { ...q };
- isSelected = prev.questions.some((e: { id: string }) => e.id === id);
- updatedCounts.totalQuestions += isSelected ? -1 : 1;
- if (difficulty === 'Easy') updatedCounts.totalEasy += isSelected ? -1 : 1;
- else if (difficulty === 'Medium') updatedCounts.totalMedium += isSelected ? -1 : 1;
- else if (difficulty === 'Hard') updatedCounts.totalHard += isSelected ? -1 : 1;
- return updatedCounts;
- }
- return q;
- });
- const questions = prev.questions.some((e: { id: string }) => e.id === id)
- ? prev.questions.filter((q: { id: string }) => q.id !== id)
- : [...prev.questions, { id, type: replace ? 'replaced' : 'original' }];
- if (!isSelected)
- debouncedSave({
- ...prev,
- questions: questions,
- actualQuestionDetails: updatedActualQuestionDetails,
- });
- else
- debouncedDelete({
- sectionId: selectedSection,
- questionId: id,
- type: difficulty,
- questionDetailId,
- });
- if (replace && !isSelected) setShowFeedback('replace');
- return { ...prev, questions, actualQuestionDetails: updatedActualQuestionDetails };
- });
- };
- const debouncedSave = useCallback(
- debounce((input) => {
- editQuestionPaperMutation({
- id: id || '',
- input: {
- section: {
- ...input,
- actualQuestionDetails: input?.actualQuestionDetails?.map(
- ({ marksPerQuestion, __typename, ...rest }) => rest,
- ),
- },
- },
- });
- }, 300),
- [],
- );
- const debouncedDelete = useCallback(
- debounce((input) => {
- deleteQuestionPaperMutation({
- id: id || '',
- input: { ...input },
- });
- }, 300),
- [],
- );
- useEffect(() => {
- if (data?.status === 'Approved') {
- setNext(true);
- setUnderReview(true);
- } else if (data?.status === 'Review Completed') {
- setNext(true);
- setReviewComplete(true);
- } else if (data?.status === 'Revision Requested') {
- setRevisionRequest(true);
- !replace && setNext(true);
- }
- }, [data, replace]);
- useEffect(() => {
- if (data?.sections && data.sections.length > 0 && !selectedSection) {
- setSelectedSection(data.sections[0].sectionId);
- }
- }, [data, selectedSection]);
- useEffect(() => {
- if (data) {
- setPaperData({
- sectionId: selectedSection,
- questions: data.sections?.find((e: { sectionId: string }) => e.sectionId === selectedSection)?.questions || [],
- actualQuestionDetails: data.sections?.find((e: { sectionId: string }) => e.sectionId === selectedSection)?.actualQuestionDetails || [],
- });
- }
- }, [data, selectedSection]);
- const handleSubmit = () => {
- editQuestionPaperStatusMutation({
- id: id || '',
- input: {
- status: 'Approved', // Skip review process - auto-approve
- },
- });
- };
- if (isLoading) return <SpinnerWithText message={t('question_paper.messages.loading')} />;
- if (paperError) return <SpinnerWithText message={t('question_paper.messages.error')} />;
- // Handle case where question paper exists but is empty or invalid
- if (data && (!data.sections || data.sections.length === 0)) {
- return (
- <div className="text-center p-8">
- <h2 className="text-xl font-semibold text-orange-600 mb-4">Question Paper Setup Required</h2>
- <p className="text-gray-600 mb-4">
- This question paper exists but doesn't have any sections configured.
- Please contact an administrator to set up the question paper structure.
- </p>
- <div className="space-y-2">
- <Button onClick={() => navigate('/assessments')} variant="blue">
- Back to Question Papers
- </Button>
- <Button
- onClick={() => window.location.reload()}
- variant="outline"
- className="ml-2"
- >
- Refresh Page
- </Button>
- </div>
- </div>
- );
- }
- if (!data && !isLoading && id) {
- return (
- <div className="text-center p-8">
- <h2 className="text-xl font-semibold text-red-600 mb-4">Question Paper Not Found</h2>
- <p className="text-gray-600 mb-4">The question paper you're looking for doesn't exist or has been deleted.</p>
- <Button onClick={() => navigate('/assessments')} variant="blue">
- Back to Question Papers
- </Button>
- </div>
- );
- }
- return (
- <div className="space-y-4">
- <div className="flex justify-between items-center">
- <Title title={t('assessments.select_questions')} />
- <div className="flex space-x-4">
- {!next && (
- <Button
- className="px-4 py-2 bg-orange-100 text-orange-800 rounded-md hover:bg-orange-50 transition-colors"
- disabled={replace}
- onClick={() => navigate('/assessments')}
- >
- {t('back-2')}
- </Button>
- )}
- <Button
- className={`px-4 py-2 ${!next ? 'bg-[#006CCE]' : 'bg-[#176F4D]'
- } text-white rounded-md ${!next ? 'hover:bg-blue-700' : 'hover:bg-green-700'
- } transition-colors`}
- onClick={() => {
- if (!next) {
- paperRefetch();
- setNext(true);
- } else handleSubmit();
- }}
- disabled={
- (next &&
- data?.sections
- ?.flatMap((e) => e.actualQuestionDetails.map((ele) => ele.totalQuestions))
- .reduce((acc, curr) => acc + curr, 0) !==
- data?.sections
- ?.flatMap((e) =>
- e.section.questionDetails.map((ele) => ele.totalQuestionsNeeded),
- )
- .reduce((acc, curr) => acc + curr, 0)) ||
- (next &&
- revisionRequest &&
- data?.sections.some((e) =>
- e.questions
- .filter((e) => e.status === 'rejected')
- .some((el) => el.feedback[el.feedback.length - 1]?.role !== 'Creator'),
- ) &&
- data?.sections.some((e) =>
- e.questions
- .filter((e) => e.status === 'rejected')
- .some((el) => el?.feedback.length),
- )) ||
- underReview ||
- replace ||
- reviewComplete
- }
- >
- {next ? t('submit') : t('next')}
- </Button>
- </div>
- </div>
- {underReview && (
- <p className="text-green-500 font-semibold">Question paper approved successfully!</p>
- )}
- {reviewComplete && (
- <p className="text-green-700 font-semibold">
- {t('question_paper.messages.review_success')}
- </p>
- )}
- <div className="border p-4 rounded grid grid-cols-[1fr,2fr]">
- <div className="space-y-8 mt-5">
- <div>
- <label className="font-semibold">{t('question_paper.labels.section_name')}</label>
- <Select
- value={selectedSection}
- disabled={replace}
- onValueChange={(e) => {
- setSelectedSection(e);
- setSearchQuery({});
- setSelectedSubject('');
- setSelectedChapter('');
- setCurrSelectedQuestions(new Set());
- !resetFilter && setResetFilter(true);
- paperRefetch();
- }}
- >
- <SelectTrigger className="w-60">
- <SelectValue placeholder={t('question_paper.labels.choose_section')} />
- </SelectTrigger>
- <SelectContent>
- {data?.sections?.map(
- (e: { sectionId: string; section: { sectionName: string } }, i: any) => (
- <SelectItem key={i} value={e.sectionId}>
- {e.section.sectionName}
- </SelectItem>
- ),
- )}
- </SelectContent>
- </Select>
- </div>
- <div>
- <label className="font-semibold">{t('question_paper.labels.subject')}</label>
- <Select
- value={selectedSubject}
- disabled={replace}
- onValueChange={(e) => {
- setSelectedSubject(e);
- setSearchQuery((prev) => ({
- ...prev,
- 'metadata.subject': [e],
- }));
- setSelectedChapter('');
- setCurrSelectedQuestions(new Set());
- !resetFilter && setResetFilter(true);
- paperRefetch();
- }}
- >
- <SelectTrigger className="w-60">
- <SelectValue placeholder={t('question_paper.labels.choose_subject')} />
- </SelectTrigger>
- <SelectContent>
- {data?.sections
- ?.find((e: { sectionId: string }) => e.sectionId === selectedSection)
- ?.section.subjects?.map((subject: string, i: any) => (
- <SelectItem key={i} value={subject}>
- {subject}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div>
- <label className="font-semibold">{t('question_paper.labels.chapter')}</label>
- <Select
- value={selectedChapter}
- disabled={replace}
- onValueChange={(e) => {
- setSelectedChapter(e);
- setSearchQuery((prev) => ({
- ...prev,
- 'metadata.chapter': [e],
- }));
- setCurrSelectedQuestions(new Set());
- !resetFilter && setResetFilter(true);
- paperRefetch();
- }}
- >
- <SelectTrigger className="w-60">
- <SelectValue placeholder={t('question_paper.labels.choose_chapter')} />
- </SelectTrigger>
- <SelectContent>
- {data?.sections
- ?.find((e: { sectionId: string }) => e.sectionId === selectedSection)
- ?.section.chapters?.map((chapter: string, i: any) => (
- <SelectItem key={i} value={chapter}>
- {chapter}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- {/* <div>
- <label className="font-semibold">{t('question_paper.labels.taxonomy')}</label>
- <Select
- value={selectedTaxonomy}
- disabled={replace}
- onValueChange={(e) => {
- setSelectedTaxonomy(e);
- setSearchQuery((prev) => ({
- ...prev,
- 'advancedMetadata.taxonomy': [
- taxonomies?.taxonomies.find((tax) => tax.id === e)?.name || '',
- ],
- }));
- setCurrSelectedQuestions(new Set());
- !resetFilter && setResetFilter(true);
- paperRefetch();
- }}
- >
- <SelectTrigger className="w-60">
- <SelectValue placeholder={t('question_paper.labels.choose_taxonomy')} />
- </SelectTrigger>
- <SelectContent>
- {taxonomies?.taxonomies?.map((taxonomy: any) => (
- <SelectItem key={taxonomy.id} value={taxonomy.id}>
- {taxonomy.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div> */}
- <div>
- <label className="font-semibold">{t('question_paper.labels.difficulty')}</label>
- <CollapsibleCheckboxList
- options={difficultyOptions}
- onSelectionChange={onDifficultyUpdate}
- disabled={replace}
- />
- </div>
- {/* <div>
- <label className="font-semibold">{t('question_paper.labels.cognitive_level')}</label>
- <CollapsibleCheckboxList
- options={skills?.skills?.map((skill: any) => ({
- key: skill.id,
- value: skill.name,
- })) || []}
- onSelectionChange={onLevelUpdate}
- disabled={replace}
- />
- </div> */}
- <div>
- <label className="font-semibold">{t('question_paper.labels.question_type')}</label>
- <CollapsibleCheckboxList
- options={[
- { key: 'multiple_choice', value: 'Multiple Choice' },
- { key: 'descriptive_answer', value: 'Descriptive Answer' },
- ]}
- onSelectionChange={onTypeUpdate}
- disabled={replace}
- />
- </div>
- <div>
- <label className="font-semibold">{t('question_paper.labels.topics')}</label>
- {data?.sections
- ?.find((e: { sectionId: string }) => e.sectionId === selectedSection)
- ?.section.topics?.map((topic: any) => (
- <CollapsibleCheckboxList
- key={topic.name}
- title={topic.name}
- options={topic.subTopics?.map((subTopic: any) => ({
- key: subTopic.id,
- value: subTopic.name,
- })) || []}
- onSelectionChange={(checkedOptions) => onTopicUpdate(topic.name, checkedOptions)}
- disabled={replace}
- />
- ))}
- </div>
- </div>
- <div className="space-y-4">
- <div className="flex flex-row justify-center">
- <Questions
- paperData={paperData as PaperData}
- next={next}
- setNext={setNext}
- setReplace={setReplace}
- underReview={underReview}
- reviewComplete={reviewComplete}
- revisionRequest={revisionRequest}
- selectedSection={
- data?.sections?.find(
- (e: { sectionId: string }) => e.sectionId === selectedSection,
- ) || {}
- }
- currSelectedQuestions={currSelectedQuestions}
- setCurrSelectedQuestions={setCurrSelectedQuestions}
- onSelectQuestion={handleSelectQuestion}
- data={difficultyLevelFilter?.searchQuestionsByFields}
- questionPaper={data}
- refetch={paperRefetch}
- />
- </div>
- </div>
- </div>
- <FeedbackDialog
- open={!!showFeedback}
- onClose={() => setShowFeedback(null)}
- feedback=""
- onSubmit={(feedback) => {
- // Handle feedback submission
- setShowFeedback(null);
- }}
- />
- </div>
- );
- }
- export default CreateQuestionPaper;
Advertisement
Add Comment
Please, Sign In to add comment