import { Badge, Box, Button, ButtonGroup, Center, Container, Divider, Heading, HStack, Icon, IconButton, Skeleton, Stack, Text, Tooltip, useBreakpointValue, useDisclosure, useToast, VStack, } from '@chakra-ui/react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { __ } from '@wordpress/i18n'; import queryString from 'query-string'; import React, { useEffect, useState } from 'react'; import { DragDropContext, Draggable, Droppable, DropResult, } from 'react-beautiful-dnd'; import { FormProvider, useForm, useWatch } from 'react-hook-form'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import NoQuestion from '../../../../assets/img/svgs/no-question.svg'; import { Sortable } from '../../../../assets/js/back-end/assets/icons'; import AddNewButton from '../../../../assets/js/back-end/components/common/AddNewButton'; import BackToBuilder from '../../../../assets/js/back-end/components/common/BackToBuilder'; import BuilderHeader from '../../../../assets/js/back-end/components/common/BuilderHeader'; import QuizBuilderTabs, { defaultPageIndex, } from '../../../../assets/js/back-end/components/common/QuizBuilderTabs'; import { Trash } from '../../../../assets/js/back-end/constants/images'; import routes from '../../../../assets/js/back-end/constants/routes'; import urls from '../../../../assets/js/back-end/constants/urls'; import { BiLinkExternal } from 'react-icons/bi'; import Description from '../../../../assets/js/back-end/screens/quiz/components/Description'; import Name from '../../../../assets/js/back-end/screens/quiz/components/Name'; import QuizSKeleton from '../../../../assets/js/back-end/skeleton/QuizSkeleton'; import { CourseDataMap } from '../../../../assets/js/back-end/types/course'; import API from '../../../../assets/js/back-end/utils/api'; import { convertMinutesToHours } from '../../../../assets/js/back-end/utils/math'; import { deepClean, editContentInBuilderCache, } from '../../../../assets/js/back-end/utils/utils'; import H5P_BADGE_COLOR from '../../constants/badgeColors'; import h5pRoutes from '../../constants/routes'; import h5pUrls from '../../constants/urls'; import TruncatedText from '../TruncatedText'; import H5PContentPicker, { H5PContent } from './H5PContentPicker'; import H5PQuizSettings from './H5PQuizSettings'; const EditH5PQuiz = () => { const { courseId, h5pQuizId }: any = useParams(); const { search } = useLocation(); const { page } = queryString.parse(search); const navigate = useNavigate(); const buttonSize = useBreakpointValue(['sm', 'md']); const toast = useToast(); const queryClient = useQueryClient(); const methods = useForm(); const h5pQuizAPI = new API(h5pUrls.h5pQuizzes); const courseAPI = new API(urls.courses); const activeStep = useWatch({ control: methods.control, name: 'activeQuizStep', defaultValue: defaultPageIndex(page as string), }) as number; const { isOpen: isPickerOpen, onOpen: openPicker, onClose: closePicker, } = useDisclosure(); const [selectedQuestions, setSelectedQuestions] = useState([]); const courseQuery = useQuery({ queryKey: [`course${courseId}`, courseId], queryFn: () => courseAPI.get(courseId, 'edit'), }); const h5pQuizQuery = useQuery({ queryKey: [`h5pQuiz${h5pQuizId}`, h5pQuizId], queryFn: () => h5pQuizAPI.get(h5pQuizId, 'edit'), }); const updateH5PQuiz = useMutation({ mutationFn: (data: any) => h5pQuizAPI.update(h5pQuizId, data), }); useEffect(() => { if (h5pQuizQuery?.isSuccess && h5pQuizQuery?.data) { // Rebuild the split fields the form binds to, else they revert on save. const [durationHour, durationMinute] = convertMinutesToHours( h5pQuizQuery.data.duration || 0, ); const perPage = h5pQuizQuery.data.questions_display_per_page; methods.reset({ ...h5pQuizQuery.data, duration_hour: durationHour, duration_minute: durationMinute, questions_display_per_page_mode: perPage ? '1' : '0', questions_display_per_page_custom: perPage || 5, activeQuizStep: methods.getValues('activeQuizStep') ?? defaultPageIndex(page as string), }); const ids: number[] = Array.isArray(h5pQuizQuery.data.h5p_content_ids) ? h5pQuizQuery.data.h5p_content_ids : h5pQuizQuery.data.h5p_content_id ? [h5pQuizQuery.data.h5p_content_id] : []; setSelectedQuestions( ids.map((id: number, i: number) => ({ id, title: h5pQuizQuery.data.h5p_content_titles?.[i] || `Content ID ${id}`, content_type: h5pQuizQuery.data.h5p_content_library_names?.[i] || h5pQuizQuery.data.h5p_library_name || '', library_name: h5pQuizQuery.data.h5p_content_machine_names?.[i] || h5pQuizQuery.data.h5p_library_name || '', created_at: '', view_url: h5pQuizQuery.data.h5p_content_view_urls?.[i] || '', })), ); } }, [h5pQuizQuery?.data, h5pQuizQuery?.isSuccess]); const onSubmit = (data: any, status?: 'draft' | 'publish') => { const payload = { ...deepClean(data), h5p_content_ids: selectedQuestions.map((q) => q.id), duration: (Number(data.duration_hour) || 0) * 60 + (Number(data.duration_minute) || 0), questions_display_per_page: data.questions_display_per_page_mode === '0' ? 0 : Number(data.questions_display_per_page_custom) || 5, status: status || data.status, }; updateH5PQuiz.mutate(payload, { onSuccess: (data: any) => { editContentInBuilderCache( queryClient, [`builder${courseId}`, courseId], data, ); queryClient.invalidateQueries({ queryKey: [`h5pQuiz${h5pQuizId}`] }); toast({ title: __('H5P Quiz Updated', 'learning-management-system'), status: 'success', isClosable: true, }); courseQuery.refetch(); }, onError: (err: any) => { toast({ title: err?.message || __('Something went wrong', 'learning-management-system'), status: 'error', isClosable: true, }); }, }); }; const addQuestions = (items: H5PContent[]) => { setSelectedQuestions((prev) => { const existingIds = new Set(prev.map((q) => q.id)); return [...prev, ...items.filter((i) => !existingIds.has(i.id))]; }); }; const onDragEnd = (result: DropResult) => { if (!result.destination) return; if (result.destination.index === result.source.index) return; const items = Array.from(selectedQuestions); const [moved] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, moved); setSelectedQuestions(items); }; if (!h5pQuizQuery.isSuccess || !courseQuery.isSuccess) { return ; } return ( methods.handleSubmit((data) => onSubmit({ ...data, status })) } previewLink={h5pQuizQuery?.data?.preview_link || ''} isLoading={updateH5PQuiz.isPending} status={h5pQuizQuery?.data?.status} /> navigate( h5pRoutes.h5pQuiz.builder.edit .replace(':courseId', courseId) .replace(':h5pQuizId', h5pQuizId) + (idx === 1 ? '?page=settings' : ''), ) } />
onSubmit(data))}> {/* Content tab — info fields and the question list. */} {/* Quiz Settings tab */}
); }; export default EditH5PQuiz;