This commit is contained in:
mrfry 2020-11-28 17:02:29 +01:00
commit 6d2b682117
14 changed files with 329 additions and 122 deletions

View file

@ -0,0 +1,49 @@
import React from 'react'
import TodoCard from './todoCard.js'
import styles from './todoBoard.module.css'
export default function TodoBoard(props) {
const { columns, cards, userId, categories, onCardClick } = props
const clickableTypes = Object.keys(columns).reduce((acc, key) => {
const col = columns[key]
if (col.clickable) {
acc.push(key)
}
return acc
}, [])
return (
<div className={styles.tableContainer}>
<div className={styles.table}>
{Object.keys(columns).map((key) => {
const category = columns[key]
return (
<div className={styles.tableCol} key={key}>
<div className={styles.categoryName}>{category.name}</div>
{cards.map((card, i) => {
if (card.state === key) {
// TODO: determine if clickable here
return (
<TodoCard
key={i}
cardData={card}
userId={userId}
categories={categories}
onClick={onCardClick}
clickable={clickableTypes.includes(key)}
/>
)
} else {
return null
}
})}
</div>
)
})}
</div>
</div>
)
}

View file

@ -0,0 +1,23 @@
.tableContainer {
overflow-y: hidden;
overflow-x: auto;
margin: 5px 0px;
}
.table {
display: flex;
min-width: 800px;
}
.categoryName {
text-align: center;
margin: 5px 0px;
font-size: 16px;
font-weight: bold;
color: white;
white-space: nowrap;
}
.tableCol {
flex: 1;
}

View file

