mirror of
https://gitlab.com/MrFry/qmining-data-editor
synced 2025-04-01 20:24:01 +02:00
Complete project redo, got carried away, and forgot to commit during rewriting
This commit is contained in:
parent
274cee57b9
commit
2ae8d7ffb2
29 changed files with 2007 additions and 875 deletions
52
src/commonStyles.module.css
Normal file
52
src/commonStyles.module.css
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 20px;
|
||||||
|
|
||||||
|
color: #eee;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div:hover {
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContainer > div {
|
||||||
|
margin: 2px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContainer li {
|
||||||
|
margin: 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoHeader {
|
||||||
|
color: white;
|
||||||
|
font-size: 38px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoReadButton {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px 30px;
|
||||||
|
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoReadButton:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
|
@ -1,13 +1,11 @@
|
||||||
import React, { PureComponent } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
class LoadingIndicator extends PureComponent {
|
import styles from './LoadingIndicator.module.css'
|
||||||
render () {
|
|
||||||
return (
|
export default function LoadingIndicator() {
|
||||||
<div className='loadingindicator'>
|
return (
|
||||||
Loading...
|
<div className={styles.loadContainer}>
|
||||||
</div>
|
<div className={styles.load} />
|
||||||
)
|
</div>
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoadingIndicator
|
|
||||||
|
|
24
src/components/LoadingIndicator.module.css
Normal file
24
src/components/LoadingIndicator.module.css
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
.load {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #99f;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
|
@ -1,78 +1,121 @@
|
||||||
import React, { PureComponent } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import styles from './question.module.css'
|
import styles from './question.module.css'
|
||||||
|
|
||||||
class Question extends PureComponent {
|
const overflowLength = 140
|
||||||
render () {
|
|
||||||
const { subjInd, question, onChange, deleteQuestion } = this.props
|
|
||||||
|
|
||||||
let qdata = question.data
|
export default function Question({ question, onChange, index }) {
|
||||||
if (typeof qdata === 'object' && qdata.type === 'simple') {
|
// FIXME: focus change when input changes to textarea
|
||||||
qdata = ''
|
const possibleAnswers =
|
||||||
}
|
question.possibleAnswers || question.data.possibleAnswers
|
||||||
if (qdata) {
|
|
||||||
try {
|
|
||||||
qdata = JSON.stringify(qdata)
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const qChange = (e) => {
|
return (
|
||||||
onChange(subjInd, question.ind, {
|
<div className={styles.questionContainer}>
|
||||||
...question,
|
{question.Q && question.Q.length > overflowLength ? (
|
||||||
[e.target.name]: e.target.value
|
<textarea
|
||||||
})
|
placeholder="Kérdés..."
|
||||||
}
|
value={question.Q}
|
||||||
|
onChange={(e) => {
|
||||||
const qDataChange = (e) => {
|
onChange({
|
||||||
try {
|
...question,
|
||||||
let newData = JSON.parse(e.target.value)
|
Q: e.target.value,
|
||||||
onChange(subjInd, question.ind, {
|
})
|
||||||
...question,
|
}}
|
||||||
data: newData
|
/>
|
||||||
})
|
) : (
|
||||||
} catch (e) {
|
<input
|
||||||
console.log('invalid JSON')
|
placeholder="Kérdés..."
|
||||||
}
|
type="text"
|
||||||
}
|
value={question.Q}
|
||||||
|
onChange={(e) => {
|
||||||
return (
|
onChange({
|
||||||
<div className={styles.questionContainer}>
|
...question,
|
||||||
<div className={styles.inputContainer}>
|
Q: e.target.value,
|
||||||
<input
|
})
|
||||||
className={styles.questionInput}
|
}}
|
||||||
type='text'
|
/>
|
||||||
value={question.Q}
|
)}
|
||||||
onChange={qChange}
|
{question.A && question.A.length > overflowLength ? (
|
||||||
name='Q'
|
<textarea
|
||||||
/>
|
placeholder="Helyes válasz..."
|
||||||
</div>
|
value={question.A}
|
||||||
<div className={styles.inputContainer}>
|
onChange={(e) => {
|
||||||
<input
|
onChange({
|
||||||
className={styles.questionInput}
|
...question,
|
||||||
type='text'
|
A: e.target.value,
|
||||||
value={question.A}
|
})
|
||||||
onChange={qChange}
|
}}
|
||||||
name='A'
|
/>
|
||||||
/>
|
) : (
|
||||||
</div>
|
<input
|
||||||
<div className={styles.inputContainer}>
|
placeholder="Helyes válasz..."
|
||||||
<input
|
type="text"
|
||||||
className={styles.questionInput}
|
value={question.A}
|
||||||
type='text'
|
onChange={(e) => {
|
||||||
value={JSON.stringify(question.data)}
|
onChange({
|
||||||
onChange={qDataChange}
|
...question,
|
||||||
name='data'
|
A: e.target.value,
|
||||||
/>
|
})
|
||||||
<span
|
}}
|
||||||
className={styles.deleteButton}
|
/>
|
||||||
onClick={() => { deleteQuestion(subjInd, question.ind) }}
|
)}
|
||||||
>
|
{possibleAnswers && possibleAnswers.length > 0 ? (
|
||||||
Delete question
|
<>
|
||||||
</span>
|
<div className={styles.text}>Lehetséges válaszok:</div>
|
||||||
</div>
|
<div className={styles.possibleAnswers}>
|
||||||
</div>
|
{possibleAnswers.map((possibleAnswer, i) => {
|
||||||
)
|
return (
|
||||||
}
|
<div key={i}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
onChange={() => {
|
||||||
|
onChange({
|
||||||
|
...question,
|
||||||
|
A: possibleAnswer,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
checked={possibleAnswer === question.A}
|
||||||
|
value={possibleAnswer}
|
||||||
|
type="radio"
|
||||||
|
name={`possiblea${index ? index : ''}`}
|
||||||
|
/>
|
||||||
|
<span>{possibleAnswer}</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
onClick={() => {
|
||||||
|
const newPossibleAnswers = possibleAnswers.filter(
|
||||||
|
(pa, j) => {
|
||||||
|
return j !== i
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// FIXME: 2 possible answers?
|
||||||
|
onChange({
|
||||||
|
...question,
|
||||||
|
data: {
|
||||||
|
...question.data,
|
||||||
|
possibleAnswers: newPossibleAnswers,
|
||||||
|
},
|
||||||
|
possibleAnswers: newPossibleAnswers,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className={styles.delete}
|
||||||
|
>
|
||||||
|
Törlés
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={JSON.stringify(question.data)}
|
||||||
|
name="data"
|
||||||
|
onChange={(e) => {
|
||||||
|
console.log(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Question
|
|
||||||
|
|
|
@ -1,79 +1,85 @@
|
||||||
import React, { PureComponent } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import Questions from './Questions.js'
|
import Questions from './Questions.js'
|
||||||
|
|
||||||
import constants from '../constants.json'
|
import constants from '../constants.json'
|
||||||
|
|
||||||
class QuestionSearchResult extends PureComponent {
|
const countReducer = (acc, subj) => {
|
||||||
render() {
|
return acc + subj.Questions.length
|
||||||
const { data, searchTerm, onChange, deleteQuestion } = this.props
|
|
||||||
|
|
||||||
let subjs = []
|
|
||||||
let results = -1
|
|
||||||
|
|
||||||
const countReducer = (acc, subj) => {
|
|
||||||
return acc + subj.Questions.length
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchTerm) {
|
|
||||||
subjs = data.reduce((acc, subj) => {
|
|
||||||
const resultQuestions = subj.Questions.reduce((qacc, question) => {
|
|
||||||
const keys = ['Q', 'A', 'data']
|
|
||||||
keys.some((key) => {
|
|
||||||
if (typeof question[key] !== 'string') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
question[key] &&
|
|
||||||
question[key].toLowerCase().includes(searchTerm.toLowerCase())
|
|
||||||
) {
|
|
||||||
qacc.push(question)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return qacc
|
|
||||||
}, [])
|
|
||||||
if (resultQuestions.length > 0) {
|
|
||||||
acc.push({
|
|
||||||
Name: subj.Name,
|
|
||||||
Questions: resultQuestions,
|
|
||||||
ind: subj.ind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
results = subjs.reduce(countReducer, 0)
|
|
||||||
} else {
|
|
||||||
results = data.reduce(countReducer, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderCount = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{searchTerm ? '' : 'Kezdj el írni kereséshez!'} {results}{' '}
|
|
||||||
{searchTerm ? 'találat' : 'kérdés'}{' '}
|
|
||||||
{searchTerm ? subjs.length : data.length} tárgy
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results > constants.maxQuestionsToRender) {
|
|
||||||
return renderCount()
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>{renderCount()}</div>
|
|
||||||
<div>
|
|
||||||
<Questions
|
|
||||||
deleteQuestion={deleteQuestion}
|
|
||||||
subjs={subjs}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QuestionSearchResult
|
export default function QuestionSearchResult(props) {
|
||||||
|
const { data, searchTerm, edits, onChange } = props
|
||||||
|
|
||||||
|
let subjs = []
|
||||||
|
let results = -1
|
||||||
|
|
||||||
|
if (searchTerm || edits.length > 0) {
|
||||||
|
subjs = data.reduce((acc, subj) => {
|
||||||
|
const resultQuestions = subj.Questions.reduce((qacc, question, i) => {
|
||||||
|
const unsaved = edits.some((e) => {
|
||||||
|
if (e.subjName === subj.Name && e.index === i && e.type === 'edit') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (unsaved) {
|
||||||
|
qacc.push({
|
||||||
|
q: question,
|
||||||
|
unsaved: unsaved,
|
||||||
|
})
|
||||||
|
return qacc
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = ['Q', 'A', 'data']
|
||||||
|
keys.some((key) => {
|
||||||
|
if (typeof question[key] !== 'string') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
searchTerm &&
|
||||||
|
question[key] &&
|
||||||
|
question[key].toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
) {
|
||||||
|
qacc.push({ q: question })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return qacc
|
||||||
|
}, [])
|
||||||
|
if (resultQuestions.length > 0) {
|
||||||
|
acc.push({
|
||||||
|
Name: subj.Name,
|
||||||
|
Questions: resultQuestions,
|
||||||
|
ind: subj.ind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
results = subjs.reduce(countReducer, 0)
|
||||||
|
} else {
|
||||||
|
results = data.reduce(countReducer, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderCount = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{searchTerm ? '' : 'Kezdj el írni kereséshez!'} {results}{' '}
|
||||||
|
{searchTerm ? 'találat' : 'kérdés'}{' '}
|
||||||
|
{searchTerm ? subjs.length : data.length} tárgy
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results > constants.maxQuestionsToRender) {
|
||||||
|
return renderCount()
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{renderCount()}</div>
|
||||||
|
<div>
|
||||||
|
<Questions subjs={subjs} onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,28 +3,75 @@ import React, { PureComponent } from 'react'
|
||||||
import Question from './Question.js'
|
import Question from './Question.js'
|
||||||
|
|
||||||
import styles from './Questions.module.css'
|
import styles from './Questions.module.css'
|
||||||
|
import commonStyles from '../commonStyles.module.css'
|
||||||
|
|
||||||
class Questions extends PureComponent {
|
class Questions extends PureComponent {
|
||||||
render () {
|
render() {
|
||||||
const { subjs, onChange, deleteQuestion } = this.props
|
const { subjs, onChange } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{subjs.map((subj, i) => {
|
{subjs.map((subj) => {
|
||||||
return (
|
return (
|
||||||
<div key={i}>
|
<div key={subj.Name}>
|
||||||
<div className={styles.subjName}>
|
<div className={styles.subjName}>{subj.Name}</div>
|
||||||
{subj.Name}
|
{subj.Questions.map((qo, i) => {
|
||||||
</div>
|
const question = qo.q
|
||||||
{ subj.Questions.map((question, i) => {
|
const unsaved = qo.unsaved
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Question
|
<React.Fragment key={i}>
|
||||||
key={i}
|
<hr />
|
||||||
question={question}
|
<div className={`${unsaved ? styles.unsaved : ''}`}>
|
||||||
subjInd={subj.ind}
|
<Question
|
||||||
onChange={onChange}
|
index={`${subj.Name}_${i}`}
|
||||||
deleteQuestion={deleteQuestion}
|
question={question}
|
||||||
/>
|
onChange={(newVal) => {
|
||||||
|
onChange({
|
||||||
|
index: i,
|
||||||
|
subjName: subj.Name,
|
||||||
|
type: 'edit',
|
||||||
|
newVal: newVal,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={commonStyles.actions}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
onChange({
|
||||||
|
index: i,
|
||||||
|
subjName: subj.Name,
|
||||||
|
type: 'reset',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Visszaállítás
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
onChange({
|
||||||
|
index: i,
|
||||||
|
subjName: subj.Name,
|
||||||
|
type: 'save',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Mentés
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
onChange({
|
||||||
|
index: i,
|
||||||
|
subjName: subj.Name,
|
||||||
|
type: 'delete',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Törlés
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,5 +3,30 @@
|
||||||
background-color: #9999ff;
|
background-color: #9999ff;
|
||||||
color: black;
|
color: black;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
margin: 10px 0px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 20px;
|
||||||
|
|
||||||
|
color: #eee;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div:hover {
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsaved {
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +1,101 @@
|
||||||
import React, { PureComponent } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import Question from './Question.js'
|
import Question from './Question.js'
|
||||||
|
|
||||||
class Subject extends PureComponent {
|
import styles from './subject.module.css'
|
||||||
render () {
|
import commonStyles from '../commonStyles.module.css'
|
||||||
const { subj, onChange, deleteQuestion } = this.props
|
|
||||||
|
|
||||||
if (subj) {
|
function DeletedQuestion({ reset }) {
|
||||||
return (
|
return (
|
||||||
<div >
|
<div>
|
||||||
{subj.Questions.map((question, i) => {
|
<div className={styles.deletedQuestion}>Törölt kérdés</div>
|
||||||
return (
|
<div className={commonStyles.actions}>
|
||||||
<Question
|
<div
|
||||||
deleteQuestion={deleteQuestion}
|
onClick={() => {
|
||||||
onChange={onChange}
|
reset()
|
||||||
key={i}
|
}}
|
||||||
subjInd={subj.ind}
|
>
|
||||||
question={question}
|
Visszaállítás
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
} else {
|
</div>
|
||||||
return (
|
)
|
||||||
<div />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Subject
|
export default function Subject(props) {
|
||||||
|
const {
|
||||||
|
subj,
|
||||||
|
unsavedIndexes,
|
||||||
|
deletedIndexes,
|
||||||
|
editedIndexes,
|
||||||
|
resetQuestion,
|
||||||
|
handleQuestionChange,
|
||||||
|
saveQuestion,
|
||||||
|
deleteQuestion,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
if (subj) {
|
||||||
|
return (
|
||||||
|
<div className={styles.questionContainer}>
|
||||||
|
{subj.Questions.map((question, i) => {
|
||||||
|
// FIXME: list edited questions first?
|
||||||
|
const unsaved = unsavedIndexes.includes(i)
|
||||||
|
const edited = editedIndexes.includes(i)
|
||||||
|
const deleted = deletedIndexes.includes(i)
|
||||||
|
return (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
<hr />
|
||||||
|
{deleted ? (
|
||||||
|
<DeletedQuestion
|
||||||
|
reset={() => {
|
||||||
|
resetQuestion(i)
|
||||||
|
}}
|
||||||
|
index={i}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`${unsaved ? styles.unsaved : ''} ${
|
||||||
|
edited ? styles.edited : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Question
|
||||||
|
index={i}
|
||||||
|
onChange={(newq) => {
|
||||||
|
handleQuestionChange(newq, i)
|
||||||
|
}}
|
||||||
|
question={question}
|
||||||
|
/>
|
||||||
|
<div className={commonStyles.actions}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
resetQuestion(i)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Visszaállítás
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
saveQuestion(i)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{edited ? 'Kérdés mentve' : 'Kérdés mentése'}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
deleteQuestion(i)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Kérdés törlése
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <div />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
import styles from './SubjectSelector.module.css'
|
import styles from './SubjectSelector.module.css'
|
||||||
|
|
||||||
export default function SubjectSelector(props) {
|
export default function SubjectSelector(props) {
|
||||||
|
|
|
@ -13,3 +13,6 @@
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subjectSelector:nth-child(2n) {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
28
src/components/dbSelector.js
Normal file
28
src/components/dbSelector.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default function DbSelector(props) {
|
||||||
|
const { qdbs, onChange } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<select
|
||||||
|
style={{ margin: '10px 0px' }}
|
||||||
|
defaultValue={-1}
|
||||||
|
onChange={(event) => {
|
||||||
|
onChange(qdbs[event.target.value])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option disabled value={-1}>
|
||||||
|
{' -- Válassz egy kérdés adatbázist -- '}
|
||||||
|
</option>
|
||||||
|
{qdbs.map((qdb, i) => {
|
||||||
|
return (
|
||||||
|
<option value={i} key={qdb.name}>
|
||||||
|
{qdb.name}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
256
src/components/possibleAnswers.js
Normal file
256
src/components/possibleAnswers.js
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import LoadingIndicator from '../components/LoadingIndicator'
|
||||||
|
import SearchBar from '../components/searchBar'
|
||||||
|
import TestView from '../components/testView'
|
||||||
|
|
||||||
|
import constants from '../constants.json'
|
||||||
|
import styles from './possibleAnswers.module.css'
|
||||||
|
|
||||||
|
const Infos = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.infoContainer}>
|
||||||
|
<div>
|
||||||
|
Itt azok a tesztek találhatók, amiknek a kitöltése után nem volt
|
||||||
|
ellenőrző oldal. A script így nem tudja, hogy melyik a helyes megoldás.
|
||||||
|
De ha ti igen, akkor jelöljétek be / írjátok be, és mentsétek el. Így
|
||||||
|
mikor legközelebb találkoztok a kérdéssel a script tudni fogja a helyes
|
||||||
|
választ. Ezzel másoknak is nagyon sokat segítetek.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPossibleAnswers = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(`${constants.apiUrl}possibleAnswers`, {
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchSubject = (subj, savedQuestionsFileName) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(
|
||||||
|
`${constants.apiUrl}/savedQuestions/${subj}/${savedQuestionsFileName}`,
|
||||||
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
resolve(resp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchTest = (subj, test) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(`${constants.apiUrl}/savedQuestions/${subj}/${test}`, {
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
resolve(resp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PossibleAnswers({ router, refetchDbs }) {
|
||||||
|
const [currSubjName, setCurrSubjName] = useState(null)
|
||||||
|
const [currTestName, setCurrTestName] = useState(null)
|
||||||
|
|
||||||
|
const [subjects, setSubjects] = useState([])
|
||||||
|
const [currSubj, setCurrSubj] = useState(null)
|
||||||
|
const [currTest, setCurrTest] = useState(null)
|
||||||
|
|
||||||
|
const [savedQuestionsFileName, setSavedQuestionsFileName] = useState(null)
|
||||||
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPossibleAnswers().then((resp) => {
|
||||||
|
setSubjects(resp.subjects)
|
||||||
|
setSavedQuestionsFileName(resp.savedQuestionsFileName)
|
||||||
|
|
||||||
|
const subj = router.query.subj
|
||||||
|
? decodeURIComponent(router.query.subj)
|
||||||
|
: ''
|
||||||
|
const test = router.query.test
|
||||||
|
? decodeURIComponent(router.query.test)
|
||||||
|
: ''
|
||||||
|
|
||||||
|
if (subj) {
|
||||||
|
fetchSubject(subj, resp.savedQuestionsFileName).then((resp) => {
|
||||||
|
setCurrSubj(resp)
|
||||||
|
setCurrSubjName(subj)
|
||||||
|
router.push(
|
||||||
|
`${router.pathname}?v=pa&subj=${encodeURIComponent(subj)}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
if (subj && test) {
|
||||||
|
fetchTest(subj, test).then((resp) => {
|
||||||
|
setCurrTest(resp)
|
||||||
|
setCurrTestName(test)
|
||||||
|
router.push(
|
||||||
|
`${router.pathname}?v=pa&subj=${encodeURIComponent(
|
||||||
|
subj
|
||||||
|
)}&test=${encodeURIComponent(test)}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const renderStuff = () => {
|
||||||
|
if (subjects && currSubj && currTest) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={styles.backButton}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrTest(null)
|
||||||
|
router.back()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Vissza
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{currTest && (
|
||||||
|
<TestView
|
||||||
|
subjName={currSubjName}
|
||||||
|
testName={currTestName}
|
||||||
|
test={currTest}
|
||||||
|
router={router}
|
||||||
|
onDelete={() => {
|
||||||
|
refetchDbs()
|
||||||
|
setCurrTest(null)
|
||||||
|
fetchSubject(currSubjName, savedQuestionsFileName).then(
|
||||||
|
(resp) => {
|
||||||
|
setCurrSubj(resp)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} else if (subjects && currSubj) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.headerContainer}>
|
||||||
|
<div
|
||||||
|
className={styles.backButton}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrSubj(null)
|
||||||
|
router.back()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Vissza
|
||||||
|
</div>
|
||||||
|
<div className={styles.subjName}>{currSubjName}</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.tableContainer}>
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<div>Dátum</div>
|
||||||
|
<div>Felhasználó ID</div>
|
||||||
|
<div>Tárgy</div>
|
||||||
|
<div>Teszt URL</div>
|
||||||
|
</div>
|
||||||
|
{currSubj &&
|
||||||
|
currSubj.map((test, i) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.testContainer}
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrTestName(test.fname)
|
||||||
|
router.push(
|
||||||
|
`${router.pathname}?v=pa&subj=${encodeURIComponent(
|
||||||
|
currSubjName
|
||||||
|
)}&test=${encodeURIComponent(test.fname)}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
fetchTest(currSubjName, test.fname).then((resp) => {
|
||||||
|
setCurrTest(resp)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{new Date(test.date).toLocaleString()}</div>
|
||||||
|
<div>{test.userid}</div>
|
||||||
|
<div>{test.subj}</div>
|
||||||
|
<div>{test.testUrl}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} else if (subjects) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Infos />
|
||||||
|
<SearchBar value={searchTerm} onChange={(e) => setSearchTerm(e)} />
|
||||||
|
<div className={styles.tableContainer}>
|
||||||
|
<>
|
||||||
|
<div>Tárgy neve</div>
|
||||||
|
{subjects.map((subj, i) => {
|
||||||
|
if (
|
||||||
|
!subj.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrSubjName(subj.name)
|
||||||
|
router.push(
|
||||||
|
`${router.pathname}?v=pa&subj=${encodeURIComponent(
|
||||||
|
subj.name
|
||||||
|
)}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
fetchSubject(subj.name, savedQuestionsFileName).then(
|
||||||
|
(resp) => {
|
||||||
|
setCurrSubj(resp)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subj.name}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <LoadingIndicator />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderStuff()
|
||||||
|
}
|
85
src/components/possibleAnswers.module.css
Normal file
85
src/components/possibleAnswers.module.css
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
.tableContainer {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div > div:nth-child(1) {
|
||||||
|
flex: 0 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div > div:nth-child(2) {
|
||||||
|
text-align: center;
|
||||||
|
flex: 0 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div > div:nth-child(3) {
|
||||||
|
flex: 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div > div:nth-child(4) {
|
||||||
|
flex: 0 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div:first-child {
|
||||||
|
text-align: center;
|
||||||
|
padding: 13px 10px;
|
||||||
|
background-color: #444;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div:not(:first-child) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div:nth-child(2n) {
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer > div:not(:first-child):hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backButton {
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 10px 5px;
|
||||||
|
padding: 10px 40px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backButton:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subjName {
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContainer > div {
|
||||||
|
margin: 2px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -1,20 +1,21 @@
|
||||||
.questionInput {
|
.questionContainer > input,
|
||||||
flex-grow: 1;
|
.questionContainer > textarea {
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border-radius: 5px;
|
||||||
padding: 8px;
|
border: 1px solid #333;
|
||||||
|
padding: 6px;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.questionContainer > input:hover,
|
||||||
|
.questionContainer > textarea:hover {
|
||||||
|
border: 1px solid #99f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questionContainer {
|
.questionContainer {
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
margin-right: 10px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.inputContainer {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteButton {
|
.deleteButton {
|
||||||
|
@ -29,3 +30,26 @@
|
||||||
.deleteButton:hover {
|
.deleteButton:hover {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.possibleAnswers > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.possibleAnswers > div:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
padding: 0px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
padding: 1px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,103 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import Head from 'next/head'
|
|
||||||
|
|
||||||
import LoadingIndicator from '../components/LoadingIndicator'
|
import Question from '../components/Question'
|
||||||
|
|
||||||
import styles from './questionAdder.module.css'
|
import styles from './questionAdder.module.css'
|
||||||
import constants from '../constants.json'
|
import constants from '../constants.json'
|
||||||
|
import commonStyles from '../commonStyles.module.css'
|
||||||
|
|
||||||
|
const handleSubmit = async (form) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!form.subj) {
|
||||||
|
reject('nosubj')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let isValid = form.quiz.every((x) => {
|
||||||
|
return x.Q && x.A
|
||||||
|
})
|
||||||
|
if (!isValid || form.quiz.length === 0) {
|
||||||
|
reject('notvalid')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = document.getElementById('cid').value
|
||||||
|
let cid = ''
|
||||||
|
let version = ''
|
||||||
|
if (t) {
|
||||||
|
cid = t.split('|')[0]
|
||||||
|
version = t.split('|')[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(form)
|
||||||
|
// {
|
||||||
|
// "quiz": [
|
||||||
|
// {
|
||||||
|
// "Q": "aaaaaaaaaaaa",
|
||||||
|
// "A": "bbbbbbbbbbbbbb",
|
||||||
|
// "data": {
|
||||||
|
// "type": "simple"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "selectedDb": {
|
||||||
|
// "path": "questionDbs/elearning.uni-obuda.hu.json",
|
||||||
|
// "name": "elearning.uni-obuda.hu"
|
||||||
|
// },
|
||||||
|
// "subj": "Elektronika"
|
||||||
|
// }
|
||||||
|
|
||||||
|
const toSend = {
|
||||||
|
id: cid,
|
||||||
|
version: version,
|
||||||
|
location: `https://${form.selectedDb.name}`,
|
||||||
|
subj: form.subj,
|
||||||
|
quiz: form.quiz,
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(constants.apiUrl + 'isAdding', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(toSend),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
resolve(resp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderUsage = () => {
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
<li>Ezek a kérdések ellenőrizve lesznek hogy megvannak-e már</li>
|
||||||
|
<li>
|
||||||
|
{
|
||||||
|
"Ha több válasz van, akkor ', '-vel válaszd el őket ( 'válasz1, válasz2, válasz3' )"
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Kérdéseknél az utolsó sor (ahol a JSON cucc van) jelenleg nem
|
||||||
|
módosítható, csak olyan kérdéseket lehet beküldeni, amik sima
|
||||||
|
kérdés-válaszok, szóval pl nincs benne kép. Ez később bővül majd
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Ha sok új kérdést küldesz be, akkor akár több percig is eltarthat a
|
||||||
|
dolog. Akárhány kérdést be lehet egyszerre küldeni, de max 10-15 az
|
||||||
|
ajánlott
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Bármilyen szöveget beküldhettek, de ne tegyétek, más felhasználóknak és
|
||||||
|
magatoknak lesz rosz, ty!
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const getDefaultQuestion = () => {
|
const getDefaultQuestion = () => {
|
||||||
return {
|
return {
|
||||||
|
@ -14,100 +107,44 @@ const getDefaultQuestion = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function QuestionAdder() {
|
export default function QuestionAdder({ data, selectedDb, refetchDbs }) {
|
||||||
const [form, setForm] = useState({ quiz: [getDefaultQuestion()] })
|
const [form, setForm] = useState({ quiz: [getDefaultQuestion()] })
|
||||||
const [subjects, setSubjects] = useState(undefined)
|
const [subjects, setSubjects] = useState(null)
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [isNewSubj, setIsNewSubj] = useState(false)
|
const [isNewSubj, setIsNewSubj] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.info('Fetching subject names')
|
if (selectedDb) {
|
||||||
fetch(`${constants.apiUrl}dataCount?detailed=true`, {
|
setForm({
|
||||||
credentials: 'include',
|
...form,
|
||||||
})
|
selectedDb: selectedDb,
|
||||||
.then((resp) => {
|
|
||||||
return resp.json()
|
|
||||||
})
|
})
|
||||||
.then((data) => {
|
}
|
||||||
let res = data.reduce((acc, x) => {
|
}, [selectedDb])
|
||||||
return [
|
|
||||||
...acc,
|
|
||||||
...x.subjs.map((subj) => {
|
|
||||||
return subj.name
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
res = res.sort()
|
useEffect(() => {
|
||||||
setSubjects(res)
|
if (data) {
|
||||||
})
|
setSubjects(
|
||||||
}, [])
|
data.map((subj) => {
|
||||||
|
return subj.Name
|
||||||
const onChange = (newData, index) => {
|
})
|
||||||
let quiz = form.quiz
|
)
|
||||||
quiz[index] = newData
|
}
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
const handleQuestionChange = (index, newVal) => {
|
||||||
setForm({
|
setForm({
|
||||||
...form,
|
...form,
|
||||||
quiz: quiz,
|
quiz: form.quiz.map((q, i) => {
|
||||||
|
if (i !== index) {
|
||||||
|
return q
|
||||||
|
} else {
|
||||||
|
return newVal
|
||||||
|
}
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderQuestionInput = (params) => {
|
|
||||||
const { index, onChange } = params
|
|
||||||
const currData = form.quiz[index]
|
|
||||||
const { Q, A, data } = form.quiz[index] || {}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.questionContainer}>
|
|
||||||
<div className={styles.inputContainer}>
|
|
||||||
<input
|
|
||||||
placeholder="Kérdés..."
|
|
||||||
type="text"
|
|
||||||
onChange={(err) =>
|
|
||||||
onChange({ ...currData, Q: err.target.value }, index)
|
|
||||||
}
|
|
||||||
value={Q || ''}
|
|
||||||
className={styles.questionInput}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.inputContainer}>
|
|
||||||
<input
|
|
||||||
placeholder="Válasz..."
|
|
||||||
type="text"
|
|
||||||
onChange={(err) =>
|
|
||||||
onChange({ ...currData, A: err.target.value }, index)
|
|
||||||
}
|
|
||||||
value={A || ''}
|
|
||||||
className={styles.questionInput}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.inputContainer}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
onChange={() => {
|
|
||||||
// TODO: handle JSON
|
|
||||||
// try {
|
|
||||||
// let newData = JSON.parse(e.target.value)
|
|
||||||
// onChange({ ...currData, data: newData }, index)
|
|
||||||
// } catch (e) {
|
|
||||||
// }
|
|
||||||
}}
|
|
||||||
value={JSON.stringify(data) || ''}
|
|
||||||
className={styles.questionInput}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={styles.deleteButton}
|
|
||||||
onClick={() => deleteQuestion(index)}
|
|
||||||
>
|
|
||||||
Kérdés törlése
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteQuestion = (index) => {
|
const deleteQuestion = (index) => {
|
||||||
let quiz = form.quiz
|
let quiz = form.quiz
|
||||||
|
|
||||||
|
@ -119,82 +156,33 @@ export default function QuestionAdder() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!form.subj) {
|
|
||||||
alert('Nem választottál ki tantárgyat!') // eslint-disable-line
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let isValid = form.quiz.every((x) => {
|
|
||||||
return x.Q && x.A
|
|
||||||
})
|
|
||||||
if (!isValid || form.quiz.length === 0) {
|
|
||||||
alert('Kérdés kitöltése kötelező!') // eslint-disable-line
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const t = document.getElementById('cid').value
|
|
||||||
let cid = ''
|
|
||||||
let version = ''
|
|
||||||
if (t) {
|
|
||||||
cid = t.split('|')[0]
|
|
||||||
version = t.split('|')[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSubmitting(true)
|
|
||||||
|
|
||||||
const rawResponse = await fetch(constants.apiUrl + 'isAdding', {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...form,
|
|
||||||
id: cid,
|
|
||||||
version: 'WEBSITE',
|
|
||||||
scriptVersion: version,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
rawResponse
|
|
||||||
.json()
|
|
||||||
.then((resp) => {
|
|
||||||
console.log(resp)
|
|
||||||
if (resp.success) {
|
|
||||||
alert(
|
|
||||||
'Sikeres beküldés, ' +
|
|
||||||
resp.newQuestions.reduce((acc, res) => {
|
|
||||||
return acc + res.newQuestions
|
|
||||||
}, 0) +
|
|
||||||
' új kérdés'
|
|
||||||
) // eslint-disable-line
|
|
||||||
setIsSubmitting(false)
|
|
||||||
} else {
|
|
||||||
alert('Hiba beküldés közben :/') // eslint-disable-line
|
|
||||||
setIsSubmitting(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
alert('Hiba beküldés közben :/') // eslint-disable-line
|
|
||||||
console.log(err)
|
|
||||||
setIsSubmitting(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderStuff = () => {
|
const renderStuff = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{form.quiz.map((q, i) => {
|
{form.quiz.map((q, i) => {
|
||||||
return (
|
return (
|
||||||
<div key={i}>
|
<React.Fragment key={i}>
|
||||||
{renderQuestionInput({
|
<hr />
|
||||||
index: i,
|
<Question
|
||||||
onChange,
|
question={form.quiz[i] || {}}
|
||||||
})}
|
onChange={(newVal) => {
|
||||||
</div>
|
handleQuestionChange(i, newVal)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={commonStyles.actions}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
deleteQuestion(i)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Törlés
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<div className={styles.newQuestionButton}>
|
<hr />
|
||||||
|
<div className={commonStyles.actions}>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let quiz = form.quiz
|
let quiz = form.quiz
|
||||||
|
@ -207,26 +195,57 @@ export default function QuestionAdder() {
|
||||||
>
|
>
|
||||||
Új kérdés
|
Új kérdés
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${isSubmitting ? styles.issubmitting : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSubmitting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIsSubmitting(true)
|
||||||
|
handleSubmit(form)
|
||||||
|
.then((res) => {
|
||||||
|
// console.log(res)
|
||||||
|
// {
|
||||||
|
// "success": true,
|
||||||
|
// "newQuestions": [
|
||||||
|
// {
|
||||||
|
// "newQuestions": 1,
|
||||||
|
// "qdbName": "elearning.uni-obuda.hu"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "totalNewQuestions": 1
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
alert(
|
||||||
|
`Sikeres beküldés, ${res.totalNewQuestions} új kérdés`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
alert('Hiba beküldés közben :/')
|
||||||
|
}
|
||||||
|
refetchDbs()
|
||||||
|
setIsSubmitting(false)
|
||||||
|
})
|
||||||
|
.catch((res) => {
|
||||||
|
if (res === 'nosubj') {
|
||||||
|
alert('Nem választottál ki tantárgyat!') // eslint-disable-line
|
||||||
|
}
|
||||||
|
if (res === 'notvalid') {
|
||||||
|
alert('Kérdés kitöltése kötelező!') // eslint-disable-line
|
||||||
|
}
|
||||||
|
setIsSubmitting(false)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSubmitting ? 'Beküldés folyamatban ...' : 'Kérdések beküldése'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isSubmitting ? (
|
|
||||||
<div className={styles.issubmitting}>
|
|
||||||
Kérdések feldolgozása folyamatban, ha sokat küldtél, akkor több perc
|
|
||||||
is lehet
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={styles.buttonContainer}>
|
|
||||||
<button className={styles.button} onClick={handleSubmit}>
|
|
||||||
Kérdések beküldése
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<input type="text" id="cid" name="cid" hidden />
|
<input type="text" id="cid" name="cid" hidden />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderSubjSelector = () => {
|
const renderSubjSelector = () => {
|
||||||
// TODO: handle if new subject
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.subjSelectorContainer}>
|
<div className={styles.subjSelectorContainer}>
|
||||||
{isNewSubj ? (
|
{isNewSubj ? (
|
||||||
|
@ -243,7 +262,6 @@ export default function QuestionAdder() {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<select
|
||||||
className={styles.subjSelector}
|
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setForm({
|
setForm({
|
||||||
...form,
|
...form,
|
||||||
|
@ -263,61 +281,29 @@ export default function QuestionAdder() {
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
)}
|
)}
|
||||||
<span
|
<div
|
||||||
className={styles.newSubj}
|
className={commonStyles.actions}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsNewSubj(!isNewSubj)
|
setIsNewSubj(!isNewSubj)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isNewSubj ? 'Létező tárgy ...' : 'Új tárgy ...'}
|
<div>{isNewSubj ? 'Létező tárgy ...' : 'Új tárgy ...'}</div>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderUsage = () => {
|
|
||||||
return (
|
|
||||||
<ul className={styles.usage}>
|
|
||||||
<li>Ezen az oldalon kérdéseket tudsz beküldeni manuálisan.</li>
|
|
||||||
<li>Ezek a kérdések ellenőrizve lesznek hogy megvannak-e már</li>
|
|
||||||
<li>
|
|
||||||
{
|
|
||||||
"Ha több válasz van, akkor ', '-vel válaszd el őket ( 'válasz1, válasz2, válasz3' )"
|
|
||||||
}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Kérdéseknél az utolsó sor (ahol a JSON cucc van) jelenleg nem
|
|
||||||
módosítható, csak olyan kérdéseket lehet beküldeni, amik sima
|
|
||||||
kérdés-válaszok, szóval pl nincs benne kép. Ez később bővül majd
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Ha sok új kérdést küldesz be, akkor akár több percig is eltarthat a
|
|
||||||
dolog. Akárhány kérdést be lehet egyszerre küldeni, de max 10-15 az
|
|
||||||
ajánlott
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Bármilyen szöveget beküldhettek, de ne tegyétek, más felhasználóknak
|
|
||||||
és magatoknak lesz rosz, ty!
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<hr />
|
||||||
<title>Qmining - Kérdés beküldés | Frylabs.net</title>
|
{renderUsage()}
|
||||||
</Head>
|
|
||||||
{subjects ? (
|
{subjects ? (
|
||||||
<>
|
<>
|
||||||
{renderUsage()}
|
|
||||||
<hr />
|
<hr />
|
||||||
{renderSubjSelector()}
|
{renderSubjSelector()}
|
||||||
{renderStuff()}
|
{renderStuff()}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : null}
|
||||||
<LoadingIndicator />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +1,28 @@
|
||||||
.questionInput {
|
.questionInput {
|
||||||
flex-grow: 1;
|
width: 80%;
|
||||||
font-size: 16px;
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border-radius: 5px;
|
||||||
padding: 8px;
|
border: 1px solid #333;
|
||||||
margin: 4px;
|
|
||||||
border: 1px solid;
|
|
||||||
border-color: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.questionContainer {
|
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
margin-right: 10px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputContainer {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainer {
|
|
||||||
text-align: center;
|
|
||||||
width: 200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteButton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #333;
|
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
font-size: 16px;
|
margin: 3px;
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid;
|
|
||||||
border-color: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteButton:hover {
|
|
||||||
background-color: #666;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.questionInput:hover {
|
.questionInput:hover {
|
||||||
border: 1px solid;
|
border: 1px solid #99f;
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--text-color);
|
|
||||||
border: none;
|
|
||||||
padding: 10px 30px;
|
|
||||||
color: white;
|
|
||||||
width: 200px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.subjSelectorContainer {
|
.subjSelectorContainer {
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subjSelector {
|
.subjSelectorContainer > select {
|
||||||
flex-grow: 1;
|
width: 80%;
|
||||||
background-color: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.newSubj {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
background-color: #333;
|
|
||||||
width: 150px;
|
|
||||||
margin: 0px 4px;
|
|
||||||
padding: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newSubj:hover {
|
|
||||||
background-color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage li {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage {
|
|
||||||
font-size: 15px;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.issubmitting {
|
.issubmitting {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.newQuestionButton {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newQuestionButton div {
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 10px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newQuestionButton div:hover {
|
|
||||||
background-color: #666;
|
|
||||||
}
|
|
||||||
|
|
0
src/components/questionSearchResult.module.css
Normal file
0
src/components/questionSearchResult.module.css
Normal file
|
@ -1,43 +1,204 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import LoadingIndicator from '../components/LoadingIndicator.js'
|
import LoadingIndicator from '../components/LoadingIndicator.js'
|
||||||
import QuestionSearchResult from '../components/QuestionSearchResult.js'
|
import QuestionSearchResult from '../components/QuestionSearchResult.js'
|
||||||
|
import SearchBar from '../components/searchBar'
|
||||||
|
|
||||||
import styles from './questionView.module.css'
|
import constants from '../constants.json'
|
||||||
|
// import styles from './questionView.module.css'
|
||||||
|
|
||||||
|
const updateQuestion = (e, selectedDb) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(constants.apiUrl + 'updateQuestion', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...e,
|
||||||
|
selectedDb: selectedDb,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const rmQuestion = (e, selectedDb) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(constants.apiUrl + 'updateQuestion', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...e,
|
||||||
|
selectedDb: selectedDb,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default function questionView(props) {
|
export default function questionView(props) {
|
||||||
const { data, onChange, deleteQuestion } = props
|
const { selectedDb } = props
|
||||||
|
const [data, setData] = useState(props.data)
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
|
const [edits, setEdits] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setData(props.data)
|
||||||
|
}, [props.data])
|
||||||
|
|
||||||
|
const updateEdits = (e) => {
|
||||||
|
setEdits(
|
||||||
|
edits.map((edit, i) => {
|
||||||
|
if (i === e.index) {
|
||||||
|
return e
|
||||||
|
} else {
|
||||||
|
return edit
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFromEdits = (e) => {
|
||||||
|
setEdits(
|
||||||
|
edits.filter((edit, i) => {
|
||||||
|
return i !== e.index
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
const editIndex = edits.findIndex((edit) => {
|
||||||
|
return edit.subjName === e.subjName && edit.index === e.index
|
||||||
|
})
|
||||||
|
if (editIndex === -1) {
|
||||||
|
if (e.type === 'edit') {
|
||||||
|
if (editIndex === -1) {
|
||||||
|
setEdits([...edits, e])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.type === 'delete') {
|
||||||
|
rmQuestion(e, selectedDb).then((res) => {
|
||||||
|
if (res.status === 'OK') {
|
||||||
|
alert('Sikeres törlés')
|
||||||
|
} else {
|
||||||
|
alert('Hiba mentés közben :/')
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(
|
||||||
|
data.map((subj) => {
|
||||||
|
if (subj.Name !== e.subjName) {
|
||||||
|
return subj
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...subj,
|
||||||
|
Questions: subj.Questions.filter((question, i) => {
|
||||||
|
return i !== e.index
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.type === 'reset') {
|
||||||
|
// edits -> saves -> resets? => should do nothing, no reset after saving
|
||||||
|
removeFromEdits(e)
|
||||||
|
setData(
|
||||||
|
data.map((subj) => {
|
||||||
|
if (subj.Name !== e.subjName) {
|
||||||
|
return subj
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...subj,
|
||||||
|
Questions: subj.Questions.map((question, i) => {
|
||||||
|
if (i !== e.index) {
|
||||||
|
return question
|
||||||
|
} else {
|
||||||
|
const ps = props.data.find((subj) => {
|
||||||
|
return subj.Name === e.subjName
|
||||||
|
})
|
||||||
|
if (ps) {
|
||||||
|
return ps.Questions[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (e.type === 'save') {
|
||||||
|
updateQuestion(
|
||||||
|
edits.find((edit) => {
|
||||||
|
return edit.index === e.index
|
||||||
|
}),
|
||||||
|
selectedDb
|
||||||
|
).then((res) => {
|
||||||
|
if (res.status === 'OK') {
|
||||||
|
alert('Sikeres mentés')
|
||||||
|
} else {
|
||||||
|
alert('Hiba mentés közben :/')
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromEdits(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (e.type === 'edit') {
|
||||||
|
updateEdits(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.type === 'edit') {
|
||||||
|
setData(
|
||||||
|
data.map((subj) => {
|
||||||
|
if (subj.Name !== e.subjName) {
|
||||||
|
return subj
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...subj,
|
||||||
|
Questions: subj.Questions.map((question, i) => {
|
||||||
|
if (i !== e.index) {
|
||||||
|
return question
|
||||||
|
} else {
|
||||||
|
return e.newVal
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.searchContainer}>
|
<SearchBar value={searchTerm} onChange={(e) => setSearchTerm(e)} />
|
||||||
<input
|
|
||||||
placeholder="Keresés..."
|
|
||||||
className={styles.searchBar}
|
|
||||||
type="text"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchTerm(e.target.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setSearchTerm('')
|
|
||||||
}}
|
|
||||||
className={styles.clearButton}
|
|
||||||
>
|
|
||||||
X
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr />
|
<hr />
|
||||||
<div>
|
<div>
|
||||||
<QuestionSearchResult
|
<QuestionSearchResult
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
data={data}
|
data={data}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
deleteQuestion={deleteQuestion}
|
edits={edits}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
28
src/components/searchBar.js
Normal file
28
src/components/searchBar.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import styles from './searchBar.module.css'
|
||||||
|
|
||||||
|
export default function SearchBar(props) {
|
||||||
|
const { onChange, value } = props
|
||||||
|
return (
|
||||||
|
<div className={styles.searchContainer}>
|
||||||
|
<input
|
||||||
|
placeholder="Keresés..."
|
||||||
|
className={styles.searchBar}
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
onChange('')
|
||||||
|
}}
|
||||||
|
className={styles.clearButton}
|
||||||
|
>
|
||||||
|
❌
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
27
src/components/searchBar.module.css
Normal file
27
src/components/searchBar.module.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
.searchBar {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
color: white;
|
||||||
|
background-color: #222426;
|
||||||
|
border: none;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchContainer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearButton {
|
||||||
|
font-size: 30px;
|
||||||
|
width: 80px;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearButton:hover {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
16
src/components/subject.module.css
Normal file
16
src/components/subject.module.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.questionContainer > div {
|
||||||
|
padding: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsaved {
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edited {
|
||||||
|
background-color: #222926;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletedQuestion {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -3,71 +3,219 @@ import React, { useState, useEffect } from 'react'
|
||||||
import LoadingIndicator from '../components/LoadingIndicator.js'
|
import LoadingIndicator from '../components/LoadingIndicator.js'
|
||||||
import Subject from '../components/Subject.js'
|
import Subject from '../components/Subject.js'
|
||||||
import SubjectSelector from '../components/SubjectSelector.js'
|
import SubjectSelector from '../components/SubjectSelector.js'
|
||||||
|
import SearchBar from '../components/searchBar'
|
||||||
|
|
||||||
import styles from './subjectView.module.css'
|
import constants from '../constants.json'
|
||||||
|
// import styles from './subjectView.module.css'
|
||||||
|
import commonStyles from '../commonStyles.module.css'
|
||||||
|
|
||||||
|
const onSave = (subjName, changedQuestions, deletedQuestions, selectedDb) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(constants.apiUrl + 'updateQuestion', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
subjName: subjName,
|
||||||
|
changedQuestions: changedQuestions,
|
||||||
|
deletedQuestions: deletedQuestions,
|
||||||
|
type: 'subjEdit',
|
||||||
|
selectedDb: selectedDb,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default function SubjectView(props) {
|
export default function SubjectView(props) {
|
||||||
const { data, onChange, deleteQuestion } = props
|
const { data, selectedDb } = props
|
||||||
const [activeSubjName, setActiveSubjName] = useState('')
|
const [activeSubjName, setActiveSubjName] = useState('')
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
|
|
||||||
const [sCount, setSCount] = useState(0)
|
const [unsavedIndexes, setUnsavedIndexes] = useState([])
|
||||||
const [qCount, setQCount] = useState(0)
|
const [editedIndexes, setEditedIndexes] = useState([])
|
||||||
|
const [deletedIndexes, setDeletedIndexes] = useState([])
|
||||||
|
const [subj, setSubj] = useState(null)
|
||||||
|
|
||||||
|
const hasChange = editedIndexes.length > 0 || deletedIndexes.length > 0
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSCount(data.length)
|
let currSubj = data.find((subj) => {
|
||||||
setQCount(
|
return subj.Name === activeSubjName
|
||||||
data.reduce((acc, subj) => {
|
})
|
||||||
return acc + subj.Questions.length
|
setSubj(currSubj)
|
||||||
}, 0)
|
}, [activeSubjName])
|
||||||
)
|
|
||||||
}, [])
|
const resetQuestion = (i) => {
|
||||||
|
if (deletedIndexes.includes(i)) {
|
||||||
|
setDeletedIndexes(
|
||||||
|
deletedIndexes.filter((ind) => {
|
||||||
|
return ind !== i
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editedIndexes.includes(i)) {
|
||||||
|
setEditedIndexes(
|
||||||
|
editedIndexes.filter((ind) => {
|
||||||
|
return ind !== i
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsavedIndexes.includes(i)) {
|
||||||
|
setUnsavedIndexes(
|
||||||
|
unsavedIndexes.filter((ind) => {
|
||||||
|
return ind !== i
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (data) {
|
|
||||||
let currSubj = data.find((subj) => {
|
let currSubj = data.find((subj) => {
|
||||||
return subj.Name === activeSubjName
|
return subj.Name === activeSubjName
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setSubj({
|
||||||
|
...subj,
|
||||||
|
Questions: subj.Questions.map((q, j) => {
|
||||||
|
if (i === j) {
|
||||||
|
return currSubj.Questions[i]
|
||||||
|
} else {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuestionChange = (newq, i) => {
|
||||||
|
if (!unsavedIndexes.includes(i)) {
|
||||||
|
setUnsavedIndexes([...unsavedIndexes, i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editedIndexes.includes(i)) {
|
||||||
|
setEditedIndexes(
|
||||||
|
editedIndexes.filter((ind) => {
|
||||||
|
return ind !== i
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubj({
|
||||||
|
...subj,
|
||||||
|
Questions: subj.Questions.map((q, j) => {
|
||||||
|
if (i !== j) {
|
||||||
|
return q
|
||||||
|
} else {
|
||||||
|
return newq
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveQuestion = (i) => {
|
||||||
|
setUnsavedIndexes(
|
||||||
|
unsavedIndexes.filter((ind) => {
|
||||||
|
return ind !== i
|
||||||
|
})
|
||||||
|
)
|
||||||
|
setEditedIndexes([...editedIndexes, i])
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteQuestion = (i) => {
|
||||||
|
setDeletedIndexes([...deletedIndexes, i])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (unsavedIndexes.length > 0) {
|
||||||
|
alert(
|
||||||
|
'Mentetlen módosításaid vannak, kérek mentsd, vagy állítsd vissza azokat'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (editedIndexes.length === 0 && deletedIndexes.length === 0) {
|
||||||
|
alert('Nem módosítottál még semmit')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const changedQuestions = editedIndexes.map((ei) => {
|
||||||
|
return {
|
||||||
|
index: ei,
|
||||||
|
value: subj.Questions[ei],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const deletedQuestions = deletedIndexes
|
||||||
|
onSave(subj.Name, changedQuestions, deletedQuestions, selectedDb).then(
|
||||||
|
(res) => {
|
||||||
|
if (res.status === 'OK') {
|
||||||
|
alert('Sikeres mentés')
|
||||||
|
} else {
|
||||||
|
alert('Hiba mentés közben :/')
|
||||||
|
}
|
||||||
|
|
||||||
|
setUnsavedIndexes([])
|
||||||
|
setEditedIndexes([])
|
||||||
|
setDeletedIndexes([])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.searchContainer}>
|
<SearchBar value={searchTerm} onChange={(e) => setSearchTerm(e)} />
|
||||||
<input
|
|
||||||
placeholder="Keresés..."
|
|
||||||
className={styles.searchBar}
|
|
||||||
type="text"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchTerm(e.target.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setSearchTerm('')
|
|
||||||
}}
|
|
||||||
className={styles.clearButton}
|
|
||||||
>
|
|
||||||
X
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr />
|
<hr />
|
||||||
<SubjectSelector
|
<SubjectSelector
|
||||||
data={data}
|
data={data}
|
||||||
activeSubjName={activeSubjName}
|
activeSubjName={activeSubjName}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
onSubjSelect={(subjName) => {
|
onSubjSelect={(subjName) => {
|
||||||
setActiveSubjName(subjName)
|
if (
|
||||||
|
!hasChange ||
|
||||||
|
confirm(
|
||||||
|
'Mentetlen módosításaid vannak a tárgynál, biztos bezárod?'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setActiveSubjName(subjName)
|
||||||
|
setUnsavedIndexes([])
|
||||||
|
setEditedIndexes([])
|
||||||
|
setDeletedIndexes([])
|
||||||
|
} else {
|
||||||
|
console.log('canceld')
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<hr />
|
<hr />
|
||||||
<div>
|
<div className={commonStyles.actions}>
|
||||||
{qCount} kérdés, {sCount} tárgy
|
{subj && (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
handleSave()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tárgy módosításainak mentése
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Subject
|
{subj && (
|
||||||
onChange={onChange}
|
<Subject
|
||||||
subj={currSubj}
|
subj={subj}
|
||||||
deleteQuestion={deleteQuestion}
|
unsavedIndexes={unsavedIndexes}
|
||||||
/>
|
editedIndexes={editedIndexes}
|
||||||
|
deletedIndexes={deletedIndexes}
|
||||||
|
resetQuestion={resetQuestion}
|
||||||
|
handleQuestionChange={handleQuestionChange}
|
||||||
|
saveQuestion={saveQuestion}
|
||||||
|
deleteQuestion={deleteQuestion}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
.searchBar {
|
.actions {
|
||||||
margin: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
color: white;
|
|
||||||
background-color: #222426;
|
|
||||||
border: none;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchContainer {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearButton {
|
.actions > div {
|
||||||
width: 80px;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
padding: 5px 20px;
|
||||||
|
|
||||||
|
color: #eee;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div:hover {
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
189
src/components/testView.js
Normal file
189
src/components/testView.js
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
import Question from '../components/Question'
|
||||||
|
|
||||||
|
import constants from '../constants.json'
|
||||||
|
import styles from './testView.module.css'
|
||||||
|
import commonStyles from '../commonStyles.module.css'
|
||||||
|
|
||||||
|
const rmTest = (subjName, testName) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(constants.apiUrl + 'rmPossibleAnswer', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
subj: subjName,
|
||||||
|
file: testName,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TestView(props) {
|
||||||
|
const { subjName, testName, router, onDelete } = props
|
||||||
|
const [test, setTest] = useState(props.test)
|
||||||
|
|
||||||
|
const renderActions = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.actions}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm('Biztos véglegesen törlöd ezt az egész tesztet?')) {
|
||||||
|
rmTest(subjName, testName).then((res) => {
|
||||||
|
if (res.res === 'ok') {
|
||||||
|
// alert('sikeres törlés')
|
||||||
|
router.back()
|
||||||
|
onDelete()
|
||||||
|
} else {
|
||||||
|
alert('hiba törlés közben!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Teszt törlése
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
confirm(
|
||||||
|
'Biztos beküldöd? Beküldés után törlődik a teszt, de a Kérdések/Tárgyak nézetnél megtalálhatóak lesznek'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const questions = test.questions.map((q) => {
|
||||||
|
return {
|
||||||
|
Q: q.Q,
|
||||||
|
A: q.A,
|
||||||
|
data: {
|
||||||
|
...q.data,
|
||||||
|
...(q.possibleAnswers && {
|
||||||
|
possibleAnswers: q.possibleAnswers,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const toSend = {
|
||||||
|
id: 'WEBSITE',
|
||||||
|
version: 'WEBSITE',
|
||||||
|
location: `https://${test.testUrl}`,
|
||||||
|
subj: test.subj,
|
||||||
|
quiz: questions,
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(constants.apiUrl + 'isAdding', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(toSend),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
alert(
|
||||||
|
`Sikeres beküldés, ${res.totalNewQuestions} új kérdés`
|
||||||
|
)
|
||||||
|
rmTest(subjName, testName).then((res) => {
|
||||||
|
if (res.res === 'ok') {
|
||||||
|
router.back()
|
||||||
|
onDelete()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
alert('Hiba beküldés közben :/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Teszt mentése
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.headerContainer}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div>Tárgy neve</div>
|
||||||
|
<div>{test.subj}</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div>Teszt URL</div>
|
||||||
|
<div>{test.testUrl}</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div>Beküldő felhasználó ID</div>
|
||||||
|
<div>{test.userid}</div>{' '}
|
||||||
|
</div>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div>Beküldés ideje</div>
|
||||||
|
<div>{new Date(test.date).toLocaleString()}</div>{' '}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
{renderActions()}
|
||||||
|
<div className={styles.questionsContainer}>
|
||||||
|
{test.questions.map((question, i) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
<hr key={`${i}hr`} />
|
||||||
|
<div key={i}>
|
||||||
|
<Question
|
||||||
|
index={i}
|
||||||
|
onChange={(newQ) => {
|
||||||
|
setTest({
|
||||||
|
...test,
|
||||||
|
questions: test.questions.map((q, j) => {
|
||||||
|
if (j === i) {
|
||||||
|
return newQ
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
question={question}
|
||||||
|
/>
|
||||||
|
<div className={commonStyles.actions}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setTest({
|
||||||
|
...test,
|
||||||
|
questions: test.questions.filter((q, j) => {
|
||||||
|
return j !== i
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Kérdés törlése
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{test.questions.length > 2 ? (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
{renderActions()}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
46
src/components/testView.module.css
Normal file
46
src/components/testView.module.css
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
.headerContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header :first-child {
|
||||||
|
color: white;
|
||||||
|
flex: 0 170px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header > * {
|
||||||
|
margin: 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.questionsContainer > div {
|
||||||
|
padding: 20px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 20px;
|
||||||
|
|
||||||
|
color: #eee;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > div:hover {
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"apiUrl": "https://api.frylabs.net/",
|
"apiUrl": "http://localhost:8080/",
|
||||||
|
"siteUrl": "http://localhost:8080/",
|
||||||
"maxQuestionsToRender": 250
|
"maxQuestionsToRender": 250
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
// import App from 'next/app'
|
// import App from 'next/app'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
import '../defaultStyles.css'
|
import '../defaultStyles.css'
|
||||||
|
|
||||||
function MyApp ({ Component, pageProps, router }) {
|
function MyApp({ Component, pageProps, router }) {
|
||||||
return (
|
return <Component {...pageProps} router={router} />
|
||||||
<Component {...pageProps} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only uncomment this method if you have blocking data requirements for
|
// Only uncomment this method if you have blocking data requirements for
|
||||||
|
|
|
@ -1,37 +1,118 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import fetch from 'unfetch'
|
import Head from 'next/head'
|
||||||
|
|
||||||
import SubjectView from '../components/subjectView'
|
import SubjectView from '../components/subjectView'
|
||||||
import QuestionView from '../components/questionView'
|
import QuestionView from '../components/questionView'
|
||||||
import QuestionAdder from '../components/questionAdder.js'
|
import QuestionAdder from '../components/questionAdder.js'
|
||||||
|
import PossibleAnswers from '../components/possibleAnswers.js'
|
||||||
|
|
||||||
|
import DbSelector from '../components/dbSelector.js'
|
||||||
import LoadingIndicator from '../components/LoadingIndicator'
|
import LoadingIndicator from '../components/LoadingIndicator'
|
||||||
|
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
import commonStyles from '../commonStyles.module.css'
|
||||||
import constants from '../constants.json'
|
import constants from '../constants.json'
|
||||||
|
|
||||||
const views = {
|
const Infos = ({ onClick, renderOKButton }) => {
|
||||||
welcome: 'WELCOME',
|
return (
|
||||||
subject: 'SUBJECT',
|
<div className={commonStyles.infoContainer}>
|
||||||
question: 'QUESTION',
|
<div className={commonStyles.infoHeader}>Kérdés szerkesztő</div>
|
||||||
questionAdder: 'QADDER',
|
<div>
|
||||||
|
Ezen az oldalon az éles adatbázisban levő kérdéseket tudod szerkeszteni,
|
||||||
|
vagy azokhoz tudsz adni.{' '}
|
||||||
|
<b>
|
||||||
|
A törléshez és módosításokhoz nem kér megerősítést, ezek azonnal
|
||||||
|
megtörténnek, és nem visszavonhatóak.
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<i>Néhány dolog, amit kérlek tarts be szerkesztés közben:</i>
|
||||||
|
<div style={{ textAlign: 'left', width: '700px' }}>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Ne rontsd el a kérdéseket sok törléssel / rossz válasz
|
||||||
|
megadásával. Sok más felhasználónak lesz rossz, és visszakereshető
|
||||||
|
/ tiltható a módosító
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Arra is vigyázz, hogy véletlen se történjen ilyesmi, vagy ha mégis
|
||||||
|
valami baj történt, akkor azt{' '}
|
||||||
|
<a href={`${constants.siteUrl}irc`}>jelezd</a>. Van sok biztonsági
|
||||||
|
mentés
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Ahhoz, hogy a script megtalálja a helyes választ a kérdés
|
||||||
|
szövegének <b>pontosan</b> olyannak kell lennie, mint a teszt
|
||||||
|
közben (elírásokkal, .... -okkal, meg mindennel)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Ha akármi kérdés van,{' '}
|
||||||
|
<a href={`${constants.siteUrl}irc`}>
|
||||||
|
itt lehet chatszobában kérdezni
|
||||||
|
</a>
|
||||||
|
, vagy{' '}
|
||||||
|
<a href={`${constants.siteUrl}feedback`}>
|
||||||
|
itt lehet üzenetet küldeni
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{renderOKButton && (
|
||||||
|
<div className={commonStyles.infoReadButton} onClick={onClick}>
|
||||||
|
OK
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add question on subjects view
|
const fetchDbs = () => {
|
||||||
// TODO: Add subject on subjects view
|
return new Promise((resolve) => {
|
||||||
// TODO: question.data editor
|
fetch(`${constants.apiUrl}getdbs`, {
|
||||||
// TODO: edit \n-s in questions / answers
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
resolve(resp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default function Index() {
|
const fetchData = (selectedDb) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const toFetch = `${constants.apiUrl}${selectedDb.path}`
|
||||||
|
fetch(toFetch, {
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
resolve(resp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const views = {
|
||||||
|
welcome: 'w',
|
||||||
|
subject: 's',
|
||||||
|
question: 'q',
|
||||||
|
questionAdder: 'qa',
|
||||||
|
possibleAnswers: 'pa',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Index({ router }) {
|
||||||
|
const [infoRead, setInfoRead] = useState(false)
|
||||||
const [data, setData] = useState(null)
|
const [data, setData] = useState(null)
|
||||||
const [qdbs, setQdbs] = useState(null)
|
const [qdbs, setQdbs] = useState(null)
|
||||||
const [selectedDb, setSelectedDb] = useState()
|
const [selectedDb, setSelectedDb] = useState()
|
||||||
const [view, setView] = useState(views.welcome)
|
const [view, setView] = useState(views.welcome)
|
||||||
const [sending, setSending] = useState(false)
|
|
||||||
const [error, setError] = useState(null)
|
|
||||||
const [password, setPassword] = useState('')
|
|
||||||
const [editedQuestions, setEditedQuestions] = useState({})
|
|
||||||
|
|
||||||
const [initialCount, setInitialCount] = useState({})
|
const [error, setError] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedDb) {
|
if (selectedDb) {
|
||||||
|
@ -39,25 +120,16 @@ export default function Index() {
|
||||||
}
|
}
|
||||||
}, [selectedDb])
|
}, [selectedDb])
|
||||||
|
|
||||||
const getCount = (data) => {
|
useEffect(() => {
|
||||||
return {
|
fetchDbs().then((resp) => {
|
||||||
subjectCount: data.length,
|
setQdbs(resp)
|
||||||
questionCount: data.reduce((acc, subj) => {
|
|
||||||
acc += subj.Questions.length
|
|
||||||
return acc
|
|
||||||
}, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setIndexes = (data) => {
|
const view = router.query.v
|
||||||
data.forEach((subj, i) => {
|
? decodeURIComponent(router.query.v)
|
||||||
subj.ind = i
|
: views.welcome
|
||||||
subj.Questions.forEach((question, j) => {
|
setView(view)
|
||||||
question.ind = j
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return data
|
}, [])
|
||||||
}
|
|
||||||
|
|
||||||
const loadData = () => {
|
const loadData = () => {
|
||||||
setData(null)
|
setData(null)
|
||||||
|
@ -66,18 +138,9 @@ export default function Index() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const toFetch = `${constants.apiUrl}${selectedDb.path}`
|
fetchData(selectedDb)
|
||||||
console.info('Fetching', toFetch)
|
|
||||||
fetch(toFetch, {
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
return resp.json()
|
setData(resp)
|
||||||
})
|
|
||||||
.then((resp) => {
|
|
||||||
setData(setIndexes(resp))
|
|
||||||
const count = getCount(resp)
|
|
||||||
setInitialCount(count)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -86,240 +149,162 @@ export default function Index() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const refetchDbs = () => {
|
||||||
fetch(`${constants.apiUrl}getdbs`, {
|
if (selectedDb) {
|
||||||
credentials: 'include',
|
fetchData(selectedDb).then((resp) => {
|
||||||
})
|
setData(resp)
|
||||||
.then((resp) => {
|
|
||||||
return resp.json()
|
|
||||||
})
|
})
|
||||||
.then((resp) => {
|
|
||||||
setQdbs(resp)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <div>{error}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteQuestion = (subjInd, questionInd) => {
|
|
||||||
data[subjInd].Questions.splice(questionInd, 1)
|
|
||||||
|
|
||||||
setData([...setIndexes(data)])
|
|
||||||
}
|
|
||||||
|
|
||||||
const onChange = (subjInd, questionInd, newVal) => {
|
|
||||||
const key = subjInd + '/' + questionInd
|
|
||||||
setEditedQuestions({
|
|
||||||
...editedQuestions,
|
|
||||||
[key]: editedQuestions[key] ? editedQuestions[key] + 1 : 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
data[subjInd].Questions[questionInd] = newVal
|
|
||||||
setData([...data])
|
|
||||||
}
|
|
||||||
|
|
||||||
const SendDataToServer = async () => {
|
|
||||||
if (sending) {
|
|
||||||
alert('Adatok már feltöltés alatt, várd meg míg feltölti') // eslint-disable-line
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
fetchDbs().then((resp) => {
|
||||||
data.forEach((subj) => {
|
setQdbs(resp)
|
||||||
delete subj.ind
|
|
||||||
subj.Questions.forEach((question) => {
|
|
||||||
delete question.ind
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setSending(true)
|
const onQuestionDelete = (deletedQuestions) => {
|
||||||
try {
|
console.log(deletedQuestions)
|
||||||
const rawResponse = await fetch(constants.apiUrl + 'uploaddata', {
|
}
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
const onQuestionChange = (newVal, subjName, questionIndex) => {
|
||||||
Accept: 'application/json',
|
console.log(newVal, subjName, questionIndex)
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify({
|
|
||||||
newData: data,
|
|
||||||
count: getCount(data),
|
|
||||||
initialCount: initialCount,
|
|
||||||
password: password,
|
|
||||||
editedQuestions: editedQuestions,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
rawResponse
|
|
||||||
.json()
|
|
||||||
.then((resp) => {
|
|
||||||
setSending(false)
|
|
||||||
if (resp.status === 'ok') {
|
|
||||||
alert(`Sikeres feltöltés! thankx ${resp.user}!`) // eslint-disable-line
|
|
||||||
console.log('OK')
|
|
||||||
loadData()
|
|
||||||
} else if (resp.status === 'invalidPass') {
|
|
||||||
alert('Hibás jelszó!') // eslint-disable-line
|
|
||||||
console.log('invalidPass')
|
|
||||||
} else {
|
|
||||||
alert(
|
|
||||||
'Hiba feltöltés közben! (szerver oldalon)! Több adat konzolban'
|
|
||||||
) // eslint-disable-line
|
|
||||||
console.error('RESPONSE', resp)
|
|
||||||
console.error(resp.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setSending(false)
|
|
||||||
alert('Hiba feltöltés közben! (kliens oldalon)! Több adat konzolban') // eslint-disable-line
|
|
||||||
console.error('Error posting data', error)
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
setSending(false)
|
|
||||||
alert('Hiba feltöltés közben! (kliens oldalon)! Több adat konzolban') // eslint-disable-line
|
|
||||||
console.error('Error posting data', error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
if (view === views.subject) {
|
if (view === views.subject) {
|
||||||
return (
|
return (
|
||||||
<SubjectView
|
<>
|
||||||
onChange={onChange}
|
<Head>
|
||||||
data={data}
|
<title>Tárgyak - Data Editor | Frylabs.net</title>
|
||||||
deleteQuestion={deleteQuestion}
|
</Head>
|
||||||
/>
|
<DbSelector qdbs={qdbs} onChange={setSelectedDb} />
|
||||||
|
{data && <SubjectView selectedDb={selectedDb} data={data} />}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
} else if (view === views.question) {
|
} else if (view === views.question) {
|
||||||
return (
|
return (
|
||||||
<QuestionView
|
<>
|
||||||
onChange={onChange}
|
<Head>
|
||||||
data={data}
|
<title>Kérdések - Data Editor | Frylabs.net</title>
|
||||||
deleteQuestion={deleteQuestion}
|
</Head>
|
||||||
/>
|
<DbSelector qdbs={qdbs} onChange={setSelectedDb} />
|
||||||
|
{data && (
|
||||||
|
<QuestionView
|
||||||
|
selectedDb={selectedDb}
|
||||||
|
onChange={onQuestionChange}
|
||||||
|
onDelete={onQuestionDelete}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
} else if (view === views.questionAdder) {
|
} else if (view === views.questionAdder) {
|
||||||
return <QuestionAdder />
|
|
||||||
} else if (view === views.welcome) {
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.welcome}>
|
<>
|
||||||
Kérdés szerkesztő
|
<Head>
|
||||||
<br />
|
<title>Kérdés beküldés - Data Editor | Frylabs.net</title>
|
||||||
{initialCount.questionCount || '...'} kérdés,{' '}
|
</Head>
|
||||||
{initialCount.subjectCount || '...'} tárgy
|
<DbSelector qdbs={qdbs} onChange={setSelectedDb} />
|
||||||
<p />
|
<QuestionAdder
|
||||||
Itt az éles adatbázis kérdései jelennek meg, amiket tudsz
|
data={data}
|
||||||
szerkeszteni. A kérdésekhez tartozó .data prop-ot még nem tudod
|
selectedDb={selectedDb}
|
||||||
rendesen szerkeszteni, az később lesz implementálva. A Tárgy / Kérdés
|
refetchDbs={refetchDbs}
|
||||||
nézet között tudsz válogatni.
|
/>
|
||||||
<br />
|
</>
|
||||||
Tárgy nézet: a tárgyak dobozban kattints egy tárgyra, minden kérdés
|
|
||||||
ott lessz hozzá.
|
|
||||||
<br />
|
|
||||||
Kérdés nézet: Kereső sávba írd be a kérdést / választ, a találatok ha
|
|
||||||
kevesebb mint 250 megjelennek, és ott tudod szerkeszteni
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
} else if (view === views.possibleAnswers) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Kitöltetlen tesztek - Data Editor | Frylabs.net</title>
|
||||||
|
</Head>
|
||||||
|
<PossibleAnswers refetchDbs={refetchDbs} router={router} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} else if (view === views.welcome) {
|
||||||
|
return <Infos renderOKButton={false} />
|
||||||
} else {
|
} else {
|
||||||
return <div>No view!</div>
|
return <div>No view!</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderDbSelector = () => {
|
function renderViewSelector() {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.viewButtonContainer}>
|
||||||
<select
|
<div
|
||||||
style={{ margin: '10px 0px' }}
|
className={view === views.question ? styles.activeView : undefined}
|
||||||
defaultValue={-1}
|
onClick={() => {
|
||||||
onChange={(event) => {
|
router.replace(
|
||||||
setSelectedDb(qdbs[event.target.value])
|
`${router.pathname}?v=${views.question}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
setView(views.question)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option disabled value={-1}>
|
Kérdések
|
||||||
{' -- Válassz egy kérdés adatbázist -- '}
|
</div>
|
||||||
</option>
|
<div
|
||||||
{qdbs.map((qdb, i) => {
|
title={
|
||||||
return (
|
'Választott adatbázisban lévő tárgyak megjelenítése, és tárgyakon belüli kérdések szerkesztése'
|
||||||
<option value={i} key={qdb.name}>
|
}
|
||||||
{qdb.name}
|
className={view === views.subject ? styles.activeView : undefined}
|
||||||
</option>
|
onClick={() => {
|
||||||
|
router.replace(`${router.pathname}?v=${views.subject}`, undefined, {
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
setView(views.subject)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tárgyak
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
view === views.questionAdder ? styles.activeView : undefined
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
router.replace(
|
||||||
|
`${router.pathname}?v=${views.questionAdder}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
)
|
)
|
||||||
})}
|
setView(views.questionAdder)
|
||||||
</select>
|
}}
|
||||||
</>
|
>
|
||||||
|
Kérdés beküldés
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
view === views.possibleAnswers ? styles.activeView : undefined
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
router.replace(
|
||||||
|
`${router.pathname}?v=${views.possibleAnswers}`,
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
)
|
||||||
|
setView(views.possibleAnswers)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Kitöltetlen tesztek
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>{error}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!infoRead) {
|
||||||
|
return <Infos onClick={() => setInfoRead(true)} renderOKButton />
|
||||||
|
}
|
||||||
|
|
||||||
if (!qdbs) {
|
if (!qdbs) {
|
||||||
return <LoadingIndicator />
|
return <LoadingIndicator />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{renderDbSelector()}
|
{renderViewSelector()}
|
||||||
<div className={styles.optionsButtonContainer}>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
loadData()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Újratöltés
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<input
|
|
||||||
placeholder="Jelszó kérdések módosításhoz"
|
|
||||||
type="text"
|
|
||||||
value={password}
|
|
||||||
onChange={(event) => {
|
|
||||||
setPassword(event.target.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
if (password && selectedDb) {
|
|
||||||
SendDataToServer()
|
|
||||||
} else {
|
|
||||||
alert('Nincs jelszó, vagy nem választottál ki adatbázist!') // eslint-disable-line
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sending ? 'Feltöltés ...' : 'Mentés'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.viewButtonContainer}>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
if (selectedDb) {
|
|
||||||
setView(views.question)
|
|
||||||
} else {
|
|
||||||
alert('Válassz egy adatbázist!')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Kérdés nézet
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
title={
|
|
||||||
'Választott adatbázisban lévő tárgyak megjelenítése, és tárgyakon belüli kérdések szerkesztése'
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
if (selectedDb) {
|
|
||||||
setView(views.subject)
|
|
||||||
} else {
|
|
||||||
alert('Válassz egy adatbázist!')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Tárgy nézet
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
setView(views.questionAdder)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Kérdés beküldés
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{renderView()}
|
{renderView()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,57 +1,32 @@
|
||||||
.viewButtonContainer span {
|
.viewButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewButtonContainer div {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 5px;
|
margin: 0px 2px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-color: var(--background-color);
|
border-color: var(--background-color);
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionsButtonContainer span {
|
.viewButtonContainer div:hover {
|
||||||
cursor: pointer;
|
border-color: white;
|
||||||
display: inline-block;
|
|
||||||
margin: 3px;
|
|
||||||
padding: 3px;
|
|
||||||
height: 25px;
|
|
||||||
font-size: 18px;
|
|
||||||
width: 26%;
|
|
||||||
text-align: center;
|
|
||||||
border-color: var(--background-color);
|
|
||||||
border: 1px solid;
|
|
||||||
word-wrap: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optionsButtonContainer span input {
|
|
||||||
width: 95%;
|
|
||||||
font-size: 16px;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: white;
|
color: white;
|
||||||
border: 0px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optionsButtonContainer span:hover {
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewButtonContainer span:hover {
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optionsButtonContainer {
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewButtonContainer {
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome {
|
.welcome {
|
||||||
|
padding: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activeView {
|
||||||
|
color: #99f;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue