Merge branch 'fix/review-pretest' into 'master'

fix(refactor): Level page review pretest navigator and Refactor history and...

See merge request profile-image/kedaireka/polinema-adapative-learning/frontend-adaptive-learning!2
This commit is contained in:
Naresh Pratista 2024-12-11 03:46:02 +00:00
commit b2c76268f9
5 changed files with 686 additions and 494 deletions

View File

@ -1,54 +1,75 @@
import axiosInstance from '../../../../utils/axiosInstance'; import axiosInstance from '../../../../utils/axiosInstance'
const fetchTopic = async () => { const fetchTopic = async () => {
try { try {
const response = await axiosInstance.get(`/topic`); const response = await axiosInstance.get(`/topic`)
return response.data; return response.data
} catch (error) { } catch (error) {
throw error; console.error(error)
throw error
} }
}; }
const fetchAllHistory = async () => { const fetchAllHistory = async () => {
try { try {
const response = await axiosInstance.get(`/learningHistory`); const response = await axiosInstance.get(`/learningHistory`)
const historyData = response.data.payload.history; const historyData = response.data.payload.history
const validHistory = historyData.filter(history => history.STUDENT_FINISH !== null); const validHistory = historyData.filter(
history => history.STUDENT_FINISH !== null
)
return {message: response.data.message, payload: validHistory, status: response.data.statusCode,}; return {
message: response.data.message,
} catch (error) { payload: validHistory,
throw error; status: response.data.statusCode,
} }
}; } catch (error) {
console.error(error)
throw error
}
}
const fetchHistoryBySection = async (section) => { const fetchHistoryBySection = async section => {
try { try {
const response = await axiosInstance.get(`/learningHistory/section/${section}`); const response = await axiosInstance.get(
const historyData = response.data.payload.history; `/learningHistory/section/${section}`
const validHistory = historyData.filter(history => history.STUDENT_FINISH !== null); )
const historyData = response.data.payload.history
const validHistory = historyData.filter(
history => history.STUDENT_FINISH !== null
)
return {message: response.data.message, payload: validHistory, status: response.data.statusCode}; return {
} catch (error) { message: response.data.message,
return {payload: []}; payload: validHistory,
status: response.data.statusCode,
} }
}; } catch (error) {
return { payload: [] }
}
}
const fetchHistoryByTopic = async (topic) => { const fetchHistoryByTopic = async topic => {
try { try {
const response = await axiosInstance.get(`/learningHistory/topic/${topic}`); const response = await axiosInstance.get(`/learningHistory/topic/${topic}`)
const historyData = response.data.payload.history; const historyData = response.data.payload.history
const validHistory = historyData.filter(history => history.STUDENT_FINISH !== null); const validHistory = historyData.filter(
history => history.STUDENT_FINISH !== null
)
return {message: response.data.message, payload: validHistory, status: response.data.statusCode,}; return {
} catch (error) { message: response.data.message,
return {payload: []}; payload: validHistory,
status: response.data.statusCode,
} }
}; } catch (error) {
return { payload: [] }
}
}
export default { export default {
fetchTopic, fetchTopic,
fetchAllHistory, fetchAllHistory,
fetchHistoryBySection, fetchHistoryBySection,
fetchHistoryByTopic fetchHistoryByTopic,
}; }

View File

@ -1,9 +1,19 @@
import React from 'react'; /* eslint-disable no-mixed-spaces-and-tabs */
import { Container, Row, Col, Card, Button, Dropdown, Breadcrumb, Tab, Nav } from 'react-bootstrap'; import {
import useHistories from '../hooks/useHirtories'; Container,
import { Link } from 'react-router-dom'; Row,
import newBie from '../../../../assets/images/illustration/emptyJourney.png'; Col,
import Skeleton from 'react-loading-skeleton'; Card,
Button,
Dropdown,
Breadcrumb,
Tab,
Nav,
} from 'react-bootstrap'
import useHistories from '../hooks/useHirtories'
import { Link } from 'react-router-dom'
import newBie from '../../../../assets/images/illustration/emptyJourney.png'
import Skeleton from 'react-loading-skeleton'
const ExerciseHistory = () => { const ExerciseHistory = () => {
const { const {
@ -17,19 +27,31 @@ const ExerciseHistory = () => {
setActiveTab, setActiveTab,
formatLocalDate, formatLocalDate,
compareLevels, compareLevels,
setSelectedTopic setSelectedTopic,
} = useHistories(); } = useHistories()
if (error) return <p>{error}</p>; if (error) return <p>{error}</p>
const topics = activeTab !== 'all' ? topicsBySection[activeTab] || []: []; const topics = activeTab !== 'all' ? topicsBySection[activeTab] || [] : []
return ( return (
<div className='exercise-history'> <div className="exercise-history">
<h3 className='fw-bold text-blue'>Your Exercise History!</h3> <h3 className="fw-bold text-blue">Your Exercise History!</h3>
<p className='text-secondary mb-4'>Track your progress with a personalized overview of all your workouts.</p> <p className="text-secondary mb-4">
<Container fluid className='mb-4 d-flex align-items-center bg-white rounded rounded-4'> Track your progress with a personalized overview of all your workouts.
<Tab.Container id="left-tabs-example" defaultActiveKey="all" onSelect={(k) => { setActiveTab(k); setSelectedTopic(''); }}> </p>
<Container
fluid
className="mb-4 d-flex align-items-center bg-white rounded rounded-4"
>
<Tab.Container
id="left-tabs-example"
defaultActiveKey="all"
onSelect={k => {
setActiveTab(k)
setSelectedTopic('')
}}
>
<Row> <Row>
<Col> <Col>
<Nav variant="pills" className="p-2 bg-white"> <Nav variant="pills" className="p-2 bg-white">
@ -46,13 +68,26 @@ const ExerciseHistory = () => {
</Row> </Row>
</Tab.Container> </Tab.Container>
{topics.length > 0 && ( {topics.length > 0 && (
<Dropdown className='ms-auto' onSelect={(topicId) => setSelectedTopic(topicId)}> <Dropdown
<Dropdown.Toggle id="dropdown-basic" size='sm' variant="outline-blue rounded-3" style={{padding:"6px 16px"}}> className="ms-auto"
{selectedTopic ? topics.find(t => t.ID_TOPIC === selectedTopic)?.NAME_TOPIC : 'Select a Topic'} onSelect={topicId => setSelectedTopic(topicId)}
>
<Dropdown.Toggle
id="dropdown-basic"
size="sm"
variant="outline-blue rounded-3"
style={{ padding: '6px 16px' }}
>
{selectedTopic
? topics.find(t => t.ID_TOPIC === selectedTopic)?.NAME_TOPIC
: 'Select a Topic'}
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu> <Dropdown.Menu>
{topics.map((topic, index) => ( {topics.map((topic, index) => (
<Dropdown.Item key={topic.ID_TOPIC ? topic.ID_TOPIC : index} eventKey={topic.ID_TOPIC ? topic.ID_TOPIC : index}> <Dropdown.Item
key={topic.ID_TOPIC ? topic.ID_TOPIC : index}
eventKey={topic.ID_TOPIC ? topic.ID_TOPIC : index}
>
{topic.NAME_TOPIC} {topic.NAME_TOPIC}
</Dropdown.Item> </Dropdown.Item>
))} ))}
@ -60,30 +95,53 @@ const ExerciseHistory = () => {
</Dropdown> </Dropdown>
)} )}
</Container> </Container>
<Container fluid className='bg-white rounded rounded-4 p-4'> <Container fluid className="bg-white rounded rounded-4 p-4">
<Row xs={1}> <Row xs={1}>
{loading?( {loading ? (
<Col> <Col>
<Card className='flex-column p-3 border rounded-4'> <Card className="flex-column p-3 border rounded-4">
<div className="w-100 d-flex justify-content-between"> <div className="w-100 d-flex justify-content-between">
<Skeleton containerClassName="mb-2" className='mb-1 rounded-4' style={{width:"15vw", height:"2vw"}} /> <Skeleton
<Skeleton containerClassName="mb-2" className='mb-1 rounded-4' style={{width:"25vw", height:"2vw"}} /> containerClassName="mb-2"
className="mb-1 rounded-4"
style={{ width: '15vw', height: '2vw' }}
/>
<Skeleton
containerClassName="mb-2"
className="mb-1 rounded-4"
style={{ width: '25vw', height: '2vw' }}
/>
</div> </div>
<Skeleton containerClassName="mb-2" className='mb-1 rounded-4' style={{width:"40vw", height:"2vw"}} /> <Skeleton
containerClassName="mb-2"
className="mb-1 rounded-4"
style={{ width: '40vw', height: '2vw' }}
/>
<div className="w-100 d-flex justify-content-between"> <div className="w-100 d-flex justify-content-between">
<div className='d-flex'> <div className="d-flex">
<Skeleton containerClassName="mb-2" className='mb-1 me-2 rounded-4' style={{width:"4vw", height:"5vw"}} /> <Skeleton
<Skeleton containerClassName="mb-2" className='mb-1 rounded-4' style={{width:"15vw", height:"5vw"}} /> containerClassName="mb-2"
className="mb-1 me-2 rounded-4"
style={{ width: '4vw', height: '5vw' }}
/>
<Skeleton
containerClassName="mb-2"
className="mb-1 rounded-4"
style={{ width: '15vw', height: '5vw' }}
/>
</div> </div>
<Skeleton containerClassName="mb-2" className='mb-1 rounded-4' style={{width:"30vw", height:"5vw"}} /> <Skeleton
containerClassName="mb-2"
className="mb-1 rounded-4"
style={{ width: '30vw', height: '5vw' }}
/>
</div> </div>
</Card> </Card>
</Col> </Col>
) : ) : historyData.length > 0 ? (
historyData.length > 0 ? (
historyData.map((history, index) => ( historyData.map((history, index) => (
<Col key={index} className='mb-3'> <Col key={index} className="mb-3">
<Card className='flex-row p-3 border-primary rounded-4'> <Card className="flex-row p-3 border-primary rounded-4">
{/* <Card.Body className='p-0 d-flex flex-column justify-content-between'> {/* <Card.Body className='p-0 d-flex flex-column justify-content-between'>
<Card.Title className='mb-1 w-100 d-flex justify-content-between'> <Card.Title className='mb-1 w-100 d-flex justify-content-between'>
<h4 className='mb-0'>{history.CURRENT_LEVEL}</h4> <h4 className='mb-0'>{history.CURRENT_LEVEL}</h4>
@ -124,62 +182,83 @@ const ExerciseHistory = () => {
</Card.Body> */} </Card.Body> */}
<Card.Body className="p-2 d-flex justify-content-between"> <Card.Body className="p-2 d-flex justify-content-between">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<h4 className='mb-0'>{history.CURRENT_LEVEL}</h4> <h4 className="mb-0">{history.CURRENT_LEVEL}</h4>
<Breadcrumb className='mt-2 custom-breadcrumb'> <Breadcrumb className="mt-2 custom-breadcrumb">
<Breadcrumb.Item>{history.SECTION_NAME}</Breadcrumb.Item> <Breadcrumb.Item>
<Breadcrumb.Item active>{history.TOPIC_NAME}</Breadcrumb.Item> {history.SECTION_NAME}
</Breadcrumb.Item>
<Breadcrumb.Item active>
{history.TOPIC_NAME}
</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<p className='mt-2 mb-0 submit-time'>Submission: {formatLocalDate(history.STUDENT_FINISH)}</p> <p className="mt-2 mb-0 submit-time">
<h3 className='mt-4 mb-0'> Submission: {formatLocalDate(history.STUDENT_FINISH)}
</p>
<h3 className="mt-4 mb-0">
<i className="bi bi-trophy-fill text-warning"></i> <i className="bi bi-trophy-fill text-warning"></i>
<span className='ms-3 text-blue fw-bold'>{history.SCORE}/100</span> <span className="ms-3 text-blue fw-bold">
{history.SCORE}/100
</span>
</h3> </h3>
</div> </div>
<div className="d-flex flex-column justify-content-between align-items-end"> <div className="d-flex flex-column justify-content-between align-items-end">
{history.IS_PASS == 1?( {history.IS_PASS == 1 ? (
<h5 className='text-green'> <h5 className="text-green">
<i className="bi bi-check2-all me-1"></i> <i className="bi bi-check2-all me-1"></i>
Topic Finished! Topic Finished!
</h5> </h5>
):( ) : compareLevels(
compareLevels(history.CURRENT_LEVEL, history.NEXT_LEVEL) === 'jump' ?( history.CURRENT_LEVEL,
<h5 className='text-blue'> history.NEXT_LEVEL
) === 'jump' ? (
<h5 className="text-blue">
<i className="bi bi-arrow-up me-1"></i> <i className="bi bi-arrow-up me-1"></i>
Jump to {history.NEXT_LEVEL} Jump to {history.NEXT_LEVEL}
</h5> </h5>
) : ( ) : compareLevels(
compareLevels(history.CURRENT_LEVEL, history.NEXT_LEVEL) === 'down' ?( history.CURRENT_LEVEL,
<h5 className='text-red'> history.NEXT_LEVEL
) === 'down' ? (
<h5 className="text-red">
<i className="bi bi-arrow-down me-1"></i> <i className="bi bi-arrow-down me-1"></i>
Go Down to {history.NEXT_LEVEL} Go Down to {history.NEXT_LEVEL}
</h5> </h5>
) : ( ) : (
<h5 className='text-dark'> <h5 className="text-dark">
<i className="bi bi-arrow-repeat me-1"></i> <i className="bi bi-arrow-repeat me-1"></i>
Stay in {history.NEXT_LEVEL} Stay in {history.NEXT_LEVEL}
</h5> </h5>
)
)
)} )}
<Link to={`/learning/review/${history.ID_STUDENT_LEARNING}`} className='btn btn-blue py-2 px-5 rounded-35'>Review</Link> <Link
to={`/learning/review/${history.ID_STUDENT_LEARNING}`}
className="btn btn-blue py-2 px-5 rounded-35"
>
Review
</Link>
</div> </div>
</Card.Body> </Card.Body>
</Card> </Card>
</Col> </Col>
)) ))
) : ( ) : (
<Col className='d-flex flex-column items-center'> <Col className="d-flex flex-column items-center">
<h4 className='mb-0'>Still new?</h4> <h4 className="mb-0">Still new?</h4>
<p className='fs-5 text-muted fw-light'>Begin your journey!</p> <p className="fs-5 text-muted fw-light">Begin your journey!</p>
<img src={newBie} alt="" /> <img src={newBie} alt="" />
<Button as={Link} to={'/learning/module'} variant='warning' className='mt-4 py-2 px-5 rounded-35'>Explore</Button> <Button
as={Link}
to={'/learning/module'}
variant="warning"
className="mt-4 py-2 px-5 rounded-35"
>
Explore
</Button>
</Col> </Col>
) )}
}
</Row> </Row>
</Container> </Container>
</div> </div>
); )
}; }
export default ExerciseHistory; export default ExerciseHistory

View File

@ -1,42 +1,70 @@
import React from 'react'; import { Container, Row, Col, Button, Breadcrumb } from 'react-bootstrap'
import { Container, Row, Col, Button, Breadcrumb } from 'react-bootstrap'; import { Link, useParams } from 'react-router-dom'
import { Link, useParams, Navigate } from 'react-router-dom'; import check from '../../../../assets/images/illustration/checkIcon.png'
import check from '../../../../assets/images/illustration/checkIcon.png';
// import { useValidation } from '../../utils/ValidationContext'; // import { useValidation } from '../../utils/ValidationContext';
import useLevels from '../hooks/useLevels'; import useLevels from '../hooks/useLevels'
import { unSlugify } from '../../../../utils/Constant'; import { unSlugify } from '../../../../utils/Constant'
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton'
const Level = () => { const Level = () => {
const { section, topic } = useParams(); const { section, topic } = useParams()
const { levels, lastCompletedLevel, loading, error, isLevelUnlocked, levelDesc } = useLevels(section, topic); const { levels, loading, error, isLevelUnlocked, levelDesc } = useLevels(
section,
topic
)
if (loading) { if (loading) {
return ( return (
<div className='row'> <div className="row">
<Skeleton containerClassName='w-100' className='w-100 mb-3 rounded-3' style={{height:"3vh"}} /> <Skeleton
<Skeleton containerClassName='w-100' className='w-100 mb-3 rounded-4' style={{height:"25vh"}} /> containerClassName="w-100"
<Skeleton containerClassName='w-100 d-flex flex-wrap justify-content-between' className='mb-3 rounded-4' count={4} inline={true} style={{width:"49%",height:"25vh"}} /> className="w-100 mb-3 rounded-3"
style={{ height: '3vh' }}
/>
<Skeleton
containerClassName="w-100"
className="w-100 mb-3 rounded-4"
style={{ height: '25vh' }}
/>
<Skeleton
containerClassName="w-100 d-flex flex-wrap justify-content-between"
className="mb-3 rounded-4"
count={4}
inline={true}
style={{ width: '49%', height: '25vh' }}
/>
</div> </div>
); )
} }
if (error) { if (error) {
return <div>Error: {error.message}</div>; return <div>Error: {error.message}</div>
} }
return ( return (
<Container className='level-page'> <Container className="level-page">
<Row> <Row>
<Col className="d-flex align-items-center mb-3 breadcrumb-con"> <Col className="d-flex align-items-center mb-3 breadcrumb-con">
<Button as={Link} variant='blue' className='btn-square-back' to={`/learning/module/${section}`}> <Button
as={Link}
variant="blue"
className="btn-square-back"
to={`/learning/module/${section}`}
>
<i className="bi bi-arrow-90deg-left"></i> <i className="bi bi-arrow-90deg-left"></i>
</Button> </Button>
<Breadcrumb className='custom-breadcrumb'> <Breadcrumb className="custom-breadcrumb">
<Breadcrumb.Item href="/learning/module"><i className="bi bi-book me-1"></i>Learning</Breadcrumb.Item> <Breadcrumb.Item href="/learning/module">
<Breadcrumb.Item href={`/learning/module/${section}`} className='text-capitalize'>{unSlugify(section)}</Breadcrumb.Item> <i className="bi bi-book me-1"></i>Learning
</Breadcrumb.Item>
<Breadcrumb.Item
href={`/learning/module/${section}`}
className="text-capitalize"
>
{unSlugify(section)}
</Breadcrumb.Item>
<Breadcrumb.Item active>{unSlugify(topic)}</Breadcrumb.Item> <Breadcrumb.Item active>{unSlugify(topic)}</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
</Col> </Col>
@ -47,34 +75,49 @@ const Level = () => {
) : ( ) : (
<Row> <Row>
{levels.map((level, index) => ( {levels.map((level, index) => (
<Col key={level.ID_LEVEL} xs={12} grant={level.ID_LEVEL} sm={level.IS_PRETEST === 1 ? 12 : 6} className='mb-3'> <Col
<div className={`p-3 level-con ${isLevelUnlocked(index) ? 'bg-gd' : 'bg-light-grey'} rounded-4`}> key={level.ID_LEVEL}
{(!isLevelUnlocked(index) && level.SCORE > 40) && <img src={check} alt="" className='check-icon' />} xs={12}
grant={level.ID_LEVEL}
sm={level.IS_PRETEST === 1 ? 12 : 6}
className="mb-3"
>
<div
className={`p-3 level-con ${
isLevelUnlocked(index) ? 'bg-gd' : 'bg-light-grey'
} rounded-4`}
>
{!isLevelUnlocked(index) && level.SCORE > 40 && (
<img src={check} alt="" className="check-icon" />
)}
<div className="mb-3 w-100 d-flex justify-content-between"> <div className="mb-3 w-100 d-flex justify-content-between">
{level.IS_PRETEST === 0 ?( {level.IS_PRETEST === 0 ? (
<div className="level-label"> <div className="level-label">
<h4 className='m-0 lh-normal'>LEVEL</h4> <h4 className="m-0 lh-normal">LEVEL</h4>
<span>{index}</span> <span>{index}</span>
</div> </div>
) : ( ) : (
<div className="level-label"> <div className="level-label">
<h4 className='m-0 lh-normal'>PRETEST</h4> <h4 className="m-0 lh-normal">PRETEST</h4>
</div> </div>
)} )}
<h5 className='m-0 text-white'>Score {level.SCORE === null ? 0 : level.SCORE}/100</h5> <h5 className="m-0 text-white">
Score {level.SCORE === null ? 0 : level.SCORE}/100
</h5>
</div> </div>
<p className='mb-3 text-white'> <p className="mb-3 text-white">{levelDesc[index]}</p>
{levelDesc[index]}
</p>
{level.IS_PRETEST === 1 && level.SCORE !== null ? (
{level.IS_PRETEST === 1 && level.SCORE !== null ?( <Button
<div className='py-2 px-5 mt-auto w-fit bg-light rounded-35'> variant="success"
className="py-2 px-5 w-fit rounded-35"
as={Link}
to={`/learning/review/${level.ID_STUDENT_LEARNING}`}
>
Review Review
</div> </Button>
):( ) : !isLevelUnlocked(index) ? (
!isLevelUnlocked(index) ? ( <div className="py-2 px-5 mt-auto w-fit bg-light rounded-35">
<div className='py-2 px-5 mt-auto w-fit bg-light rounded-35'>
Not Allowed Not Allowed
</div> </div>
) : ( ) : (
@ -89,22 +132,26 @@ const Level = () => {
// Finished // Finished
// </div> // </div>
// ) // )
<Button variant='warning' className='py-2 px-5 w-fit rounded-35' <Button
as={Link} to={level.IS_PRETEST === 1 ? `/learning/module/${section}/${topic}/pretest/material` : `/learning/module/${section}/${topic}/level-${index}/material`} variant="warning"
className="py-2 px-5 w-fit rounded-35"
as={Link}
to={
level.IS_PRETEST === 1
? `/learning/module/${section}/${topic}/pretest/material`
: `/learning/module/${section}/${topic}/level-${index}/material`
}
> >
Learn Now Learn Now
</Button> </Button>
)
)} )}
</div> </div>
</Col> </Col>
))} ))}
</Row> </Row>
)} )}
</Container> </Container>
); )
}; }
export default Level; export default Level

