mirror of
https://github.com/UniversityOfHelsinkiCS/ohtuilmo.git
synced 2026-10-12 19:03:41 +03:00
Merge pull request #253 from Ohtuilmo/feature/separate-inputs-for-each-student-in-peer-review
Separate inputs for each team member in peer review
This commit is contained in:
@@ -4,7 +4,9 @@ const { checkLogin, checkAdmin } = require('../middleware')
|
||||
|
||||
const handleDatabaseError = (res, error) => {
|
||||
console.log(error)
|
||||
res.status(500).json({ error: 'Something is wrong... try reloading the page' })
|
||||
res
|
||||
.status(500)
|
||||
.json({ error: 'Something is wrong... try reloading the page' })
|
||||
}
|
||||
|
||||
const isNil = (value) => value === undefined || value === null
|
||||
@@ -31,6 +33,11 @@ const validateAnswerSheet = (peerReview) => {
|
||||
if (error) {
|
||||
return error
|
||||
}
|
||||
} else if (question.type === 'peerReview') {
|
||||
error = validatePeerReviewAnswer(question)
|
||||
if (error) {
|
||||
return error
|
||||
}
|
||||
} else if (question.type === 'radio') {
|
||||
error = validateRadioAnswer(question)
|
||||
if (error) {
|
||||
@@ -48,6 +55,23 @@ const validateAnswerSheet = (peerReview) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const validatePeerReviewAnswer = (question) => {
|
||||
const peerAnswers = Object.values(question.peers)
|
||||
for (let answer of peerAnswers) {
|
||||
if (answer.length === 0) {
|
||||
return 'You must answer all questions'
|
||||
}
|
||||
if (answer.length < 7) {
|
||||
return 'Text answers must be over 7 characters long.'
|
||||
}
|
||||
if (answer.length > 2000) {
|
||||
return 'Text answer must be less than 2000 characters.'
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const validateTextAnswer = (question) => {
|
||||
if (question.answer.length === 0) {
|
||||
return 'You must answer all questions'
|
||||
@@ -126,7 +150,9 @@ const createTestData = async (req, res) => {
|
||||
let returnLog = []
|
||||
for (let peerReview in peerReviews) {
|
||||
try {
|
||||
const sentAnswerSheet = await db.PeerReview.create(peerReviews[peerReview])
|
||||
const sentAnswerSheet = await db.PeerReview.create(
|
||||
peerReviews[peerReview]
|
||||
)
|
||||
returnLog = returnLog.concat(sentAnswerSheet)
|
||||
} catch (err) {
|
||||
return handleDatabaseError(res, err)
|
||||
@@ -138,14 +164,14 @@ const createTestData = async (req, res) => {
|
||||
peerReviewRouter.get('/', checkLogin, async (req, res) => {
|
||||
try {
|
||||
const registrationManagement = await db.RegistrationManagement.findOne({
|
||||
order: [['createdAt', 'DESC']]
|
||||
order: [['createdAt', 'DESC']],
|
||||
})
|
||||
|
||||
const entries = await db.PeerReview.findAll({
|
||||
where: {
|
||||
user_id: req.user.id,
|
||||
configuration_id: registrationManagement.peer_review_conf
|
||||
}
|
||||
configuration_id: registrationManagement.peer_review_conf,
|
||||
},
|
||||
})
|
||||
|
||||
return res.status(200).json(entries)
|
||||
@@ -157,7 +183,7 @@ peerReviewRouter.get('/', checkLogin, async (req, res) => {
|
||||
peerReviewRouter.get('/all', checkAdmin, async (req, res) => {
|
||||
try {
|
||||
const reviews = await db.PeerReview.findAll({
|
||||
include: ['user']
|
||||
include: ['user'],
|
||||
})
|
||||
return res.status(200).json(reviews)
|
||||
} catch (error) {
|
||||
@@ -167,9 +193,11 @@ peerReviewRouter.get('/all', checkAdmin, async (req, res) => {
|
||||
|
||||
peerReviewRouter.get('/forInstructor', checkLogin, async (req, res) => {
|
||||
try {
|
||||
const query = req.user.admin ? {} : {
|
||||
where: { instructorId: req.user.id }
|
||||
}
|
||||
const query = req.user.admin
|
||||
? {}
|
||||
: {
|
||||
where: { instructorId: req.user.id },
|
||||
}
|
||||
const instructedGroups = await db.Group.findAll(query)
|
||||
|
||||
if (instructedGroups.length === 0) {
|
||||
@@ -187,21 +215,21 @@ peerReviewRouter.get('/forInstructor', checkLogin, async (req, res) => {
|
||||
as: 'students',
|
||||
model: db.User,
|
||||
attributes: ['student_number', 'first_names', 'last_name'],
|
||||
through: { attributes: [] }
|
||||
through: { attributes: [] },
|
||||
},
|
||||
{
|
||||
as: 'configuration',
|
||||
model: db.Configuration
|
||||
model: db.Configuration,
|
||||
},
|
||||
{
|
||||
as: 'instructor',
|
||||
model: db.User
|
||||
}
|
||||
]
|
||||
model: db.User,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const allAnswers = await db.PeerReview.findAll({
|
||||
include: ['user']
|
||||
include: ['user'],
|
||||
})
|
||||
|
||||
const filterAndFormatAnswers = (answers, group, round) => {
|
||||
@@ -218,9 +246,9 @@ peerReviewRouter.get('/forInstructor', checkLogin, async (req, res) => {
|
||||
return {
|
||||
student: {
|
||||
first_names: answer.user.first_names,
|
||||
last_name: answer.user.last_name
|
||||
last_name: answer.user.last_name,
|
||||
},
|
||||
answer_sheet: answer.answer_sheet
|
||||
answer_sheet: answer.answer_sheet,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -241,10 +269,10 @@ peerReviewRouter.get('/forInstructor', checkLogin, async (req, res) => {
|
||||
configurationName: group.configuration.name,
|
||||
instructorName: group.instructor
|
||||
? group.instructor.first_names + ' ' + group.instructor.last_name
|
||||
: ''
|
||||
: '',
|
||||
},
|
||||
round1Answers,
|
||||
round2Answers
|
||||
round2Answers,
|
||||
})
|
||||
|
||||
return list
|
||||
|
||||
@@ -60,6 +60,15 @@ const initTests = () => {
|
||||
},
|
||||
questionHeader: 'Miten perustelet tämän arvosanan?',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'peerReview',
|
||||
peers: {
|
||||
'Donald John Trump': 'Lorem ipsum dolor sit amet',
|
||||
'Timo *Teppo Tellervo Testaaja': '...consectetur adipiscing elit. Fusce at.',
|
||||
},
|
||||
questionHeader: 'Arviot erikseen ryhmän jäsenistä',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -95,6 +104,15 @@ const initTests = () => {
|
||||
},
|
||||
questionHeader: 'Miten perustelet tämän arvosanan?',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'peerReview',
|
||||
peers: {
|
||||
'Donald John Trump': 'Lorem ipsum dolor sit amet',
|
||||
'Timo *Teppo Tellervo Testaaja': '...consectetur adipiscing elit. Fusce at.',
|
||||
},
|
||||
questionHeader: 'Arviot erikseen ryhmän jäsenistä',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -135,6 +153,15 @@ const initTests = () => {
|
||||
},
|
||||
questionHeader: 'Miten perustelet tämän arvosanan?',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'peerReview',
|
||||
peers: {
|
||||
'Donald John Trump': 'Lorem ipsum dolor sit amet',
|
||||
'Timo *Teppo Tellervo Testaaja': '...consectetur adipiscing elit. Fusce at.',
|
||||
},
|
||||
questionHeader: 'Arviot erikseen ryhmän jäsenistä',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -170,6 +197,15 @@ const initTests = () => {
|
||||
},
|
||||
questionHeader: 'Miten perustelet tämän arvosanan?',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'peerReview',
|
||||
peers: {
|
||||
'Donald John Trump': 'Lorem ipsum dolor sit amet',
|
||||
'Timo *Teppo Tellervo Testaaja': '...consectetur adipiscing elit. Fusce at.',
|
||||
},
|
||||
questionHeader: 'Arviot erikseen ryhmän jäsenistä',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -210,6 +246,15 @@ const initTests = () => {
|
||||
},
|
||||
questionHeader: 'Miten perustelet tämän arvosanan?',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'peerReview',
|
||||
peers: {
|
||||
'Donald John Trump': 'Lorem ipsum dolor sit amet',
|
||||
'Timo *Teppo Tellervo Testaaja': '...consectetur adipiscing elit. Fusce at.',
|
||||
},
|
||||
questionHeader: 'Arviot erikseen ryhmän jäsenistä',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -245,6 +290,15 @@ const initTests = () => {
|
||||
},
|
||||
questionHeader: 'Miten perustelet tämän arvosanan?',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'peerReview',
|
||||
peers: {
|
||||
'Donald John Trump': 'Lorem ipsum dolor sit amet',
|
||||
'Timo *Teppo Tellervo Testaaja': '...consectetur adipiscing elit. Fusce at.',
|
||||
},
|
||||
questionHeader: 'Arviot erikseen ryhmän jäsenistä',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -282,7 +336,7 @@ describe('Instructor page', () => {
|
||||
cy.get('.configuration-menu-item').contains('Konfiguraatio 2').click()
|
||||
cy.contains('Kämmäilijät')
|
||||
cy.contains(
|
||||
"This group hasn't answered to the second peer review round yet."
|
||||
"This group has not answered to the second peer review round yet."
|
||||
)
|
||||
cy.get('[data-cy=configuration-selector]').click()
|
||||
cy.get('.configuration-menu-item').contains('Konfiguraatio 1').click()
|
||||
|
||||
@@ -20,6 +20,11 @@ describe('Review-questions page', () => {
|
||||
description: '',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
header: 'Peer review for group members',
|
||||
description: 'Separate review for each member of the group',
|
||||
type: 'peerReview',
|
||||
},
|
||||
{
|
||||
header: 'Ok and with option',
|
||||
description: 'Choose a radio button you want',
|
||||
|
||||
@@ -68,7 +68,7 @@ const Answers = ({ answers, currentConfiguration, currentGroupID, viewMode }) =>
|
||||
</div>
|
||||
) : (
|
||||
<h2>
|
||||
This group hasn't answered to the first peer review round yet.
|
||||
This group has not answered to the first peer review round yet.
|
||||
</h2>
|
||||
)}
|
||||
|
||||
@@ -86,7 +86,7 @@ const Answers = ({ answers, currentConfiguration, currentGroupID, viewMode }) =>
|
||||
</div>
|
||||
) : (
|
||||
<h2>
|
||||
This group hasn't answered to the second peer review round yet.
|
||||
This group has not answered to the second peer review round yet.
|
||||
</h2>
|
||||
)}
|
||||
|
||||
@@ -94,7 +94,7 @@ const Answers = ({ answers, currentConfiguration, currentGroupID, viewMode }) =>
|
||||
</div>
|
||||
)}
|
||||
else {
|
||||
return (<div></div>)
|
||||
return (<div key='empty-div'></div>)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
@@ -129,6 +129,12 @@ const GroupAnswers = ({ answers, students }) => {
|
||||
<TextNumberAnswer answers={answers} questionNumber={index} />
|
||||
</Question>
|
||||
)
|
||||
} else if (question.type === 'peerReview') {
|
||||
return (
|
||||
<Question key={index} title={question.questionHeader}>
|
||||
<PeerReviewAnswer answers={answers} questionNumber={index} />
|
||||
</Question>
|
||||
)
|
||||
} else if (question.type === 'radio') {
|
||||
return (
|
||||
<Question key={index} title={question.questionHeader}>
|
||||
@@ -147,6 +153,28 @@ const GroupAnswers = ({ answers, students }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const PeerReviewAnswer = ({ answers, questionNumber }) => {
|
||||
return (
|
||||
<div>
|
||||
{answers.map((member, index) => {
|
||||
const peers = member && member.answer_sheet && member.answer_sheet[questionNumber] ? member.answer_sheet[questionNumber].peers : null
|
||||
return (
|
||||
<div key={`${member}-${index}`}>
|
||||
<p>{member.student.last_name}</p>
|
||||
{peers && Object.entries(peers).map(
|
||||
([peer, review]) => (
|
||||
<p key={peer}>
|
||||
{peer}: {review}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TextNumberAnswer = ({ answers, questionNumber }) => {
|
||||
return (
|
||||
<div>
|
||||
@@ -275,7 +303,7 @@ const DownloadButton = ({ jsonData, fileName }) => {
|
||||
|
||||
const SelectViewButton = ({ viewMode, setViewMode }) => {
|
||||
const toggleView = () => {
|
||||
setViewMode(viewMode === 'questions' ? 'students' : 'questions');
|
||||
setViewMode(viewMode === 'questions' ? 'students' : 'questions')
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -66,10 +66,22 @@ const StudentViewGroupAnswers = ({ answers }) => {
|
||||
student={studentAnswer.student}
|
||||
/>
|
||||
)
|
||||
} else if (question.type === 'peerReview') {
|
||||
return (
|
||||
<div key={questionIndex}>
|
||||
<h3>{question.questionHeader}</h3>
|
||||
{question &&
|
||||
Object.entries(question.peers).map(([peer, review]) => (
|
||||
<p key={peer}>
|
||||
{peer}: {review}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div key={questionIndex}>
|
||||
<h4>{question.questionHeader}</h4>
|
||||
<h3>{question.questionHeader}</h3>
|
||||
<p>{question.answer}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -46,3 +46,36 @@
|
||||
padding: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.peer-review-box__name-text {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
grid-row: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.peer-review-box__text-field-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.peer-review-box__text-question-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.peer-review-box__text-field-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.peer-review-box__text-field {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.peer-review__description {
|
||||
}
|
||||
|
||||
@@ -80,10 +80,29 @@ class PeerReview extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const initializePeerReview = (question, questionId) => {
|
||||
let peerTextAnswers = {
|
||||
type: 'peerReview',
|
||||
questionHeader: question.header,
|
||||
id: questionId,
|
||||
peers: {},
|
||||
}
|
||||
|
||||
peers.forEach(
|
||||
(peer) =>
|
||||
(peerTextAnswers.peers[peer.first_names + ' ' + peer.last_name] =
|
||||
null)
|
||||
)
|
||||
|
||||
return peerTextAnswers
|
||||
}
|
||||
|
||||
const tempAnswerSheet = questionObject.questions.map(
|
||||
(question, questionID) => {
|
||||
if (question.type === 'radio') {
|
||||
return initializeRadioAnswer(question, questionID)
|
||||
} else if (question.type === 'peerReview') {
|
||||
return initializePeerReview(question, questionID)
|
||||
} else if (question.type === 'text') {
|
||||
return initializeTextAnswer(question, questionID)
|
||||
} else if (question.type === 'number') {
|
||||
@@ -128,6 +147,7 @@ class PeerReview extends React.Component {
|
||||
const {
|
||||
answerSheet,
|
||||
updateAnswer,
|
||||
updatePeerReview,
|
||||
peers,
|
||||
groupsLoading,
|
||||
submittedReviews,
|
||||
@@ -167,6 +187,7 @@ class PeerReview extends React.Component {
|
||||
questions={questionObject.questions}
|
||||
answerSheet={answerSheet}
|
||||
updateAnswer={updateAnswer}
|
||||
updatePeerReview={updatePeerReview}
|
||||
/>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Button
|
||||
@@ -185,7 +206,13 @@ class PeerReview extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const Questions = ({ questions, peers, answerSheet, updateAnswer }) => {
|
||||
const Questions = ({
|
||||
questions,
|
||||
peers,
|
||||
answerSheet,
|
||||
updateAnswer,
|
||||
updatePeerReview,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
{questions.map((question, questionId) => {
|
||||
@@ -197,6 +224,7 @@ const Questions = ({ questions, peers, answerSheet, updateAnswer }) => {
|
||||
questionId={questionId}
|
||||
answerSheet={answerSheet}
|
||||
updateAnswer={updateAnswer}
|
||||
updatePeerReview={updatePeerReview}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@@ -210,6 +238,7 @@ const Question = ({
|
||||
questionId,
|
||||
answerSheet,
|
||||
updateAnswer,
|
||||
updatePeerReview,
|
||||
}) => {
|
||||
if (question.type === 'radio') {
|
||||
let temp = question.options
|
||||
@@ -238,24 +267,42 @@ const Question = ({
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
} else if (question.type === 'text') {
|
||||
} else if (question.type === 'peerReview') {
|
||||
return (
|
||||
<div className="peer-review-box">
|
||||
<h3 className="peer-review-box__h3">{question.header}</h3>
|
||||
<p>{question.description}</p>
|
||||
|
||||
<TextField
|
||||
value={answerSheet[questionId].answer}
|
||||
rows="4"
|
||||
style={{ width: 400 }}
|
||||
multiline
|
||||
variant="outlined"
|
||||
onChange={(e) =>
|
||||
textFieldHandler(e.target.value, questionId, updateAnswer)
|
||||
}
|
||||
<PeerReviewTextFields
|
||||
peers={peers}
|
||||
questionId={questionId}
|
||||
answerSheet={answerSheet}
|
||||
updatePeerReview={updatePeerReview}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else if (question.type === 'text') {
|
||||
return (
|
||||
<div className="peer-review-box">
|
||||
<div className="peer-review-box__text-question-container">
|
||||
<div>
|
||||
<h3 className="peer-review-box__h3">{question.header}</h3>
|
||||
<p>{question.description}</p>
|
||||
|
||||
<TextField
|
||||
value={answerSheet[questionId].answer}
|
||||
rows="4"
|
||||
style={{ width: 400 }}
|
||||
multiline
|
||||
variant="outlined"
|
||||
onChange={(e) =>
|
||||
textFieldHandler(e.target.value, questionId, updateAnswer)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else if (question.type === 'number') {
|
||||
return (
|
||||
<div className="peer-review-box">
|
||||
@@ -276,7 +323,7 @@ const Question = ({
|
||||
return (
|
||||
<div className="peer-review-box">
|
||||
<h3>{question.header}</h3>
|
||||
<p>{question.description}</p>
|
||||
<p className="peer-review__description">{question.description}</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
@@ -288,10 +335,6 @@ const Question = ({
|
||||
}
|
||||
}
|
||||
|
||||
const textFieldHandler = (value, questionId, updateAnswer) => {
|
||||
updateAnswer(value, questionId)
|
||||
}
|
||||
|
||||
const OptionHeaders = ({ options }) => {
|
||||
return options.map((option, optionId) => {
|
||||
return (
|
||||
@@ -324,6 +367,40 @@ const QuestionRows = ({ peers, options, questionId, answerSheet }) => (
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
const PeerReviewTextFields = ({
|
||||
peers,
|
||||
questionId,
|
||||
answerSheet,
|
||||
updatePeerReview,
|
||||
}) => {
|
||||
return (
|
||||
<div className="peer-review-box__text-field-rows">
|
||||
{peers.map((peer, key) => (
|
||||
<div key={key} className="peer-review-box__text-field-container">
|
||||
<p className="peer-review-box__name-text">
|
||||
{extractCallingName(peer.first_names) + ' ' + peer.last_name}:{' '}
|
||||
</p>
|
||||
<TextField
|
||||
className="peer-review-box__text-field"
|
||||
value={answerSheet[questionId].answer}
|
||||
rows="2"
|
||||
multiline
|
||||
variant="outlined"
|
||||
onChange={(e) =>
|
||||
peerReviewHandler(
|
||||
`${peer.first_names} ${peer.last_name}`,
|
||||
e.target.value,
|
||||
questionId,
|
||||
updatePeerReview
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const QuestionRow = ({
|
||||
peerName,
|
||||
options,
|
||||
@@ -351,6 +428,14 @@ const QuestionRow = ({
|
||||
)
|
||||
}
|
||||
|
||||
const peerReviewHandler = (peerId, value, questionId, updatePeerReview) => {
|
||||
updatePeerReview(peerId, value, questionId)
|
||||
}
|
||||
|
||||
const textFieldHandler = (value, questionId, updateAnswer) => {
|
||||
updateAnswer(value, questionId)
|
||||
}
|
||||
|
||||
const radioSelectHandler = (peerId, buttonId, answerSheet) => {
|
||||
updateAnswers(peerId, buttonId, answerSheet)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const QuestionSetForm = ({
|
||||
initialName,
|
||||
initialQuestionsJson,
|
||||
onSubmit,
|
||||
controls
|
||||
controls,
|
||||
}) => {
|
||||
const [name, setName] = useState(initialName || '')
|
||||
const [questionsJson, setQuestionsJson] = useState(initialQuestionsJson || '')
|
||||
@@ -55,7 +55,7 @@ const QuestionSetForm = ({
|
||||
<form className="question-set-form" onSubmit={handleFormSubmit}>
|
||||
<TextField
|
||||
inputProps={{
|
||||
className: 'question-set-form__name'
|
||||
className: 'question-set-form__name',
|
||||
}}
|
||||
fullWidth
|
||||
required
|
||||
@@ -70,9 +70,9 @@ const QuestionSetForm = ({
|
||||
style: {
|
||||
fontFamily:
|
||||
'source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace',
|
||||
fontSize: '0.9em'
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
className: 'question-set-form__questions'
|
||||
className: 'question-set-form__questions',
|
||||
}}
|
||||
fullWidth
|
||||
required
|
||||
@@ -80,9 +80,40 @@ const QuestionSetForm = ({
|
||||
helperText={questionsError || ''}
|
||||
margin="normal"
|
||||
multiline
|
||||
rows={10}
|
||||
rows={28}
|
||||
value={questionsJson}
|
||||
onChange={handleQuestionsChange}
|
||||
placeholder={`
|
||||
Available question types:
|
||||
|
||||
- number
|
||||
- radio
|
||||
- peerReview (individual text fields for each team member)
|
||||
- text (single text field)
|
||||
|
||||
For example:
|
||||
|
||||
[
|
||||
{
|
||||
"type": "number",
|
||||
"header": "Kuinka monta tuntia käytit projektin parissa?",
|
||||
"description": "Arvioi käyttämäsi aika tunteina."
|
||||
},
|
||||
{
|
||||
"type": "radio",
|
||||
"header": "Tiimin jäsenten tekninen kontribuutio",
|
||||
"options": [1, 2, 3, 4, 5]
|
||||
},
|
||||
{
|
||||
"type": "peerReview",
|
||||
"header": "Teknisen kontribuution arviointi",
|
||||
"description": "Arvioi sanallisesti tiimin jäsenten teknistä kontribuutiota."
|
||||
}
|
||||
]
|
||||
`}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
{controls}
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const initializeAnswerSheet = (answerSheet) => {
|
||||
return {
|
||||
type: 'INITIALIZE_ANSWER_SHEET',
|
||||
payload: answerSheet
|
||||
payload: answerSheet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,48 +9,57 @@ const updateAnswer = (answer, questionId) => {
|
||||
return {
|
||||
type: 'UPDATE_ANSWER',
|
||||
answer: answer,
|
||||
questionId: questionId
|
||||
questionId: questionId,
|
||||
}
|
||||
}
|
||||
const updatePeerReview = (peerId, answer, questionId) => {
|
||||
return {
|
||||
type: 'UPDATE_PEER_REVIEW',
|
||||
answer: answer,
|
||||
peerId: peerId,
|
||||
questionId: questionId,
|
||||
}
|
||||
}
|
||||
const createPeers = (peers) => {
|
||||
return {
|
||||
type: 'CREATE_PEERS',
|
||||
payload: peers
|
||||
payload: peers,
|
||||
}
|
||||
}
|
||||
const setSubmittedReviews = (submittedReviews) => {
|
||||
return {
|
||||
type: 'SET_SUBMITTED_REVIEWS',
|
||||
payload: submittedReviews
|
||||
payload: submittedReviews,
|
||||
}
|
||||
}
|
||||
const setLoading = (loading) => {
|
||||
return {
|
||||
type: 'LOADING',
|
||||
payload: loading
|
||||
payload: loading,
|
||||
}
|
||||
}
|
||||
|
||||
const setQuestions = (questions) => {
|
||||
return {
|
||||
type: 'SET_QUESTIONS',
|
||||
payload: questions
|
||||
payload: questions,
|
||||
}
|
||||
}
|
||||
|
||||
const setConfiguration = (configurationId) => {
|
||||
return {
|
||||
type: 'SET_CONFIGURATION',
|
||||
payload: configurationId
|
||||
payload: configurationId,
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
updateAnswer,
|
||||
updatePeerReview,
|
||||
initializeAnswerSheet,
|
||||
createPeers,
|
||||
setSubmittedReviews,
|
||||
setLoading,
|
||||
setQuestions,
|
||||
setConfiguration
|
||||
setConfiguration,
|
||||
}
|
||||
|
||||
@@ -28,6 +28,21 @@ const peerReviewReducer = (state = initialState, action) => {
|
||||
...state.answerSheet.slice(action.questionId + 1)
|
||||
]
|
||||
}
|
||||
case 'UPDATE_PEER_REVIEW':
|
||||
return {
|
||||
...state,
|
||||
answerSheet: [
|
||||
...state.answerSheet.slice(0, action.questionId),
|
||||
{
|
||||
...state.answerSheet[action.questionId],
|
||||
peers: {
|
||||
...state.answerSheet[action.questionId].peers,
|
||||
[action.peerId]: action.answer
|
||||
},
|
||||
},
|
||||
...state.answerSheet.slice(action.questionId + 1),
|
||||
],
|
||||
}
|
||||
case 'CREATE_PEERS':
|
||||
return {
|
||||
...state,
|
||||
|
||||
Reference in New Issue
Block a user