mirror of
https://gitlab.com/MrFry/qmining-page
synced 2025-04-01 20:23:44 +02:00
removed forum, todos, added p2p info page & lots of minor changes/fixes
This commit is contained in:
parent
32522097c0
commit
e2d304c130
28 changed files with 303 additions and 954 deletions
|
@ -6,7 +6,15 @@ function highlightText(text, toHighlight) {
|
|||
}
|
||||
try {
|
||||
const re = new RegExp(toHighlight, 'gi')
|
||||
return text.replace(re, `<mark>${toHighlight}</mark>`)
|
||||
const splitText = text.split(toHighlight)
|
||||
console.log(splitText)
|
||||
return (
|
||||
<>
|
||||
{splitText[0]}
|
||||
<span style={{ color: '#99f' }}>{toHighlight}</span>
|
||||
{splitText[1]}
|
||||
</>
|
||||
)
|
||||
} catch (e) {
|
||||
return text
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.groupDescription {
|
||||
font-size: 16px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.todoButtons {
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import TodoCard from './todoCard'
|
||||
|
||||
import styles from './todoBoard.module.css'
|
||||
|
||||
export default function TodoBoard(props) {
|
||||
const { columns, cards, userId, categories, onCardClick, selectedGroup } =
|
||||
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]
|
||||
const cardsToShow = cards.filter((card) => {
|
||||
const shouldHide =
|
||||
card.state !== key ||
|
||||
(selectedGroup !== null &&
|
||||
selectedGroup !== 'uncat' &&
|
||||
card.group !== selectedGroup) ||
|
||||
(selectedGroup === 'uncat' && card.group !== undefined)
|
||||
|
||||
return !shouldHide
|
||||
})
|
||||
return (
|
||||
<div className={styles.tableCol} key={key}>
|
||||
<div className={styles.categoryName}>{category.name}</div>
|
||||
|
||||
{cardsToShow.length === 0 && (
|
||||
<div className={styles.empty}>Üres</div>
|
||||
)}
|
||||
{cardsToShow.map((card, i) => {
|
||||
return (
|
||||
<TodoCard
|
||||
key={i}
|
||||
cardData={card}
|
||||
userId={userId}
|
||||
categories={categories}
|
||||
onClick={onCardClick}
|
||||
clickable={clickableTypes.includes(key)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
.tableContainer {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.categoryName {
|
||||
text-align: center;
|
||||
margin: 5px 0px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tableCol {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0px;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styles from './todoCard.module.css'
|
||||
|
||||
export default function TodoCard(props) {
|
||||
const { categories, onClick, userId } = props
|
||||
const { name, category, points, votes, id, group } = props.cardData
|
||||
const voted = votes.includes(userId)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.card}
|
||||
style={{
|
||||
border: `1px solid ${group || '#f2cb05'}`,
|
||||
}}
|
||||
onClick={() => {
|
||||
onClick(props.cardData)
|
||||
}}
|
||||
>
|
||||
<div className={styles.description}>
|
||||
<span className={styles.id}>{`#${id}`}</span>
|
||||
{name}
|
||||
</div>
|
||||
<div className={styles.category}>
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: categories[category].color,
|
||||
color: 'white',
|
||||
borderRadius: '2px',
|
||||
padding: '3px',
|
||||
}}
|
||||
>
|
||||
{categories[category].name}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.numbers}>
|
||||
<div
|
||||
className={`${voted && styles.voted}`}
|
||||
>{`Szavazatok: ${votes.length}`}</div>
|
||||
<div>{`Nehézség: ${points}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
.card {
|
||||
border-radius: 5px;
|
||||
padding: 7px;
|
||||
margin: 8px 4px;
|
||||
cursor: pointer;
|
||||
background-color: #171616;
|
||||
}
|
||||
|
||||
.voted {
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
background-color: #333;
|
||||
border: 2px solid #f99;
|
||||
}
|
||||
|
||||
.card > div {
|
||||
margin: 6px 4px;
|
||||
}
|
||||
|
||||
.description {
|
||||
word-break: normal;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.category {
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.numbers {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.numbers > div {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.id {
|
||||
margin: 1px 6px 1px 1px;
|
||||
color: #999;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styles from './todoRow.module.css'
|
||||
|
||||
export default function TodoRow(props) {
|
||||
const { categories, userId, onClick } = props
|
||||
const { name, category, votes, id, group } = props.rowData
|
||||
const voted = votes.includes(userId)
|
||||
const borderColor =
|
||||
categories[category].borderColor || categories[category].color
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
onClick(props.rowData)
|
||||
}}
|
||||
className={styles.row}
|
||||
style={{
|
||||
border: `2px dashed ${borderColor || 'white'}`,
|
||||
borderRadius: '3px',
|
||||
}}
|
||||
>
|
||||
<div className={styles.id}>{`#${id}`}</div>
|
||||
<div className={styles.description}>{name}</div>
|
||||
<div className={styles.catName}>
|
||||
<div
|
||||
style={{
|
||||
wordBreak: 'break-all',
|
||||
fontSize: '12px',
|
||||
backgroundColor: categories[category].color,
|
||||
color: 'white',
|
||||
borderRadius: '2px',
|
||||
padding: '3px',
|
||||
}}
|
||||
>
|
||||
{categories[category].name}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.votes} ${voted && styles.voted}`}
|
||||
>{`Szavazatok: ${votes.length}`}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
.row {
|
||||
border-radius: 5px;
|
||||
padding: 7px;
|
||||
margin: 8px 4px;
|
||||
cursor: pointer;
|
||||
background-color: #171616;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
background-color: #333;
|
||||
border: 2px solid #f99;
|
||||
}
|
||||
|
||||
.row > div {
|
||||
margin: 6px 4px;
|
||||
}
|
||||
|
||||
.id {
|
||||
flex: 0 20px;
|
||||
margin: 1px 6px 1px 1px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1;
|
||||
word-break: normal;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.votes {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.voted {
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.catName {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.category {
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styles from './todoSidebar.module.css'
|
||||
|
||||
export default function Todos({
|
||||
card,
|
||||
userId,
|
||||
categories,
|
||||
columns,
|
||||
voteOn,
|
||||
namedGroups,
|
||||
}) {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
category,
|
||||
points,
|
||||
votes,
|
||||
id,
|
||||
group,
|
||||
gitlink,
|
||||
} = card
|
||||
const voteable = columns[card.state].clickable
|
||||
|
||||
// TODO: hide vote button if not voteable
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>
|
||||
<span className={styles.id}>#{id}</span>
|
||||
<div className={'subtitle'} style={{ textAlign: 'left' }}>
|
||||
{name}
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: categories[category].color,
|
||||
color: 'white',
|
||||
fontSize: '12px',
|
||||
borderRadius: '2px',
|
||||
padding: '3px',
|
||||
}}
|
||||
>
|
||||
{categories[category].name}
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
{group && (
|
||||
<div className={styles.row}>
|
||||
<div style={{ color: group }}>Csoport:</div>
|
||||
<div style={{ color: group }}>
|
||||
{namedGroups[group] ? namedGroups[group].name : group}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.row} title={'Minél több a pont, annál nehezebb'}>
|
||||
<div>Nehézség:</div>
|
||||
<div>{points}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.row} ${votes.includes(userId) &&
|
||||
styles.votedtext}`}
|
||||
title={'Hány felhasználó szavazott a feladatra'}
|
||||
>
|
||||
<div>Szavazatok:</div>
|
||||
<div>{votes.length}</div>
|
||||
</div>
|
||||
{gitlink && (
|
||||
<center>
|
||||
<div>
|
||||
<a
|
||||
href={gitlink}
|
||||
style={{ textDecoration: 'none' }}
|
||||
target={'_blank'}
|
||||
rel={'noreferrer'}
|
||||
>
|
||||
Git link
|
||||
</a>
|
||||
</div>
|
||||
</center>
|
||||
)}
|
||||
<hr />
|
||||
{description && (
|
||||
<>
|
||||
<div className={'subtitle'} style={{ textAlign: 'left' }}>
|
||||
Leírás
|
||||
</div>
|
||||
<div
|
||||
className={styles.description}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
<div className={styles.category} />
|
||||
{voteable && (
|
||||
<center>
|
||||
<div className={'buttonContainer'} style={{ width: '40%' }}>
|
||||
<div
|
||||
className={`${styles.button} ${votes.includes(userId) &&
|
||||
styles.voted}`}
|
||||
onClick={() => {
|
||||
voteOn(card)
|
||||
}}
|
||||
>
|
||||
{votes.includes(userId) ? 'Szavazat visszavonása' : 'Szavazás'}
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.container > hr {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--text-color);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--text-color);
|
||||
font-size: 20px;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
.name {
|
||||
}
|
||||
|
||||
.category {
|
||||
word-break: break-all;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.id {
|
||||
font-size: 20px;
|
||||
margin: 0px 3px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.description {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
max-height: calc(38vh);
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 2px 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: 2px solid var(--text-color);
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.votedtext {
|
||||
color: #cf9;
|
||||
}
|
||||
.voted {
|
||||
border: 2px solid #cf9;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import TodoRow from './todoRow'
|
||||
|
||||
import styles from './todoTable.module.css'
|
||||
|
||||
export default function TodoBoard(props) {
|
||||
const { tables, cards, userId, categories, onClick, selectedGroup } = props
|
||||
|
||||
return (
|
||||
<div className={styles.tableContainer}>
|
||||
<div className={styles.table}>
|
||||
{Object.keys(tables).map((key) => {
|
||||
const table = tables[key]
|
||||
|
||||
const tableCards = cards.filter((card) => {
|
||||
return card.state === key
|
||||
})
|
||||
|
||||
const tableCardsToShow = tableCards.filter((card) => {
|
||||
const shouldHide =
|
||||
card.state !== key ||
|
||||
(selectedGroup !== null &&
|
||||
selectedGroup !== 'uncat' &&
|
||||
card.group !== selectedGroup) ||
|
||||
(selectedGroup === 'uncat' && card.group !== undefined)
|
||||
|
||||
return !shouldHide
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styles.container} key={key}>
|
||||
<div className={styles.title}>{table.name}</div>
|
||||
<div className={styles.scroll}>
|
||||
{tableCardsToShow.length === 0 && (
|
||||
<div className={styles.empty}>Üres</div>
|
||||
)}
|
||||
{tableCardsToShow.map((card, i) => {
|
||||
return (
|
||||
<TodoRow
|
||||
onClick={onClick}
|
||||
key={i}
|
||||
type={key}
|
||||
rowData={card}
|
||||
userId={userId}
|
||||
categories={categories}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
.tableContainer {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
.table {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex-direction: column;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0px;
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
import LoadingIndicator from '../LoadingIndicator'
|
||||
import TodoBoard from './todoBoard'
|
||||
import TodoTable from './todoTable'
|
||||
import TodoSidebar from './todoSidebar'
|
||||
import Modal from '../modal'
|
||||
|
||||
import styles from './todo.module.css'
|
||||
import constants from '../../constants.json'
|
||||
|
||||
const byVotes = (a, b) => {
|
||||
return b.votes.length - a.votes.length
|
||||
}
|
||||
|
||||
export default function Todos({ globalState, setGlobalState }) {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [columns, setColumns] = useState(null)
|
||||
const [cards, setCards] = useState(null)
|
||||
const [categories, setCategories] = useState(null)
|
||||
const [userId, setUserId] = useState(null)
|
||||
const [sidebarCard, setSidebarCard] = useState(null)
|
||||
const [namedGroups, setGroups] = useState(null)
|
||||
const [selectedGroup, setSelectedGroup] = useState(null)
|
||||
|
||||
const [activeGroups, setActiveGroups] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState.todos) {
|
||||
setTodos(globalState.todos)
|
||||
} else {
|
||||
fetch(`${constants.apiUrl}todos`, {
|
||||
credentials: 'include',
|
||||
})
|
||||
.then((resp) => {
|
||||
return resp.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setTodos(data)
|
||||
setGlobalState({
|
||||
todos: data,
|
||||
})
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setTodos = (data) => {
|
||||
setCategories(data.todos.categories)
|
||||
setGroups(data.todos.groups)
|
||||
setUserId(data.userId)
|
||||
|
||||
setCards(data.todos.cards)
|
||||
setColumns(data.todos.columns)
|
||||
setLoaded(true)
|
||||
|
||||
const notTables = Object.keys(data.todos.columns).reduce((acc, key) => {
|
||||
const col = data.todos.columns[key]
|
||||
if (col.type !== 'table') {
|
||||
acc.push(key)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
setActiveGroups(
|
||||
data.todos.cards.reduce((acc, card) => {
|
||||
if (card.group && notTables.includes(card.state)) {
|
||||
if (!acc.includes(card.group)) acc.push(card.group)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
)
|
||||
}
|
||||
|
||||
const onClick = (card) => {
|
||||
setSidebarCard(card.id)
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
return <LoadingIndicator />
|
||||
}
|
||||
|
||||
let bCards = []
|
||||
let tCards = []
|
||||
cards.forEach((card) => {
|
||||
if (columns[card.state].type === 'table') {
|
||||
tCards.push(card)
|
||||
} else {
|
||||
bCards.push(card)
|
||||
}
|
||||
})
|
||||
bCards = bCards.sort(byVotes)
|
||||
tCards = tCards.sort(byVotes)
|
||||
|
||||
let cols = {}
|
||||
let tables = {}
|
||||
Object.keys(columns).forEach((key) => {
|
||||
const col = columns[key]
|
||||
if (col.type !== 'table') {
|
||||
cols = {
|
||||
...cols,
|
||||
[key]: col,
|
||||
}
|
||||
} else {
|
||||
tables = {
|
||||
...tables,
|
||||
[key]: col,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const groups = cards.reduce((acc, card) => {
|
||||
if (!acc.includes(card.group) && card.group) {
|
||||
acc.push(card.group)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const renderGrouper = () => {
|
||||
const sg = namedGroups[selectedGroup]
|
||||
return (
|
||||
<>
|
||||
<div className={'buttonContainer'}>
|
||||
{groups.map((group) => {
|
||||
const namedGroup = namedGroups[group]
|
||||
const shouldSkip = activeGroups && !activeGroups.includes(group)
|
||||
|
||||
if (shouldSkip) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${'buttonContainer'}, ${styles.todoButtons} ${
|
||||
group === selectedGroup && 'activeButton'
|
||||
}`}
|
||||
key={group}
|
||||
onClick={() => {
|
||||
setSelectedGroup(group)
|
||||
}}
|
||||
>
|
||||
{namedGroup ? namedGroup.name : group}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div
|
||||
className={`${'buttonContainer'}, ${styles.todoButtons} ${
|
||||
'uncat' === selectedGroup && 'activeButton'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedGroup('uncat')
|
||||
}}
|
||||
>
|
||||
{'Kategorizálatlan'}
|
||||
</div>
|
||||
<div
|
||||
className={`${'buttonContainer'}, ${styles.todoButtons} ${
|
||||
null === selectedGroup && 'activeButton'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedGroup(null)
|
||||
}}
|
||||
>
|
||||
{'Összes'}
|
||||
</div>
|
||||
</div>
|
||||
{sg && sg.description ? (
|
||||
<div className={styles.groupDescription}>{sg.description}</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sidebarCard && (
|
||||
<Modal
|
||||
closeClick={() => {
|
||||
setSidebarCard(null)
|
||||
}}
|
||||
>
|
||||
<TodoSidebar
|
||||
card={cards.find((card) => {
|
||||
return card.id === sidebarCard
|
||||
})}
|
||||
userId={userId}
|
||||
categories={categories}
|
||||
columns={columns}
|
||||
namedGroups={namedGroups}
|
||||
voteOn={(card) => {
|
||||
fetch(`${constants.apiUrl}voteTodo?id=${card.id}`, {
|
||||
credentials: 'include',
|
||||
})
|
||||
.then((resp) => {
|
||||
return resp.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setTodos(data)
|
||||
setGlobalState({
|
||||
todos: data,
|
||||
})
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{renderGrouper()}
|
||||
<div>
|
||||
<TodoBoard
|
||||
columns={cols}
|
||||
cards={bCards}
|
||||
userId={userId}
|
||||
categories={categories}
|
||||
onCardClick={onClick}
|
||||
selectedGroup={selectedGroup}
|
||||
/>
|
||||
<TodoTable
|
||||
tables={tables}
|
||||
cards={tCards}
|
||||
userId={userId}
|
||||
categories={categories}
|
||||
onClick={onClick}
|
||||
selectedGroup={selectedGroup}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue