ENCOA-228 Now when user navigates between modules the generation items persist. Reading, listening and writing added to level module

This commit is contained in:
Carlos-Mesquita
2024-11-12 14:17:54 +00:00
parent 696c968ebc
commit fdf411d133
66 changed files with 2546 additions and 1635 deletions

View File

@@ -14,27 +14,51 @@ interface GeneratorConfig {
export function generate(
sectionId: number,
module: Module,
type: "context" | "exercises",
type: Generating,
config: GeneratorConfig,
mapData: (data: any) => Record<string, any>[]
mapData: (data: any) => Record<string, any>[],
levelSectionId?: number,
level: boolean = false
) {
const dispatch = useExamEditorStore.getState().dispatch;
const setGenerating = (sectionId: number, generating: Generating, level: boolean, remove?: boolean) => {
const state = useExamEditorStore.getState();
const dispatch = state.dispatch;
let generatingUpdate;
if (level) {
if (remove) {
generatingUpdate = state.modules["level"].sections.find((s) => s.sectionId === levelSectionId)!.levelGenerating.filter(g => g === generating)
}
else {
generatingUpdate = [...state.modules["level"].sections.find((s) => s.sectionId === levelSectionId)!.levelGenerating, generating];
}
} else {
generatingUpdate = generating;
}
const setGenerating = (sectionId: number, generating: Generating) => {
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: { sectionId, module, field: "generating", value: generating }
payload: { sectionId : sectionId, module: level ? "level" : module, field: level ? "levelGenerating" : "generating", value: generatingUpdate }
});
};
const setGeneratedExercises = (sectionId: number, exercises: Record<string, any>[] | undefined) => {
const setGeneratedResult = (sectionId: number, generating: Generating, result: Record<string, any>[] | undefined, level: boolean) => {
const state = useExamEditorStore.getState();
const dispatch = state.dispatch;
let genResults;
if (level) {
genResults = [...state.modules["level"].sections.find((s) => s.sectionId === levelSectionId)!.levelGenResults, { generating, result, module }];
} else {
genResults = { generating, result, module };
}
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: { sectionId, module, field: "genResult", value: exercises }
payload: { sectionId, module: level ? "level" : module, field: level ? "levelGenResults" : "genResult", value: genResults }
});
};
setGenerating(sectionId, type);
setGenerating(level ? levelSectionId! : sectionId, type, level);
const queryString = config.queryParams
? new URLSearchParams(config.queryParams).toString()
@@ -49,13 +73,11 @@ export function generate(
request
.then((result) => {
playSound("check");
setGeneratedExercises(sectionId, mapData(result.data));
setGeneratedResult(level ? levelSectionId! : sectionId, type, mapData(result.data), level);
})
.catch((error) => {
setGenerating(sectionId, undefined, level, true);
playSound("error");
toast.error("Something went wrong! Try to generate again.");
})
.finally(() => {
setGenerating(sectionId, undefined);
});
}

View File

