Skip to content

Commit

Permalink
Add full flow of add questions
Browse files Browse the repository at this point in the history
  • Loading branch information
linxiaoxin committed May 26, 2024
1 parent 030b094 commit 6852f20
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 53 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ jobs:
- name: Clean Install dependencies
run: npm ci

#Run lint sonar cloud scan

#Run sonar cloud scan
#- name: SonarCloud Scan
# uses: sonarsource/sonarcloud-github-action@2.2.1
# env:
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

- name: Build
run: npm run build --if-present

Expand Down
128 changes: 103 additions & 25 deletions app/(main)/questions/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { Questions } from '@/types';
import { QuestionsService } from '../../../../service/QuestionsService';
import { TabView, TabPanel } from 'primereact/tabview';
import { Editor, EditorTextChangeEvent } from "primereact/editor";
import { InputSwitch, InputSwitchChangeEvent } from 'primereact/inputswitch';
import { InputSwitch, InputSwitchChangeEvent } from "primereact/inputswitch";
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';


const EditQuestion = () => {
const [selectedTopics, setSelectedTopics] = useState(null);
Expand All @@ -20,6 +22,8 @@ const EditQuestion = () => {
const [isAnswer, setIsAnswer] = useState<boolean>(false);
const [listOfTopics, setListOfTopics] = useState<Questions.Topic[]>([]);
const [listOfSkills, setListOfSkills] = useState<Questions.Skill[]>([]);
const [showOptionDialog, setShowOptionDialog] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<number>(0);

useEffect(()=>{
QuestionsService.getTopics().then((data) => {
Expand All @@ -32,19 +36,36 @@ const EditQuestion = () => {

const optionsItemTemplate = (option: Questions.Option) => {
return(
<Editor readOnly value={option.text + option.isAnswer} showHeader={false}>
<Editor readOnly value={option.text} showHeader={false}>
</Editor>
)
}
const optionItemActionTemplate = (option: Questions.Option) => {
return(
<Button className="pi pi-times-circle" style={{ color: 'red' }} rounded text onClick={(e) => {handleOnDeleteOption(option.no)}}></Button>
<Button className="pi pi-trash" style={{ color: 'red' }} rounded text onClick={(e) => {handleOnDeleteOption(option.no)}}></Button>
)
}
const optionItemisAnswer = (option: Questions.Option) => {
return {'bg-primary' : option.isAnswer};
}


const optionItemAnswerSwitch = (option: Questions.Option) => {
return(
<InputSwitch checked={option.isAnswer} onChange={(e: InputSwitchChangeEvent) => handleOnSelectAnswer(option.no, e.value)} />
)
}

const OptionsHeader = (
<div className="grid">
<div className="col-10">
</div>
<div className="col-2">
<Button icon="pi pi-plus" rounded label="Add" size="small" onClick={() => setShowOptionDialog(true)} />
</div>
</div>
);

const handleOnDeleteOption = (input_no: number) => {
addedOptions.splice(input_no-1, 1);
const reOrderedOptions =addedOptions.map((option, index) => {
Expand All @@ -56,6 +77,20 @@ const EditQuestion = () => {
})
setAddedOptions(reOrderedOptions);
}
const handleOnSelectAnswer = (selectedNo: number, checked: boolean) => {
var refreshAnswer = addedOptions;
refreshAnswer[selectedNo-1].isAnswer = checked;
if(checked){
refreshAnswer =addedOptions.map((option) => {
return {
no: option.no,
text: option.text,
isAnswer: option.no === selectedNo
}
})
}
setAddedOptions(refreshAnswer);
}
const handleOnClickAdd = () => {
//add options
console.log(answer);
Expand All @@ -78,19 +113,38 @@ const EditQuestion = () => {
setAddedOptions(options);
setAnswer("");
setIsAnswer(false);

setShowOptionDialog(false);
};
const handleOnClickDone =() => {
//validate
const answer = addedOptions.filter((option: Questions.Option)=>{
return option.isAnswer
})
if(!answer || answer.length <= 0 )
throw "No answer selected"
console.log(answer)

//instantiate MCQ object before adding
let mcq = {
stem: stem,
option: addedOptions,
isAnswer: answer[0].no,
status: "Draft",
topics: selectedTopics,
skills: selectedSkills
}
console.log(mcq)
}
return (
<>
<h5>Add Question</h5>
<br/>
<TabView>
<TabView activeIndex={activeTab} onTabChange={(e) => setActiveTab(e.index)}>
<TabPanel header="General Information">
<div className="grid">
<div className="col-12 md:col-6 mb-5">
<FloatLabel>
<MultiSelect id="ms-topics"
<MultiSelect id="ms-topics"
value={selectedTopics}
onChange={(e) => setSelectedTopics(e.value)}
options={listOfTopics}
Expand Down Expand Up @@ -120,9 +174,12 @@ const EditQuestion = () => {
<label htmlFor="ms-skills">Skills</label>
</FloatLabel>
</div>
<div>
<div className="col-12">
Other information like publised date
</div>
<div className="col-12">
<Button label="Next" onClick={()=> {setActiveTab(1)}}></Button>
</div>
</div>
</TabPanel>
<TabPanel header="Stem">
Expand All @@ -132,40 +189,61 @@ const EditQuestion = () => {
<Editor value={stem} onTextChange={(e: EditorTextChangeEvent) => setStem(e.htmlValue || '')} style={{ height: '320px' }} />
</div>
</div>

<div className="col-12">
<Button label="Next" onClick={()=> {setActiveTab(2)}}></Button>
</div>
</TabPanel>
<TabPanel header="Options">
<div className="grid">
<div className="col-12">Fill in the options of the question and indicate answer.</div>
<div className="col-12">
<Editor value={answer} onTextChange={(e: EditorTextChangeEvent) => setAnswer(e.htmlValue || '')} style={{ height: '50px' }} />
</div>
<div className="card justify-content-center col-3">
<InputSwitch checked={isAnswer} onChange={(e: InputSwitchChangeEvent) => setIsAnswer(e.value)} />Is Answer
</div>
<div className="col-3">
<Button label="Add" onClick={handleOnClickAdd} />
<Dialog visible={showOptionDialog} style={{ width: '80vw' }} modal={false} position="bottom" onHide={() => {if (!showOptionDialog) return; setShowOptionDialog(false); }}>
<div className="grid">
<div className="col-10">Fill in the options of the question and indicate answer.</div>
<div className="col-2">
<Button label="Add" onClick={handleOnClickAdd} size="small"/>
</div>
<div className="col-12">
<Editor value={answer} onTextChange={(e: EditorTextChangeEvent) => setAnswer(e.htmlValue || '')} style={{ height: '100px' }} />
</div>
<div className="col-10">
</div>
</div>
</Dialog>
<div className="grid">
<div className="col-12">
<DataTable rowClassName={optionItemisAnswer} value={addedOptions} showHeaders={false} >
<DataTable rowClassName={optionItemisAnswer} value={addedOptions}
header={OptionsHeader}
emptyMessage="Add options for question">
<Column field="no" style={{ width: '5%' }}></Column>
<Column style={{ width: '90%' }} body={optionsItemTemplate}></Column>
<Column header="Answer" style={{ width: '10%' }} body={optionItemAnswerSwitch}></Column>
<Column style={{ width: '5%' }} body={optionItemActionTemplate}></Column>
</DataTable>
</div>
<div className="col-12">
<Button label="Next" onClick={()=> {setActiveTab(3)}}></Button>
</div>
</div>
</TabPanel>
<TabPanel header="Review">
<div className="grid">
<div className="col-12">
<Editor readOnly value={stem} showHeader={false} style={{ height: '400px' }}>
</Editor>
</div>

<div className="col-12">
<Editor readOnly value={stem} showHeader={false} style={{ height: '400px' }}>
</Editor>
</div>
<div className="col-12">
<DataTable value={addedOptions} emptyMessage="No options added">
<Column field="no" style={{ width: '5%' }}></Column>
<Column style={{ width: '90%' }} body={optionsItemTemplate}></Column>
</DataTable>
</div>
<div className="col-11">&nbsp;</div>
<div className="col-1">
<Button label="Done" onClick={handleOnClickDone}></Button>
</div>

</div>
</TabPanel>
</TabView>

</>
)
}
Expand Down
1 change: 0 additions & 1 deletion app/(main)/questions/searchlist/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const QuestioSearchList = () => {
const [MCQ, setMCQ] = useState<Questions.MCQ[]>([]);
const [loading, setLoading] = useState(true);


useEffect(() => {
QuestionsService.getMCQ().then((data) => {
setMCQ(data);
Expand Down
46 changes: 28 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"lint": "next lint"
},
"dependencies": {
"@types/node": "20.3.1",
"@types/react": "18.2.12",
"@types/react-dom": "18.2.5",
"@types/node": "20.12.12",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"chart.js": "4.2.1",
"next": "^14.2.3",
"primeflex": "^3.3.1",
Expand Down
12 changes: 8 additions & 4 deletions types/questions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ declare namespace Questions {

interface MCQ {
id?: number;
stem?: string;
key: Answer;
distractors: Answer[];
stem: string;
option: Answer[];
isAnswer: number;
topics: Topic[];
skills: Skills[];
status: string ;
published_on?: Date ;
published_by?: string ;
}
closed_on?: Date,
closed_by?: string,
created_on?: Date,
created_by?: string,
}

interface Option{
no: number;
Expand Down

0 comments on commit 6852f20

Please sign in to comment.