@ -2,12 +2,9 @@ import React from 'react'
import styles from './todoCard.module.css' import styles from './todoCard.module.css'
const clickableTypes = ['todo', 'blocked', 'inprogress', 'testing']
export default function TodoCard(props) { export default function TodoCard(props) {
const { categories, type, onClick, userId } = props const { categories, onClick, userId, clickable } = props
const { name, description, category, points, votes, id } = props.cardData const { name, description, category, points, votes, id } = props.cardData
const clickable = clickableTypes.includes(type)
const voted = votes.includes(userId) const voted = votes.includes(userId)
return ( return (

View file

@ -0,0 +1,34 @@
import React from 'react'
import styles from './todoRow.module.css'
export default function TodoRow(props) {
const { categories, userId } = props
const { name, description, category, votes, id } = props.rowData
const voted = votes.includes(userId)
return (
<div
className={`${styles.row} ${voted && styles.voted}`}
title={description}
>
<div className={styles.id}>{`#${id}`}</div>
<div className={styles.description}>{name}</div>
<div className={styles.catName}>
<div
style={{
wordBreak: 'break-all',
fontSize: '10px',
backgroundColor: categories[category].color,
color: 'white',
borderRadius: '2px',
padding: '0px 2px',
}}
>
{categories[category].name}
</div>
</div>
<div className={styles.votes}>{`Votes: ${votes.length}`}</div>
</div>
)
}

View file

@ -0,0 +1,40 @@
.row {
display: flex;
border: 2px solid #99f;
border-radius: 2px;
padding: 5px;
margin: 6px 3px;
}
.row > div {
margin: 0px 5px;
}
.id {
flex: 0 20px;
margin: 0px 3px;
color: #999;
}
.description {
flex: 1;
word-break: normal;
font-size: 14px;
color: white;
}
.votes {
}
.voted {
border: 2px solid #cf9;
}
.catName {
display: flex;
}
.category {
word-break: break-all;
font-size: 10px;
}

View file

@ -1,80 +1,35 @@
import React, { useState, useEffect } from 'react' import React from 'react'
import LoadingIndicator from '../LoadingIndicator.js' import TodoRow from './todoRow.js'
import TodoCard from './todoCard.js'
import constants from '../../constants.json'
import styles from './todoTable.module.css' import styles from './todoTable.module.css'
export default function TodoTable() { export default function TodoBoard(props) {
const [cards, setCards] = useState(null) const { tables, cards, userId, categories } = props
const [columns, setColumns] = useState(null)
const [categories, setCategories] = useState(null)
const [userId, setUserId] = useState(null)
useEffect(() => {
console.info('Fetching todos')
fetch(`${constants.apiUrl}todos`, {
credentials: 'include',
})
.then((resp) => {
return resp.json()
})
.then((data) => {
setTodos(data)
})
}, [])
const setTodos = (data) => {
setCards(
data.todos.cards.sort((a, b) => {
return b.votes.length - a.votes.length
})
)
setColumns(data.todos.columns)
setCategories(data.todos.categories)
setUserId(data.userId)
}
const onCardClick = (id) => {
fetch(`${constants.apiUrl}voteTodo?id=${id}`, {
credentials: 'include',
})
.then((resp) => {
return resp.json()
})
.then((data) => {
setTodos(data)
})
}
if (!cards || !columns || !categories) {
return <LoadingIndicator />
}
return ( return (
<div> <div className={styles.tableContainer}>
<div className={styles.table}> <div className={styles.table}>
{Object.keys(columns).map((key) => { {Object.keys(tables).map((key) => {
const category = columns[key] const table = tables[key]
const tableCards = cards.filter((card) => {
return card.state === key
})
return ( return (
<div className={styles.tableCol} key={key}> <div className={styles.container} key={key}>
<div className={styles.categoryName}>{category.name}</div> <div className={styles.title}>{table.name}</div>
{cards.map((card, i) => { {tableCards.map((card, i) => {
if (card.state === key) { return (
return ( <TodoRow
<TodoCard key={i}
key={i} type={key}
type={key} rowData={card}
cardData={card} userId={userId}
userId={userId} categories={categories}
categories={categories} />
onClick={onCardClick} )
/>
)
} else {
return null
}
})} })}
</div> </div>
) )

View file

@ -1,16 +1,17 @@
.table { .tableContainer {
display: flex;
}
.categoryName {
text-align: center;
margin: 5px 0px; margin: 5px 0px;
font-size: 16px;
font-weight: bold;
color: white;
white-space: nowrap;
} }
.tableCol { .table {
flex: 1; justify-content: center;
}
.container {
flex-direction: column;
}
.title {
text-align: center;
font-size: 20px;
font-weight: bold;
} }

View file

@ -0,0 +1,107 @@
import React, { useState, useEffect } from 'react'
import LoadingIndicator from '../LoadingIndicator.js'
import TodoBoard from './todoBoard.js'
import TodoTable from './todoTable.js'
import constants from '../../constants.json'
const byVotes = (a, b) => {
return b.votes.length - a.votes.length
}
export default function Todos() {
const [loaded, setLoaded] = useState(false)
const [columns, setColumns] = useState(null)
const [boardCards, setBoardCards] = useState(null)
const [tables, setTables] = useState(null)
const [tableCards, setTableCards] = useState(null)
const [categories, setCategories] = useState(null)
const [userId, setUserId] = useState(null)
useEffect(() => {
console.info('Fetching todos')
fetch(`${constants.apiUrl}todos`, {
credentials: 'include',
})
.then((resp) => {
return resp.json()
})
.then((data) => {
setTodos(data)
})
}, [])
const setTodos = (data) => {
setCategories(data.todos.categories)
setUserId(data.userId)
let bCards = []
let tCards = []
data.todos.cards.forEach((card) => {
if (data.todos.columns[card.state].type === 'table') {
tCards.push(card)
} else {
bCards.push(card)
}
})
setTableCards(tCards.sort(byVotes))
setBoardCards(bCards.sort(byVotes))
let cols = {}
let tables = {}
Object.keys(data.todos.columns).forEach((key) => {
const col = data.todos.columns[key]
if (col.type !== 'table') {
cols = {
...cols,
[key]: col,
}
} else {
tables = {
...tables,
[key]: col,
}
}
})
setColumns(cols)
setTables(tables)
setLoaded(true)
}
const onCardClick = (id) => {
fetch(`${constants.apiUrl}todos?id=${id}`, {
credentials: 'include',
})
.then((resp) => {
return resp.json()
})
.then((data) => {
setTodos(data)
})
}
if (!loaded) {
return <LoadingIndicator />
}
return (
<div>
<TodoBoard
columns={columns}
cards={boardCards}
userId={userId}
categories={categories}
onCardClick={onCardClick}
/>
<TodoTable
tables={tables}
cards={tableCards}
userId={userId}
categories={categories}
/>
</div>
)
}

View file

@ -10,5 +10,13 @@
"data": { "data": {
"href": "/data.json", "href": "/data.json",
"text": "Összes kérdés JSON" "text": "Összes kérdés JSON"
},
"irc": {
"href": "/irc",
"text": "IRC chat"
},
"dataeditor": {
"href": "/dataeditor",
"text": "Dataeditor"
} }
} }

View file

@ -25,7 +25,7 @@
}, },
"contribute": { "contribute": {
"href": "/contribute", "href": "/contribute",
"text": "Contribute / Todos" "text": "Todos, contribute"
}, },
"ranklist": { "ranklist": {
"href": "/ranklist", "href": "/ranklist",

View file

@ -1,9 +1,8 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import Head from 'next/head' import Head from 'next/head'
import Button from '../components/Button.js'
import Sleep from '../components/sleep' import Sleep from '../components/sleep'
import TodoTable from '../components/todoStuff/todoTable' import Todos from '../components/todoStuff/todos'
import constants from '../constants.json' import constants from '../constants.json'
import styles from './contribute.module.css' import styles from './contribute.module.css'
@ -72,39 +71,26 @@ export default function contribute() {
annál nehezebb a feladat. Tartsd egeret kártyán részletesebb leírásért annál nehezebb a feladat. Tartsd egeret kártyán részletesebb leírásért
(ha van hozzá) (ha van hozzá)
</div> </div>
<TodoTable /> <Todos />
<div className={styles.description}>Itt írhatsz todo-ra ötleteket </div> <div className={styles.description}>Itt írhatsz todo-ra ötleteket </div>
{renderNewTaskArea()} {renderNewTaskArea()}
<Sleep /> <Sleep />
<hr /> <hr />
<div className={styles.description}> <div className={styles.title}>Git repos</div>
Csak álnévvel commitolj git repókhoz! <hr />
</div> <hr />
<div className={styles.repos}> <div className={styles.repos}>
<div>Git repo links</div> <ul>
{Object.keys(repos.repos).map((key) => { {Object.keys(repos.repos).map((key) => {
let repo = repos.repos[key] let repo = repos.repos[key]
return ( return (
<div key={key}> <li key={key}>
<a href={repo.href}>{repo.description}</a> <a href={repo.href}>{repo.description}</a>
</div> </li>
) )
})} })}
</ul>
</div> </div>
<hr />
<div className={styles.description}>
IRC chat: egy IRC chatszoba van létrehozva egy random szerveren, ahol
tudsz azonnal üzenni, és ha épp fent vagyok akkor azonnal válaszolok
</div>
<Button text="IRC chat" href="/irc" />
<hr />
<div className={styles.description}>
Kérdés szerkesztő: Ezen az oldalon lehet szerkeszteni az összes kérdést,
duplikációkat eltávolítani vagy helytelen válaszokat kijavítani kézzel.
Ha van hozzá jelszavad, akkor ezt azonnal el tudod menteni, ha nincs és
úgy érzed szeretnél akkor kattints a fenti IRC chat gombra!
</div>
<Button text="Data Editor" href="/dataeditor" />
</div> </div>
) )
} }

View file

@ -1,9 +1,3 @@
.repos {
display: flex;
flex-direction: column;
align-items: center;
}
.description { .description {
font-size: 15px; font-size: 15px;
color: white; color: white;
@ -39,3 +33,9 @@
.inputArea { .inputArea {
display: flex; display: flex;
} }
.title {
color: #9999ff;
font-size: 30px;
text-align: center;
}

View file

@ -167,6 +167,7 @@ export default function Index({ router }) {
{renderMotd()} {renderMotd()}
{userSpecificMotd && renderUserSpecificMotd()} {userSpecificMotd && renderUserSpecificMotd()}
<hr /> <hr />
<hr />
<Sleep /> <Sleep />
{renderNews()} {renderNews()}
</div> </div>

View file

@ -7,10 +7,11 @@
color: white; color: white;
background-color: #303030; background-color: #303030;
margin: 2px; margin: 2px;
padding: 15px 32px; padding: 10px 5px;
text-align: center; text-align: center;
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
word-wrap: break-word;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -69,3 +70,8 @@
margin: 0px 5px; margin: 0px 5px;
font-size: 24px; font-size: 24px;
} }
.repos {
display: flex;
flex-direction: column;
}