@@ -2,6 +2,7 @@ import { Module } from "@/interfaces";
import useExamEditorStore from "@/stores/examEditor";
import { Generating } from "@/stores/examEditor/types";
import clsx from "clsx";
import { useEffect, useState } from "react";
import { BsArrowRepeat } from "react-icons/bs";
import { GiBrain } from "react-icons/gi";
@@ -11,25 +12,37 @@ interface Props {
genType: Generating;
generateFnc: (sectionId: number) => void
className?: string;
levelId?: number;
level?: boolean;
}
const GenerateBtn: React.FC<Props> = ({module, sectionId, genType, generateFnc, className}) => {
const section = useExamEditorStore((store) => store.modules[module].sections.find((s)=> s.sectionId == sectionId));
const GenerateBtn: React.FC<Props> = ({ module, sectionId, genType, generateFnc, className, level = false, levelId }) => {
const section = useExamEditorStore((store) => store.modules[level ? "level" : module].sections.find((s) => s.sectionId == levelId ? levelId : sectionId));
const [loading, setLoading] = useState(false);
const generating = section?.generating;
const levelGenerating = section?.levelGenerating;
useEffect(()=> {
const gen = level ? levelGenerating?.find(g => g === genType) !== undefined : (generating !== undefined && generating === genType);
if (loading !== gen) {
setLoading(gen);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [generating, levelGenerating])
if (section === undefined) return <></>;
const {generating} = section;
const loading = generating && generating === genType;
return (
<button
key={`section-${sectionId}`}
className={clsx(
"flex items-center w-[140px] justify-center px-4 py-2 text-white rounded-xl transition-colors duration-300 text-lg",
`bg-ielts-${module}/70 border border-ielts-${module} hover:bg-ielts-${module}`,
"flex items-center w-[140px] justify-center px-4 py-2 text-white rounded-xl transition-colors duration-300 text-lg disabled:cursor-not-allowed",
`bg-ielts-${module}/70 border border-ielts-${module} hover:bg-ielts-${module} disabled:bg-ielts-${module}/40`,
className
)}
onClick={loading ? () => { } : () => generateFnc(sectionId)}
disabled={loading}
onClick={loading ? () => { } : () => generateFnc(levelId ? levelId : sectionId)}
>
{loading ? (
<div key={`section-${sectionId}`} className="flex items-center justify-center">
@@ -43,6 +56,6 @@ const GenerateBtn: React.FC<Props> = ({module, sectionId, genType, generateFnc,
)}
</button>
);
}
}
export default GenerateBtn;

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { Module } from "@/interfaces";
import useExamEditorStore from "@/stores/examEditor";
import Dropdown from "./SettingsDropdown";
import { LevelSectionSettings } from "@/stores/examEditor/types";
interface Props {
module: Module;
sectionId: number;
localSettings: LevelSectionSettings;
updateLocalAndScheduleGlobal: (updates: Partial<LevelSectionSettings>, schedule?: boolean) => void;
}
const SectionPicker: React.FC<Props> = ({
module,
sectionId,
localSettings,
updateLocalAndScheduleGlobal
}) => {
const { dispatch } = useExamEditorStore();
const [selectedValue, setSelectedValue] = React.useState<number | null>(null);
const sectionState = useExamEditorStore(state =>
state.modules["level"].sections.find((s) => s.sectionId === sectionId)
);
if (sectionState === undefined) return null;
const { readingSection, listeningSection } = sectionState;
const currentValue = selectedValue ?? (module === "reading" ? readingSection : listeningSection);
const options = module === "reading" ? [1, 2, 3] : [1, 2, 3, 4];
const openPicker = module === "reading" ? "isReadingPickerOpen" : "isListeningPickerOpen";
const handleSectionChange = (value: number) => {
setSelectedValue(value);
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: "level",
field: module === "reading" ? "readingSection" : "listeningSection",
value: value
}
});
};
const getTitle = () => {
const section = module === "reading" ? "Passage" : "Section";
if (!currentValue) return `Choose a ${section}`;
return `${section} ${currentValue}`;
};
return (
<Dropdown
title={getTitle()}
module={module}
open={localSettings[openPicker]}
setIsOpen={(isOpen: boolean) =>
updateLocalAndScheduleGlobal({ [openPicker]: isOpen }, false)
}
contentWrapperClassName={`pt-6 px-4 bg-gray-200 rounded-b-lg shadow-md transition-all duration-300 ease-in-out border border-ielts-${module}`}
>
<div className="space-y-2 pt-3 pb-3 px-2 border border-gray-200 rounded-lg shadow-inner">
{options.map((num) => (
<label
key={num}
className={`
flex items-center space-x-3 font-semibold cursor-pointer p-2 rounded
transition-colors duration-200
${currentValue === num
? `bg-ielts-${module}/90 text-white`
: `hover:bg-ielts-${module}/70 text-gray-700`}
`}
>
<input
type="radio"
name={`${module === "reading" ? 'passage' : 'section'}-${sectionId}`}
value={num}
checked={currentValue === num}
onChange={() => handleSectionChange(num)}
className={`
h-5 w-5 cursor-pointer
accent-ielts-${module}
`}
/>
<div className="flex items-center space-x-2">
<span>
{module === "reading" ? `Passage ${num}` : `Section ${num}`}
</span>
</div>
</label>
))}
</div>
</Dropdown>
);
};
export default SectionPicker;

View File

@@ -10,9 +10,10 @@ interface Props {
setIsOpen: (isOpen: boolean) => void;
children: ReactNode;
center?: boolean;
contentWrapperClassName?: string;
}
const SettingsDropdown: React.FC<Props> = ({ module, title, open, setIsOpen, children, disabled = false, center = false}) => {
const SettingsDropdown: React.FC<Props> = ({ module, title, open, setIsOpen, children, contentWrapperClassName = '', disabled = false, center = false}) => {
return (
<Dropdown
title={title}
@@ -21,7 +22,7 @@ const SettingsDropdown: React.FC<Props> = ({ module, title, open, setIsOpen, chi
`bg-ielts-${module}/70 border border-ielts-${module} hover:bg-ielts-${module} disabled:bg-ielts-${module}/30`,
open ? "rounded-t-lg" : "rounded-lg"
)}
contentWrapperClassName={`pt-6 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out ${center ? "flex justify-center" : ""}`}
contentWrapperClassName={`pt-6 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out ${center ? "flex justify-center" : ""} ${contentWrapperClassName}`}
open={open}
setIsOpen={setIsOpen}
disabled={disabled}