View File

@ -1,81 +1,81 @@
import axiosInstance from '../../../../utils/axiosInstance'; import axiosInstance from '../../../../utils/axiosInstance'
const getSoalNumber = (title) => { // const getSoalNumber = (title) => {
const match = title.match(/\d+$/); // const match = title.match(/\d+$/);
return match ? parseInt(match[0], 10) : 0; // return match ? parseInt(match[0], 10) : 0;
}; // };
const getLevelId = async (topicId, levelName) => { const getLevelId = async (topicId, levelName) => {
try { try {
const response = await axiosInstance.get(`/level/topic/${topicId}`); const response = await axiosInstance.get(`/level/topic/${topicId}`)
const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName); const filteredData = response.data.data.levels.filter(
return filteredData[0].ID_LEVEL; item => item.NAME_LEVEL === levelName
)
return filteredData[0].ID_LEVEL
} catch (error) { } catch (error) {
return []; return []
} }
}; }
const createStdLearning = async (data) => { const createStdLearning = async data => {
try { try {
const response = await axiosInstance.post(`/stdLearning`, data); const response = await axiosInstance.post(`/stdLearning`, data)
return response.data; return response.data
} catch (error) { } catch (error) {
console.error('Error creating std_learning:', error); console.error('Error creating std_learning:', error)
throw error; throw error
} }
}; }
const fetchReview = async (stdLearning) => { const fetchReview = async stdLearning => {
try { try {
const response = await axiosInstance.get(`/studentAnswers/${stdLearning}`); const response = await axiosInstance.get(`/studentAnswers/${stdLearning}`)
return response.data; return response.data
} catch (error) { } catch (error) {
throw error; console.error('Error fetching review:', error)
throw error
} }
}; }
const submitAnswer = async dataAnswer => {
const sumbitAnswer = async (dataAnswer) => {
try { try {
const response = await axiosInstance.post(`/stdExercise`, dataAnswer); const response = await axiosInstance.post(`/stdExercise`, dataAnswer)
return response.data; return response.data
} catch (error) { } catch (error) {
console.error('Error submit exercise:', error); console.error('Error submit exercise:', error)
throw error; throw error
} }
}; }
const checkStdLearning = async (level) => { const checkStdLearning = async level => {
try { try {
const response = await axiosInstance.get(`/stdLearning/level/${level}`); const response = await axiosInstance.get(`/stdLearning/level/${level}`)
return response.data.payload.ID_STUDENT_LEARNING; return response.data.payload.ID_STUDENT_LEARNING
} catch (error) { } catch (error) {
// console.error('Error checking std_learning:', error); // console.error('Error checking std_learning:', error);
// throw error; // throw error;
return null; return null
} }
}; }
const getScore = async (stdLearning) => { const getScore = async stdLearning => {
try { try {
const response = await axiosInstance.get(`/stdLearning/score/${stdLearning}`); const response = await axiosInstance.get(
return response.data; `/stdLearning/score/${stdLearning}`
)
return response.data
} catch (error) { } catch (error) {
console.error('Error fetching result:', error); console.error('Error fetching result:', error)
throw error; throw error
} }
}; }
export default { export default {
getLevelId, getLevelId,
checkStdLearning, checkStdLearning,
createStdLearning, createStdLearning,
fetchReview, fetchReview,
submitAnswer,
sumbitAnswer, getScore,
getScore }
};

View File

@ -1,15 +1,21 @@
import React from 'react'; import { useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'; import { useReview } from '../hooks/useReview'
import { useReview } from '../hooks/useReview'; import {
import { Container, Row, Col, ListGroup, Button, OverlayTrigger, Popover } from 'react-bootstrap'; Container,
import MultipleChoiceQuestion from './components/MultipleChoiceQuestion'; Row,
import TrueFalseQuestion from './components/TrueFalseQuestion'; Col,
import MatchingPairsQuestion from './components/MatchingPairsQuestion'; ListGroup,
import Skeleton from 'react-loading-skeleton'; Button,
OverlayTrigger,
Popover,
} from 'react-bootstrap'
import MultipleChoiceQuestion from './components/MultipleChoiceQuestion'
import TrueFalseQuestion from './components/TrueFalseQuestion'
import MatchingPairsQuestion from './components/MatchingPairsQuestion'
import Skeleton from 'react-loading-skeleton'
const Review = () => { const Review = () => {
const { stdLearning } = useParams(); const { stdLearning } = useParams()
const { const {
questions, questions,
currentQuestion, currentQuestion,
@ -20,7 +26,7 @@ const Review = () => {
error, error,
nextQuestion, nextQuestion,
prevQuestion, prevQuestion,
} = useReview(stdLearning); } = useReview(stdLearning)
const renderQuestion = () => { const renderQuestion = () => {
switch (currentQuestion.QUESTION_TYPE) { switch (currentQuestion.QUESTION_TYPE) {
@ -31,7 +37,7 @@ const Review = () => {
studentAnswer={currentQuestion.ANSWER_STUDENT} studentAnswer={currentQuestion.ANSWER_STUDENT}
index={currentQuestionIndex} index={currentQuestionIndex}
/> />
); )
case 'TFQ': case 'TFQ':
return ( return (
<TrueFalseQuestion <TrueFalseQuestion
@ -39,7 +45,7 @@ const Review = () => {
studentAnswer={currentQuestion.ANSWER_STUDENT} studentAnswer={currentQuestion.ANSWER_STUDENT}
index={currentQuestionIndex} index={currentQuestionIndex}
/> />
); )
case 'MPQ': case 'MPQ':
return ( return (
<MatchingPairsQuestion <MatchingPairsQuestion
@ -47,64 +53,102 @@ const Review = () => {
studentAnswer={currentQuestion.ANSWER_STUDENT} studentAnswer={currentQuestion.ANSWER_STUDENT}
index={currentQuestionIndex} index={currentQuestionIndex}
/> />
); )
default: default:
return <p>Unknown question type.</p>; return <p>Unknown question type.</p>
}
} }
};
const popover = ( const popover = (
<Popover id="popover-basic"> <Popover id="popover-basic">
<Popover.Header as="h4">Tips</Popover.Header> <Popover.Header as="h4">Tips</Popover.Header>
<Popover.Body className='p-2'> <Popover.Body className="p-2">
<ul className='ps-3 m-0'> <ul className="ps-3 m-0">
<li>Click the <strong>left arrow</strong> key to go to the previous question</li> <li>
<li>Click the <strong>right arrow</strong> key to go to the next question</li> Click the <strong>left arrow</strong> key to go to the previous
question
</li>
<li>
Click the <strong>right arrow</strong> key to go to the next
question
</li>
</ul> </ul>
</Popover.Body> </Popover.Body>
</Popover> </Popover>
); )
if (isLoading) { if (isLoading) {
return ( return (
<div className="row"> <div className="row">
<div className="col-2"> <div className="col-2">
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' count={6} style={{height:"4vh"}} /> <Skeleton
containerClassName="w-100"
className="w-100 mb-1 rounded-3"
count={6}
style={{ height: '4vh' }}
/>
</div> </div>
<div className="col-10"> <div className="col-10">
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' style={{height:"5vh"}} /> <Skeleton
<Skeleton containerClassName='w-100' className='w-50 mb-1 rounded-3' style={{height:"20vh"}} /> containerClassName="w-100"
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' style={{height:"30vh"}} /> className="w-100 mb-1 rounded-3"
style={{ height: '5vh' }}
/>
<Skeleton
containerClassName="w-100"
className="w-50 mb-1 rounded-3"
style={{ height: '20vh' }}
/>
<Skeleton
containerClassName="w-100"
className="w-100 mb-1 rounded-3"
style={{ height: '30vh' }}
/>
</div> </div>
</div> </div>
); )
} }
if (error) return <h1 className='text-center'>Exercise questions not yet available</h1>; if (error)
return <h1 className="text-center">Exercise questions not yet available</h1>
return ( return (
<Container fluid className='exercise-page'> <Container fluid className="exercise-page">
<Row> <Row>
<Col sm={2}> <Col sm={2}>
<div className='p-3 rounded-4 bg-white'> <div className="p-3 rounded-4 bg-white">
<div className="mb-3 d-flex justify-content-between align-items-center"> <div className="mb-3 d-flex justify-content-between align-items-center">
<h4 className='mb-0 text-gd fw-bold'>Review</h4> <h4 className="mb-0 text-gd fw-bold">Review</h4>
<OverlayTrigger trigger="click" placement="right" overlay={popover}> <OverlayTrigger
trigger="click"
placement="right"
overlay={popover}
>
<i className=" bi bi-info-circle cursor-pointer text-secondary"></i> <i className=" bi bi-info-circle cursor-pointer text-secondary"></i>
</OverlayTrigger> </OverlayTrigger>
</div> </div>
<ListGroup variant="flush" className='review-list'> <ListGroup variant="flush" className="review-list">
{questions.map((q, index) => ( {questions.map((q, index) => (
<ListGroup.Item <ListGroup.Item
key={q.ID_ADMIN_EXERCISE} key={q.ID_ADMIN_EXERCISE}
active={index === currentQuestionIndex} active={index === currentQuestionIndex}
onClick={() => setCurrentQuestionIndex(index)} onClick={() => setCurrentQuestionIndex(index)}
className={`border-0 rounded-3 number-label fw-bold ${q.IS_CORRECT === 1 ? 'correct' : 'incorrect'}`} className={`border-0 rounded-3 number-label fw-bold ${
q.IS_CORRECT === 1 ? 'correct' : 'incorrect'
}`}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
> >
<i className={`me-2 bi bi-circle ${answers[index] !== null ? 'd-none' : 'd-block'}`}></i> <i
<i className={`me-2 bi bi-check2-circle ${answers[index] !== null ? 'd-block' : 'd-none'}`}></i> className={`me-2 bi bi-circle ${
{index+1} answers[index] !== null ? 'd-none' : 'd-block'
}`}
></i>
<i
className={`me-2 bi bi-check2-circle ${
answers[index] !== null ? 'd-block' : 'd-none'
}`}
></i>
{index + 1}
</ListGroup.Item> </ListGroup.Item>
))} ))}
</ListGroup> </ListGroup>
@ -112,37 +156,38 @@ const Review = () => {
</Col> </Col>
<Col sm={10}> <Col sm={10}>
<div className='p-4 rounded-4 bg-white'> <div className="p-4 rounded-4 bg-white">
<div className="pb-4 d-flex justify-content-between align-items-center"> <div className="pb-4 d-flex justify-content-between align-items-center">
<Button <Button
variant='outline-blue' variant="outline-blue"
className={`rounded-35 ${currentQuestionIndex === 0 ? 'invisible' : 'visible'}`} className={`rounded-35 ${
currentQuestionIndex === 0 ? 'invisible' : 'visible'
}`}
onClick={prevQuestion} onClick={prevQuestion}
disabled={currentQuestionIndex === 0} disabled={currentQuestionIndex === 0}
> >
<i className="bi bi-arrow-left"></i> <i className="bi bi-arrow-left"></i>
</Button> </Button>
<h5 className='m-0'>{`Questions ${currentQuestionIndex + 1} of ${questions.length}`}</h5> <h5 className="m-0">{`Questions ${currentQuestionIndex + 1} of ${
questions.length
}`}</h5>
<Button <Button
variant="blue" variant="blue"
className={`rounded-35 ${currentQuestionIndex === questions.length - 1 ? 'd-none' : ''}`} className={`rounded-35 ${
currentQuestionIndex === questions.length - 1 ? 'd-none' : ''
}`}
onClick={nextQuestion} onClick={nextQuestion}
disabled={currentQuestionIndex === questions.length - 1} disabled={currentQuestionIndex === questions.length - 1}
> >
Next Questions <i className="bi bi-arrow-right"></i> Next Questions <i className="bi bi-arrow-right"></i>
</Button> </Button>
</div> </div>
<div className='p-3 border rounded-3'> <div className="p-3 border rounded-3">{renderQuestion()}</div>
{renderQuestion()}
</div>
</div> </div>
</Col> </Col>
</Row> </Row>
</Container> </Container>
); )
}; }
export default Review;
export default Review