Compare commits
29 Commits
fccedac8f2
...
a77a31d01c
Author | SHA1 | Date | |
---|---|---|---|
a77a31d01c | |||
970a948cec | |||
4dbcc540d6 | |||
836ec0aef2 | |||
6c4da8c181 | |||
aa1c1c3737 | |||
00a9a82bcb | |||
01f82b1365 | |||
afd2361689 | |||
cfeac6b920 | |||
d26a91b65c | |||
0b3d809e3b | |||
7d7b6d88b9 | |||
38735c7982 | |||
671d0ed650 | |||
d645964266 | |||
3610415657 | |||
69f65846c2 | |||
74e0ef19db | |||
decd4faf22 | |||
1abe2ddfaf | |||
b8b4c31b45 | |||
e5d0e50102 | |||
098e865e91 | |||
3fcd4b4154 | |||
8211ed629c | |||
5a968ff026 | |||
60ecbd331a | |||
916796ef8e |
@ -14,13 +14,6 @@
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -16,13 +16,13 @@ const App = () => {
|
||||
const renderApp = React.useCallback(() => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<PopUpMessageManager>
|
||||
<BrowserRouter>
|
||||
<PopUpMessageManager>
|
||||
<NavigationManager>
|
||||
<RoutingManager />
|
||||
</NavigationManager>
|
||||
</BrowserRouter>
|
||||
</PopUpMessageManager>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}, []);
|
||||
|
@ -4,7 +4,7 @@ import LoggedBar from '../../components/navigation/LoggedBar';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
import { CHILDREN_WITH_PROPS, IS_MOBILE } from '../../utils/globals';
|
||||
|
||||
const NavigationManager = (props) => {
|
||||
const NavigationManager = ({children, popUpMessageHandler}) => {
|
||||
const [loggedBarVisible, setLoggedBarVisible] = React.useState('100vw');
|
||||
const [loggedBarHover, setLoggedBarHover] = React.useState(false);
|
||||
const [navOptions, setNavOptions] = React.useState(true);
|
||||
@ -27,7 +27,7 @@ const NavigationManager = (props) => {
|
||||
<>
|
||||
<NavBar
|
||||
loggedBarVisibleHandler={loggedBarVisibleHandler}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
navOptions={navOptions}
|
||||
/>
|
||||
{!IS_MOBILE() && (
|
||||
@ -39,7 +39,7 @@ const NavigationManager = (props) => {
|
||||
username={KeyCloakService.getUsername()}
|
||||
/>
|
||||
)}
|
||||
{CHILDREN_WITH_PROPS(props.children, { hideNavOptions, showNavOptions })}
|
||||
{CHILDREN_WITH_PROPS(children, { hideNavOptions, showNavOptions, popUpMessageHandler })}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -5,11 +5,13 @@ import { CHILDREN_WITH_PROPS } from '../../utils/globals';
|
||||
const PopUpMessageManager = (props) => {
|
||||
const [popUpHeader, setPopUpHeader] = React.useState('');
|
||||
const [popUpMessage, setPopUpMessage] = React.useState('');
|
||||
const [borderColor, setBorderColor] = React.useState(null);
|
||||
const [confirmPopUpHandler, setConfirmPopUpHandler] = React.useState(null);
|
||||
|
||||
const popUpMessageHandler = (header, message, confirmHandler) => {
|
||||
const popUpMessageHandler = (header, message, confirmHandler=null, borderColor=null) => {
|
||||
setPopUpHeader(header);
|
||||
setPopUpMessage(message);
|
||||
setBorderColor(borderColor);
|
||||
if (confirmHandler !== null && confirmHandler !== undefined) {
|
||||
setConfirmPopUpHandler(() => confirmHandler());
|
||||
} else {
|
||||
@ -24,6 +26,7 @@ const PopUpMessageManager = (props) => {
|
||||
header={popUpHeader}
|
||||
message={popUpMessage}
|
||||
confirmHandler={confirmPopUpHandler}
|
||||
borderColor={borderColor}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
|
@ -16,15 +16,30 @@ const RoutingManager = (props) => {
|
||||
<Routes>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.LEADERBOARD} />}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.LEADERBOARD}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/leaderboard`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.LEADERBOARD} />}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.LEADERBOARD}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/allentries`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.ALL_ENTRIES} />}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.ALL_ENTRIES}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/readme`}
|
||||
@ -41,13 +56,26 @@ const RoutingManager = (props) => {
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/myentries`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.MY_ENTRIES} />}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.MY_ENTRIES}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/submit`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.SUBMIT} />}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.SUBMIT}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={CHALLENGES_PAGE}
|
||||
element={<Challenges popUpMessageHandler={props.popUpMessageHandler} />}
|
||||
/>
|
||||
<Route path={CHALLENGES_PAGE} element={<Challenges />} />
|
||||
<Route
|
||||
path={POLICY_PRIVACY_PAGE}
|
||||
element={
|
||||
@ -74,8 +102,18 @@ const RoutingManager = (props) => {
|
||||
/>
|
||||
{KeyCloakService.isLoggedIn() ? (
|
||||
<>
|
||||
<Route exact path="/" element={<Challenges />} />
|
||||
<Route element={<Challenges />} />
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
element={
|
||||
<Challenges popUpMessageHandler={props.popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Challenges popUpMessageHandler={props.popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
@ -38,7 +38,12 @@ const challengeSubmission = (
|
||||
.then((data) => {
|
||||
dispatch({ type: SUBMIT_ACTION.TOGGLE_SUBMISSION_LOADING });
|
||||
const processUrl = API.replace('/api', '');
|
||||
window.location.replace(`${processUrl}/open-view-progress/${data}#form`);
|
||||
if (Number.isInteger(Number(data))) {
|
||||
console.log(`${processUrl}/open-view-progress/${data}#form`);
|
||||
window.location.replace(
|
||||
`${processUrl}/open-view-progress/${data}#form`
|
||||
);
|
||||
}
|
||||
// console.log(data);
|
||||
|
||||
// fetch(`${API}/view-progress-with-web-sockets/${data}`)
|
||||
|
@ -1,17 +1,42 @@
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
import { API } from '../utils/globals';
|
||||
import theme from '../utils/theme';
|
||||
|
||||
const deleteSubmission = (submissionId) => {
|
||||
fetch(`${API}/delete-submission/${submissionId}`, {
|
||||
const deleteSubmission = async (
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
fetch(`${API}/delete-submission/${item.id}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
Authorization: `Bearer ${KeyCloakService.getToken()}`,
|
||||
},
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => resp.text())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data === 'deleted') {
|
||||
let newDeletedItems = deletedItems.slice();
|
||||
newDeletedItems.push(item);
|
||||
setDeletedItems(newDeletedItems);
|
||||
popUpMessageHandler('Complete', `Submission "${item.id}" deleted`);
|
||||
} else if (data.includes('<!doctype html>') && data.includes('Login')) {
|
||||
popUpMessageHandler(
|
||||
'Error',
|
||||
'You have to be login in to edit submission!',
|
||||
null,
|
||||
theme.colors.red
|
||||
);
|
||||
} else {
|
||||
popUpMessageHandler(
|
||||
'Error',
|
||||
"You can't delete this submission!",
|
||||
null,
|
||||
theme.colors.red
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
47
src/api/editSubmission.js
Normal file
47
src/api/editSubmission.js
Normal file
@ -0,0 +1,47 @@
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
import { API } from '../utils/globals';
|
||||
import theme from '../utils/theme';
|
||||
|
||||
const editSubmission = async (
|
||||
submisssion,
|
||||
tags,
|
||||
description,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
tags = tags.replaceAll(',', '%2C');
|
||||
fetch(`${API}/edit-submission/${submisssion}/${tags}/${description}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
Authorization: `Bearer ${KeyCloakService.getToken()}`,
|
||||
},
|
||||
})
|
||||
.then((resp) => resp.text())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data === 'Submission changed') {
|
||||
popUpMessageHandler(
|
||||
'Submission changed!',
|
||||
`Submission ${submisssion} edited`,
|
||||
null,
|
||||
theme.colors.green
|
||||
);
|
||||
} else if (data === 'Only owner can edit a submission!') {
|
||||
popUpMessageHandler('Error', data, null, theme.colors.red);
|
||||
} else if (data.includes('<!doctype html>') && data.includes('Login')) {
|
||||
popUpMessageHandler(
|
||||
'Error',
|
||||
'You have to be login in to edit submission!',
|
||||
null,
|
||||
theme.colors.red
|
||||
);
|
||||
} else {
|
||||
if (data.length > 650) {
|
||||
data = `${data.slice(0, 650)}...`;
|
||||
}
|
||||
popUpMessageHandler('Error', data, null, theme.colors.red);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default editSubmission;
|
@ -1,19 +1,62 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getChallengeLeaderboard = async (
|
||||
setDataState,
|
||||
const getChallengeLeaderboard = (
|
||||
endpoint,
|
||||
challengeName,
|
||||
setLoading
|
||||
setDataStates,
|
||||
setLoadingState,
|
||||
setScoreSorted
|
||||
) => {
|
||||
await fetch(`${API}/leaderboard/${challengeName}`)
|
||||
fetch(`${API}/${endpoint}/${challengeName}`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setDataState(data.entries);
|
||||
});
|
||||
|
||||
if (setLoading) {
|
||||
setLoading(false);
|
||||
let item = {};
|
||||
let result = [];
|
||||
let initSetScoreSorted = [];
|
||||
let tests = data.tests;
|
||||
for (let submission of data.entries) {
|
||||
for (let evaluation of submission.evaluations) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${evaluation.test.metric}.${evaluation.test.name}`]:
|
||||
evaluation.score,
|
||||
},
|
||||
};
|
||||
}
|
||||
for (let test of tests) {
|
||||
if (!item.evaluations) {
|
||||
item.evaluations = {};
|
||||
}
|
||||
if (!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: -999999999,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
item = {
|
||||
...item.evaluations,
|
||||
...submission,
|
||||
};
|
||||
result.push(item);
|
||||
item = {};
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let _ of tests) {
|
||||
initSetScoreSorted.push(false);
|
||||
}
|
||||
for (let setDataState of setDataStates) setDataState(result);
|
||||
if (setScoreSorted) setScoreSorted(initSetScoreSorted);
|
||||
if (setLoadingState) setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
export default getChallengeLeaderboard;
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getAllEntries = (
|
||||
const getEntries = (
|
||||
endpoint,
|
||||
challengeName,
|
||||
setDataOriginalState,
|
||||
setDataState,
|
||||
setDataStates,
|
||||
setLoadingState,
|
||||
setScoreSorted
|
||||
) => {
|
||||
fetch(`${API}/challenge-all-submissions/${challengeName}`, {
|
||||
fetch(`${API}/${endpoint}/${challengeName}`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (setDataOriginalState) setDataOriginalState(data);
|
||||
let item = {};
|
||||
let result = [];
|
||||
let initSetScoreSorted = [];
|
||||
@ -30,37 +29,35 @@ const getAllEntries = (
|
||||
};
|
||||
}
|
||||
for (let test of tests) {
|
||||
if (item.evaluations) {
|
||||
if (
|
||||
!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)
|
||||
) {
|
||||
if (!item.evaluations) {
|
||||
item.evaluations = {};
|
||||
}
|
||||
if (!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: '-1',
|
||||
[`${test.metric}.${test.name}`]: -999999999,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
item = {
|
||||
...item,
|
||||
id: submission.id,
|
||||
submitter: submission.submitter,
|
||||
when: submission.when,
|
||||
...item.evaluations,
|
||||
...submission,
|
||||
};
|
||||
result.push(item);
|
||||
item = {};
|
||||
}
|
||||
result = result.filter((item) => !item.deleted);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let _ of tests) {
|
||||
initSetScoreSorted.push(false);
|
||||
}
|
||||
setDataState(result);
|
||||
for (let setDataState of setDataStates) setDataState(result);
|
||||
if (setScoreSorted) setScoreSorted(initSetScoreSorted);
|
||||
if (setLoadingState) setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
export default getAllEntries;
|
||||
export default getEntries;
|
@ -1,15 +1,14 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getFullUser = (setDataState, setLoadingState) => {
|
||||
const getFullUser = async (setDataState, setLoadingState) => {
|
||||
fetch(`${API}/full-user-info`, {
|
||||
headers: {'Authorization': `Bearer ${KeyCloakService.getToken()}`}
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setDataState(data);
|
||||
if (setLoadingState)
|
||||
setLoadingState(false);
|
||||
if (setLoadingState) setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getMyEntries = (
|
||||
challengeName,
|
||||
setDataOriginalState,
|
||||
setDataStateForSearch,
|
||||
setDataState,
|
||||
setLoadingState,
|
||||
setScoreSorted
|
||||
) => {
|
||||
fetch(`${API}/challenge-my-submissions/${challengeName}`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setDataOriginalState(data);
|
||||
let item = {};
|
||||
let result = [];
|
||||
let initSetScoreSorted = [];
|
||||
let tests = data.tests;
|
||||
for (let submission of data.submissions) {
|
||||
for (let evaluation of submission.evaluations) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${evaluation.test.metric}.${evaluation.test.name}`]:
|
||||
evaluation.score,
|
||||
},
|
||||
};
|
||||
}
|
||||
for (let test of tests) {
|
||||
if (item.evaluations) {
|
||||
if (
|
||||
!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)
|
||||
) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: '-1',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
item = {
|
||||
...item,
|
||||
id: submission.id,
|
||||
submitter: submission.submitter,
|
||||
when: submission.when,
|
||||
};
|
||||
result.push(item);
|
||||
item = {};
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let _ of tests) {
|
||||
initSetScoreSorted.push(false);
|
||||
}
|
||||
setScoreSorted(initSetScoreSorted);
|
||||
setDataStateForSearch(result);
|
||||
setDataState(result);
|
||||
setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
export default getMyEntries;
|
@ -1,11 +1,10 @@
|
||||
import SUBMIT_ACTION from '../pages/Submit/model/SubmitActionEnum';
|
||||
import { API } from '../utils/globals';
|
||||
|
||||
const getTags = (dispatch) => {
|
||||
const getTags = (setState) => {
|
||||
fetch(`${API}/list-tags`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
dispatch({ type: SUBMIT_ACTION.LOAD_TAGS, payload: data });
|
||||
setState(data);
|
||||
});
|
||||
};
|
||||
|
||||
|
3
src/assets/delete_ico.svg
Normal file
3
src/assets/delete_ico.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.3571 2.24695C31.9857 2.24695 32.5 2.69695 32.5 3.24695V5.24695C32.5 5.79695 31.9857 6.24695 31.3571 6.24695H1.64286C1.01429 6.24695 0.5 5.79695 0.5 5.24695V3.24695C0.5 2.69695 1.01429 2.24695 1.64286 2.24695H10.2143L10.8857 1.0782C11.1429 0.621948 11.8214 0.246948 12.4071 0.246948H12.4143H20.5786C21.1643 0.246948 21.85 0.621948 22.1143 1.0782L22.7857 2.24695H31.3571ZM4.3 29.4344L2.78571 8.24695H30.2143L28.7 29.4344C28.5857 30.9844 27.0571 32.2469 25.2786 32.2469H7.72143C5.94286 32.2469 4.41429 30.9844 4.3 29.4344Z" fill="#343434"/>
|
||||
</svg>
|
After Width: | Height: | Size: 654 B |
@ -63,7 +63,7 @@ const Pager = (props) => {
|
||||
as="a"
|
||||
href="#start"
|
||||
src={polygon}
|
||||
onClick={() => PREVIOUS_PAGE(props.pageNr, props.setPage)}
|
||||
onClick={() => PREVIOUS_PAGE(props.pageNr, props.setPageNr)}
|
||||
size="cover"
|
||||
backgroundColor={leftArrowVisible()}
|
||||
/>
|
||||
@ -76,7 +76,7 @@ const Pager = (props) => {
|
||||
as="a"
|
||||
href="#start"
|
||||
src={polygon}
|
||||
onClick={() => NEXT_PAGE(props.elements, props.pageNr, props.setPage)}
|
||||
onClick={() => NEXT_PAGE(props.elements, props.pageNr, props.setPageNr)}
|
||||
size="cover"
|
||||
backgroundColor={rightArrowVisible()}
|
||||
/>
|
||||
|
@ -9,12 +9,12 @@ const PopUpStyle = styled(FlexColumn)`
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: ${({ theme }) => theme.colors.dark01};
|
||||
background-color: ${({ theme, backgroundColor }) =>
|
||||
backgroundColor ? backgroundColor : theme.colors.dark01};
|
||||
|
||||
.PopUpStyle__body {
|
||||
width: ${({ width }) => (width ? width : '60%')};
|
||||
height: ${({ height }) => (height ? height : '50%')};
|
||||
min-height: ${({ minHeight }) => (minHeight ? minHeight : '50%')};
|
||||
padding: ${({ padding }) => (padding ? padding : '48px')};
|
||||
margin: ${({ margin }) => (margin ? margin : '0')};
|
||||
border-radius: 12px;
|
||||
@ -32,6 +32,7 @@ const PopUp = (props) => {
|
||||
|
||||
return (
|
||||
<PopUpStyle
|
||||
backgroundColor={props.backgroundColor}
|
||||
padding={props.padding}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
|
@ -28,13 +28,13 @@ const PopupMessage = (props) => {
|
||||
borderRadius="12px"
|
||||
backgroundColor={theme.colors.white}
|
||||
padding="56px"
|
||||
border={`4px solid ${theme.colors.green}`}
|
||||
border={`4px solid ${props.borderColor ? props.borderColor : theme.colors.green}`}
|
||||
>
|
||||
<FlexColumn gap="48px" margin="0 0 48px 0">
|
||||
<H3>{props.header}</H3>
|
||||
<Body>{props.message}</Body>
|
||||
</FlexColumn>
|
||||
<Button handler={confirmPopUp}>Ok</Button>
|
||||
<Button backgroundColor={props.borderColor ? props.borderColor : theme.colors.green} handler={confirmPopUp}>Ok</Button>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
|
@ -18,6 +18,7 @@ const SubmitInput = (props) => {
|
||||
height="36px"
|
||||
border={`1px solid ${theme.colors.dark}`}
|
||||
shadow={theme.shadow}
|
||||
defaultValue={props.defaultValue}
|
||||
onChange={(e) => props.handler(e.target.value)}
|
||||
padding="4px"
|
||||
/>
|
||||
|
@ -1,222 +1,79 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
Grid,
|
||||
} from '../../../utils/containers';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../../utils/theme';
|
||||
import { ELEMENTS_PER_PAGE, IS_MOBILE } from '../../../utils/globals';
|
||||
import { Body, Medium } from '../../../utils/fonts';
|
||||
import ColumnFilterIcon from '../ColumnFilterIcon';
|
||||
// import deleteSubmission from '../../api/deleteSubmission';
|
||||
import TableStyle from './styles/TableStyle';
|
||||
import TableLine from './styles/TableLine';
|
||||
import MobileTableStyle from './styles/MobileTableStyle';
|
||||
import MobileTable from './components/MobileTable';
|
||||
import DesktopTable from './components/DesktopTable';
|
||||
import EditPopUp from './components/EditPopUp';
|
||||
import DeletePopUp from './components/DeletePopUp';
|
||||
|
||||
const Table = (props) => {
|
||||
const Table = ({
|
||||
items,
|
||||
orderedKeys,
|
||||
popUpMessageHandler,
|
||||
sortByUpdate,
|
||||
profileInfo,
|
||||
rowFooter = true,
|
||||
}) => {
|
||||
const [, updateState] = React.useState();
|
||||
const forceUpdate = React.useCallback(() => updateState({}), []);
|
||||
const [activeIcon, setActiveIcon] = React.useState(null);
|
||||
const [rotateActiveIcon, setRotateActiveIcon] = React.useState(false);
|
||||
|
||||
const metricsRender = (elem) => {
|
||||
if (!props.iterableColumnElement) return <></>;
|
||||
if (Array.isArray(elem[props.iterableColumnElement.name]))
|
||||
elem = elem[props.iterableColumnElement.name];
|
||||
else {
|
||||
let newElem = [];
|
||||
for (let metric of props.possibleMetrics) {
|
||||
if (Object.hasOwn(elem, props.iterableColumnElement.name)) {
|
||||
if (elem[props.iterableColumnElement.name][metric] === '-1')
|
||||
newElem.push('N/A');
|
||||
else newElem.push(elem[props.iterableColumnElement.name][metric]);
|
||||
} else {
|
||||
newElem.push('N/A');
|
||||
}
|
||||
}
|
||||
elem = newElem;
|
||||
}
|
||||
let indexModificator = 2;
|
||||
if (props.tableType === 'leaderboard') indexModificator = 4;
|
||||
if (props.tableType === 'allEntries') indexModificator = 3;
|
||||
|
||||
return elem.map((iterableElem, i) => {
|
||||
return (
|
||||
<Body
|
||||
key={`metric-result-${i}`}
|
||||
as="td"
|
||||
order={props.iterableColumnElement.order}
|
||||
textAlign={props.iterableColumnElement.align}
|
||||
minWidth="88px"
|
||||
margin="auto 0"
|
||||
overflowWrap="anywhere"
|
||||
>
|
||||
{IS_MOBILE() && (
|
||||
<Container className="mobile-table-header">
|
||||
{props.headerElements[indexModificator + i]}
|
||||
</Container>
|
||||
)}
|
||||
{props.iterableColumnElement.format
|
||||
? props.iterableColumnElement.format(iterableElem)
|
||||
: iterableElem}
|
||||
</Body>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const rowRender = (elem) => {
|
||||
let RowStyle = Body;
|
||||
console.log(elem);
|
||||
if (elem.submitter === props.user) RowStyle = Medium;
|
||||
return props.staticColumnElements.map((elemName, i) => {
|
||||
return (
|
||||
<RowStyle
|
||||
key={`leaderboard-static-elemName-${i}-${elem[elemName.name]}`}
|
||||
as="td"
|
||||
order={elemName.order}
|
||||
textAlign={elemName.align}
|
||||
margin="auto 0"
|
||||
minWidth="88px"
|
||||
overflowWrap="anywhere"
|
||||
cursor="pointer"
|
||||
// onClick={props.myEntries && (() => deleteSubmission(elem.id))}
|
||||
>
|
||||
{IS_MOBILE() && (
|
||||
<Container className="mobile-table-header">
|
||||
{props.headerElements[i]}
|
||||
</Container>
|
||||
)}
|
||||
{elemName.format
|
||||
? elemName.format(elem[elemName.name])
|
||||
: elem[elemName.name]}
|
||||
</RowStyle>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
const n = (props.pageNr - 1) * (ELEMENTS_PER_PAGE * 2);
|
||||
let elementsToMap = props.elements.slice(n, n + ELEMENTS_PER_PAGE * 2);
|
||||
if (elementsToMap.length > 0) {
|
||||
return (
|
||||
<TableStyle as="table" margin="32px 0 72px 0" width="100%">
|
||||
<FlexColumn as="tbody" width="100%">
|
||||
<Grid
|
||||
as="tr"
|
||||
gridGap="20px"
|
||||
position="relative"
|
||||
width="100%"
|
||||
padding="0 6px"
|
||||
minHeight="44px"
|
||||
margin="0 0 6px 0"
|
||||
gridTemplateColumns={props.gridTemplateColumns}
|
||||
>
|
||||
{props.headerElements.map((elem, i) => {
|
||||
return (
|
||||
<FlexRow
|
||||
key={`table-header-${i}`}
|
||||
alignmentX="flex-start"
|
||||
as="td"
|
||||
cursor="pointer"
|
||||
onClick={() => {
|
||||
if (activeIcon === i) {
|
||||
let newRotateActiveIcon = !rotateActiveIcon;
|
||||
setRotateActiveIcon(newRotateActiveIcon);
|
||||
} else {
|
||||
setRotateActiveIcon(false);
|
||||
}
|
||||
setActiveIcon(i);
|
||||
props.sortByUpdate(elem, i);
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
<Medium
|
||||
cursor={elem !== '#' ? 'pointer' : ''}
|
||||
textAlign={elem === 'when' ? 'right' : 'left'}
|
||||
width={elem === 'when' ? '100%' : 'auto'}
|
||||
padding="0 4px 0 0"
|
||||
overflowWrap="anywhere"
|
||||
minWidth="72px"
|
||||
>
|
||||
{elem.replace('.', ' ')}
|
||||
</Medium>
|
||||
{elem !== '#' && (
|
||||
<ColumnFilterIcon
|
||||
cursor="pointer"
|
||||
index={i}
|
||||
active={activeIcon}
|
||||
rotateIcon={rotateActiveIcon}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
})}
|
||||
<TableLine
|
||||
height="2px"
|
||||
top="calc(100% + 2px)"
|
||||
as="td"
|
||||
shadow={theme.shadow}
|
||||
/>
|
||||
</Grid>
|
||||
{elementsToMap.map((elem, index) => {
|
||||
return (
|
||||
<Grid
|
||||
as="tr"
|
||||
key={`leaderboard-row-${index}`}
|
||||
backgroundColor={
|
||||
index % 2 === 1 ? theme.colors.dark01 : 'transparent'
|
||||
}
|
||||
gridTemplateColumns={props.gridTemplateColumns}
|
||||
gridGap="20px"
|
||||
position="relative"
|
||||
width="100%"
|
||||
padding="4px"
|
||||
minHeight="48px"
|
||||
>
|
||||
{rowRender(elem)}
|
||||
{props.headerElements ? metricsRender(elem) : ''}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</FlexColumn>
|
||||
</TableStyle>
|
||||
);
|
||||
}
|
||||
return <Medium margin="72px 0">No results ;c</Medium>;
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
const n = (props.pageNr - 1) * ELEMENTS_PER_PAGE;
|
||||
let elementsToMap = props.elements.slice(n, n + ELEMENTS_PER_PAGE);
|
||||
if (elementsToMap.length > 0) {
|
||||
return (
|
||||
<MobileTableStyle
|
||||
as="table"
|
||||
staticColumnElements={props.staticColumnElements}
|
||||
headerElements={props.headerElements}
|
||||
>
|
||||
<Container as="tbody">
|
||||
{elementsToMap.map((elem, index) => {
|
||||
return (
|
||||
<Grid as="tr" key={`leaderboard-row-${index}`}>
|
||||
{rowRender(elem)}
|
||||
{props.headerElements ? metricsRender(elem) : ''}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Container>
|
||||
</MobileTableStyle>
|
||||
);
|
||||
}
|
||||
return <Medium margin="72px 0">No results ;c</Medium>;
|
||||
};
|
||||
const tableUpdate = React.useCallback(() => updateState({}), []);
|
||||
const [deletedItems, setDeletedItems] = React.useState([]);
|
||||
const [deletePopUp, setDeletePopUp] = React.useState(false);
|
||||
const [editPopUp, setEditPopUp] = React.useState(false);
|
||||
const [itemToHandle, setItemToHandle] = React.useState(null);
|
||||
const itemsToRender = items.filter((item) => !deletedItems.includes(item));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
<DeletePopUp
|
||||
item={itemToHandle}
|
||||
setDeletePopUp={setDeletePopUp}
|
||||
deletePopUp={deletePopUp}
|
||||
setDeletedItems={setDeletedItems}
|
||||
deletedItems={deletedItems}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
<EditPopUp
|
||||
item={itemToHandle}
|
||||
setEditPopUp={setEditPopUp}
|
||||
editPopUp={editPopUp}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
<Media query={theme.mobile}>
|
||||
<MobileTable
|
||||
elements={itemsToRender}
|
||||
deletePopUp={deletePopUp}
|
||||
orderedKeys={orderedKeys}
|
||||
rowFooter={rowFooter}
|
||||
deletedItems={deletedItems}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
itemToHandle={itemToHandle}
|
||||
sortByUpdate={sortByUpdate}
|
||||
profileInfo={profileInfo}
|
||||
tableUpdate={tableUpdate}
|
||||
setItemToHandle={setItemToHandle}
|
||||
setEditPopUp={setEditPopUp}
|
||||
setDeletePopUp={setDeletePopUp}
|
||||
setDeletedItems={setDeletedItems}
|
||||
/>
|
||||
</Media>
|
||||
<Media query={theme.desktop}>
|
||||
<DesktopTable
|
||||
elements={itemsToRender}
|
||||
deletePopUp={deletePopUp}
|
||||
orderedKeys={orderedKeys}
|
||||
rowFooter={rowFooter}
|
||||
deletedItems={deletedItems}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
itemToHandle={itemToHandle}
|
||||
sortByUpdate={sortByUpdate}
|
||||
profileInfo={profileInfo}
|
||||
tableUpdate={tableUpdate}
|
||||
setItemToHandle={setItemToHandle}
|
||||
setEditPopUp={setEditPopUp}
|
||||
setDeletePopUp={setDeletePopUp}
|
||||
setDeletedItems={setDeletedItems}
|
||||
/>
|
||||
</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,74 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import PopUp from '../../../PopUp';
|
||||
import Button from '../../../Button';
|
||||
import { Medium, H3 } from '../../../../../utils/fonts';
|
||||
import { FlexColumn, FlexRow } from '../../../../../utils/containers';
|
||||
import theme from '../../../../../utils/theme';
|
||||
import deleteSubmission from '../../../../../api/deleteSubmission';
|
||||
|
||||
const deleteItem = async (
|
||||
item,
|
||||
setDeletePopUp,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
setDeletePopUp(false);
|
||||
await deleteSubmission(
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
);
|
||||
};
|
||||
|
||||
const DeletePopUp = ({
|
||||
deletePopUp,
|
||||
setDeletePopUp,
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler,
|
||||
}) => {
|
||||
if (deletePopUp) {
|
||||
return createPortal(
|
||||
<PopUp
|
||||
width="30%"
|
||||
height="30vh"
|
||||
padding="32px"
|
||||
backgroundColor={theme.colors.dark003}
|
||||
closeHandler={() => setDeletePopUp(false)}
|
||||
>
|
||||
<FlexColumn width="100%" height="100%" gap="48px">
|
||||
<H3>Warning</H3>
|
||||
<Medium>
|
||||
Are you sure want to delete submission with id: {item.id}?
|
||||
</Medium>
|
||||
<FlexRow gap="48px">
|
||||
<Button
|
||||
handler={() =>
|
||||
deleteItem(
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
)
|
||||
}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
handler={() => setDeletePopUp(false)}
|
||||
backgroundColor={theme.colors.dark}
|
||||
>
|
||||
No
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</PopUp>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default DeletePopUp;
|
@ -0,0 +1 @@
|
||||
export { default } from './DeletePopUp';
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import TableStyle from '../../styles/TableStyle';
|
||||
import TableHeader from '../TableHeader/TableHeader';
|
||||
import TableRowItems from '../TableRowItems/TableRowItems';
|
||||
import TableRowFooter from '../TableRowFooter/TableRowFooter';
|
||||
import RowsBackgroundStyle from '../../styles/RowsBackgroundStyle';
|
||||
|
||||
const DesktopTable = (props) => {
|
||||
return (
|
||||
<TableStyle rowFooter={props.rowFooter}>
|
||||
<tbody>
|
||||
<TableHeader
|
||||
orderedKeys={props.orderedKeys}
|
||||
sortByUpdate={props.sortByUpdate}
|
||||
tableUpdate={props.tableUpdate}
|
||||
/>
|
||||
{props.elements.map((item, i) => {
|
||||
return (
|
||||
<tr key={`table-row-${i}`} className="TableStyle__tr">
|
||||
<TableRowItems
|
||||
orderedKeys={props.orderedKeys}
|
||||
item={item}
|
||||
i={i}
|
||||
/>
|
||||
<TableRowFooter
|
||||
deleteItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setDeletePopUp(true);
|
||||
}}
|
||||
editItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setEditPopUp(true);
|
||||
}}
|
||||
rowFooter={props.rowFooter}
|
||||
profileInfo={props.profileInfo}
|
||||
item={item}
|
||||
i={i}
|
||||
/>
|
||||
<RowsBackgroundStyle i={i} as="td" />
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default DesktopTable;
|
@ -0,0 +1 @@
|
||||
export { default } from './DesktopTable';
|
105
src/components/generic/Table/components/EditPopUp/EditPopUp.js
Normal file
105
src/components/generic/Table/components/EditPopUp/EditPopUp.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import PopUp from '../../../PopUp';
|
||||
import Button from '../../../Button';
|
||||
import { H3 } from '../../../../../utils/fonts';
|
||||
import { FlexColumn, FlexRow } from '../../../../../utils/containers';
|
||||
import theme from '../../../../../utils/theme';
|
||||
import SubmitInput from '../../../SubmitInput';
|
||||
import TagsChoose from '../../../../../pages/Submit/components/TagsChoose/TagsChoose';
|
||||
import editSubmission from '../../../../../api/editSubmission';
|
||||
import getTags from '../../../../../api/getTags';
|
||||
import React from 'react';
|
||||
|
||||
const editSubmissionHandler = async (
|
||||
item,
|
||||
setEditPopUp,
|
||||
tagsToEdit,
|
||||
description,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
setEditPopUp(false);
|
||||
let tags = '';
|
||||
if (tagsToEdit) {
|
||||
tags = tagsToEdit.join(',');
|
||||
} else {
|
||||
if (item?.tags) {
|
||||
tags = item.tags.map((tag) => tag.name).join(',');
|
||||
}
|
||||
}
|
||||
await editSubmission(item.id, tags, description, popUpMessageHandler);
|
||||
};
|
||||
|
||||
const EditPopUp = ({ editPopUp, setEditPopUp, item, popUpMessageHandler }) => {
|
||||
const [tags, setTags] = React.useState([]);
|
||||
const [tagsToEdit, setTagsToEdit] = React.useState(item?.tags?.slice());
|
||||
const [description, setDescription] = React.useState(
|
||||
item?.description?.slice()
|
||||
);
|
||||
|
||||
React.useMemo(() => {
|
||||
getTags(setTags);
|
||||
}, []);
|
||||
|
||||
if (editPopUp) {
|
||||
return createPortal(
|
||||
<PopUp
|
||||
width="30%"
|
||||
height="50vh"
|
||||
padding="32px"
|
||||
backgroundColor={theme.colors.dark003}
|
||||
closeHandler={() => setEditPopUp(false)}
|
||||
>
|
||||
<FlexColumn width="100%" height="100%" gap="48px">
|
||||
<H3>Editing submission</H3>
|
||||
<SubmitInput
|
||||
label="Description"
|
||||
defaultValue={item.description}
|
||||
handler={(value) => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
<TagsChoose
|
||||
label="Submission tags"
|
||||
updateTags={(submissionTags, globalTags) => {
|
||||
setTagsToEdit(submissionTags);
|
||||
setTags(globalTags);
|
||||
}}
|
||||
tags={tags ? tags : []}
|
||||
submissionTags={tagsToEdit?.length ? tagsToEdit : item.tags}
|
||||
/>
|
||||
<FlexRow gap="48px">
|
||||
<Button
|
||||
width="100px"
|
||||
height="32px"
|
||||
handler={() =>
|
||||
editSubmissionHandler(
|
||||
item,
|
||||
setEditPopUp,
|
||||
tagsToEdit,
|
||||
description,
|
||||
popUpMessageHandler
|
||||
)
|
||||
}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button
|
||||
width="100px"
|
||||
height="32px"
|
||||
handler={() => {
|
||||
setTagsToEdit([]);
|
||||
setEditPopUp(false);
|
||||
}}
|
||||
backgroundColor={theme.colors.dark}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</PopUp>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default EditPopUp;
|
@ -0,0 +1 @@
|
||||
export { default } from './EditPopUp';
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import TableRowItems from '../TableRowItems/TableRowItems';
|
||||
import TableRowFooter from '../TableRowFooter/TableRowFooter';
|
||||
import MobileTableStyle from './MobileTableStyle';
|
||||
|
||||
const MobileTable = (props) => {
|
||||
return (
|
||||
<MobileTableStyle as="table">
|
||||
{props.elements.map((item, i) => {
|
||||
return (
|
||||
<tr key={`table-row-${i}`} className="TableStyle__tr">
|
||||
<TableRowItems orderedKeys={props.orderedKeys} item={item} i={i} />
|
||||
<TableRowFooter
|
||||
deleteItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setDeletePopUp(true);
|
||||
}}
|
||||
editItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setEditPopUp(true);
|
||||
}}
|
||||
rowFooter={props.rowFooter}
|
||||
item={item}
|
||||
i={i}
|
||||
/>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</MobileTableStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileTable;
|
@ -1,51 +1,43 @@
|
||||
import styled from 'styled-components';
|
||||
import { Container } from '../../../../utils/containers';
|
||||
import { Container } from '../../../../../utils/containers';
|
||||
|
||||
const MobileTableStyle = styled(Container)`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 32px 0;
|
||||
|
||||
tr:nth-of-type(odd) {
|
||||
background: ${({ theme }) => theme.colors.dark03};
|
||||
}
|
||||
|
||||
th {
|
||||
background: ${({ theme }) => theme.colors.dark05};
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 6px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.white};
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
display: block;
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
th,
|
||||
tr,
|
||||
td {
|
||||
display: block;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
td {
|
||||
border: none;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.dark01};
|
||||
position: relative;
|
||||
padding-left: 50%;
|
||||
}
|
||||
|
||||
.mobile-table-header {
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
@ -53,6 +45,22 @@ const MobileTableStyle = styled(Container)`
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.TableStyle__row-footer {
|
||||
padding: 0 8px;
|
||||
min-height: 36px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.TableStyle__tag {
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
background-color: ${({ theme }) => theme.colors.green08};
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.TableStyle__tags-container {
|
||||
gap: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default MobileTableStyle;
|
@ -0,0 +1 @@
|
||||
export { default } from './MobileTable';
|
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
import ColumnFilterIcon from '../../../ColumnFilterIcon';
|
||||
|
||||
const TableHeader = (props) => {
|
||||
const [activeIcon, setActiveIcon] = React.useState(null);
|
||||
const [rotateActiveIcon, setRotateActiveIcon] = React.useState(false);
|
||||
|
||||
return (
|
||||
<tr className="TableStyle__tr-header">
|
||||
{props.orderedKeys.map((keyValue, i) => {
|
||||
return (
|
||||
<th
|
||||
key={`table-header-${i}`}
|
||||
className="TableStyle__th"
|
||||
onClick={() => {
|
||||
if (activeIcon === i) {
|
||||
let newRotateActiveIcon = !rotateActiveIcon;
|
||||
setRotateActiveIcon(newRotateActiveIcon);
|
||||
} else {
|
||||
setRotateActiveIcon(false);
|
||||
}
|
||||
setActiveIcon(i);
|
||||
props.sortByUpdate(keyValue);
|
||||
props.tableUpdate();
|
||||
}}
|
||||
>
|
||||
<FlexRow as="span" alignmentX="flex-start" gap="8px" width="100%">
|
||||
{keyValue}
|
||||
<FlexRow
|
||||
as="span"
|
||||
className="TableStyle__sort-button"
|
||||
column={keyValue}
|
||||
>
|
||||
<ColumnFilterIcon
|
||||
index={i}
|
||||
active={activeIcon}
|
||||
rotateIcon={rotateActiveIcon}
|
||||
/>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
<FlexRow className="TableStyle__line" as="td" />
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHeader;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableHeader';
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { FlexRow, Svg } from '../../../../../utils/containers';
|
||||
import theme from '../../../../../utils/theme';
|
||||
|
||||
const TableRowButtons = ({ buttons, i, active, buttonAccessMessage }) => {
|
||||
const getButtonTitle = (defaultTitle) => {
|
||||
if (buttonAccessMessage === 'default') {
|
||||
return defaultTitle;
|
||||
} else return buttonAccessMessage;
|
||||
};
|
||||
|
||||
return (
|
||||
<FlexRow gap="12px" position='relative'>
|
||||
{buttons.map((button, j) => {
|
||||
return (
|
||||
<Svg
|
||||
title={getButtonTitle(button.title)}
|
||||
key={`table-item-button-${i}-${j}`}
|
||||
onClick={active ? button.handler : null}
|
||||
src={button.icon}
|
||||
backgroundColor={active ? theme.colors.dark : theme.colors.dark05}
|
||||
cursor={active ? 'pointer' : 'auto'}
|
||||
size="cover"
|
||||
width="16px"
|
||||
height="16px"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowButtons;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowButtons';
|
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
import TableRowTags from '../TableRowTags/TableRowTags';
|
||||
import TableRowButtons from '../TableRowButtons/TableRowButtons';
|
||||
import pensilIco from '../../../../../assets/pencil_ico.svg';
|
||||
import deleteIco from '../../../../../assets/delete_ico.svg';
|
||||
import KeyCloakService from '../../../../../services/KeyCloakService';
|
||||
|
||||
const TableRowFooter = ({ rowFooter, item, i, deleteItem, editItem, profileInfo }) => {
|
||||
const buttonsActive = () => {
|
||||
if (!KeyCloakService.isLoggedIn()) return false;
|
||||
else if (
|
||||
profileInfo?.preferred_username !== item.submitter &&
|
||||
profileInfo?.name !== item.submitter
|
||||
) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const getButtonAccessMessage = () => {
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
return "You must be logged in to use this option.";
|
||||
} else if (profileInfo?.preferred_username !== item.submitter &&
|
||||
profileInfo?.name !== item.submitter) {
|
||||
return "You don't have permission to use this option.";
|
||||
} return "default";
|
||||
};
|
||||
|
||||
if (rowFooter) {
|
||||
return (
|
||||
<FlexRow className="TableStyle__row-footer">
|
||||
<TableRowTags item={item} i={i} />
|
||||
<TableRowButtons
|
||||
buttons={[
|
||||
{ title: "edit", icon: pensilIco, handler: () => editItem() },
|
||||
{ title: "delete", icon: deleteIco, handler: () => deleteItem() },
|
||||
]}
|
||||
active={buttonsActive()}
|
||||
buttonAccessMessage={getButtonAccessMessage()}
|
||||
i={i}
|
||||
/>
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default TableRowFooter;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowFooter';
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RENDER_WHEN,
|
||||
RENDER_METRIC_VALUE,
|
||||
IS_MOBILE,
|
||||
} from '../../../../../utils/globals';
|
||||
import { Container } from '../../../../../utils/containers';
|
||||
|
||||
const TableRowItems = ({ orderedKeys, item, i }) => {
|
||||
const renderValue = (keyValue) => {
|
||||
if (keyValue === 'when') {
|
||||
return RENDER_WHEN(item[keyValue]);
|
||||
} else {
|
||||
return RENDER_METRIC_VALUE(item[keyValue]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{orderedKeys.map((keyValue, j) => {
|
||||
return (
|
||||
<td key={`table-item-${i}-${j}`} className="TableStyle__td">
|
||||
{IS_MOBILE() && (
|
||||
<Container as="span" className="mobile-table-header">
|
||||
{keyValue}
|
||||
</Container>
|
||||
)}
|
||||
{renderValue(keyValue)}
|
||||
{keyValue === '#' && i + 1}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowItems;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowItems';
|
@ -0,0 +1,12 @@
|
||||
import renderTags from './renderTags';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
|
||||
const TableRowTags = ({ item, i }) => {
|
||||
return (
|
||||
<FlexRow as="span" className="TableStyle__tags-container">
|
||||
{renderTags(item.tags)}
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowTags;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowTags';
|
@ -0,0 +1,15 @@
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
|
||||
const renderTags = (tags, i) => {
|
||||
if (tags && tags.length > 0) {
|
||||
return tags.map((tag, j) => {
|
||||
return (
|
||||
<FlexRow className="TableStyle__tag" key={`submissionTag-${i}-${j}`}>
|
||||
{tag.name}
|
||||
</FlexRow>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default renderTags;
|
14
src/components/generic/Table/styles/RowsBackgroundStyle.js
Normal file
14
src/components/generic/Table/styles/RowsBackgroundStyle.js
Normal file
@ -0,0 +1,14 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexRow } from '../../../../utils/containers';
|
||||
|
||||
const RowsBackgroundStyle = styled(FlexRow)`
|
||||
width: calc(100% + 12px);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -6px;
|
||||
height: 100%;
|
||||
background-color: ${({ theme, i }) =>
|
||||
i % 2 === 0 ? theme.colors.dark01 : 'transparent'};
|
||||
`;
|
||||
|
||||
export default RowsBackgroundStyle;
|
@ -1,14 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexRow } from '../../../../utils/containers';
|
||||
|
||||
const TableLine = styled(FlexRow)`
|
||||
position: absolute;
|
||||
top: ${({ top }) => (top ? top : 'auto')};
|
||||
bottom: ${({ bottom }) => (bottom ? bottom : 'auto')};
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.colors.dark04};
|
||||
height: ${({ height }) => (height ? height : '1px')};
|
||||
`;
|
||||
|
||||
export default TableLine;
|
@ -1,8 +1,66 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexColumn } from '../../../../utils/containers';
|
||||
|
||||
const TableStyle = styled(FlexColumn)`
|
||||
overflow-x: ${({ metrics }) => (metrics > 10 ? 'scroll' : 'auto')};
|
||||
const TableStyle = styled.table`
|
||||
border-collapse: separate;
|
||||
border-spacing: 12px 0;
|
||||
width: 100%;
|
||||
|
||||
.TableStyle__th {
|
||||
cursor: pointer;
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.TableStyle__tr-header {
|
||||
height: 48px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.TableStyle__tr {
|
||||
position: relative;
|
||||
height: ${({ rowFooter }) => (rowFooter ? '72px' : 'auto')};
|
||||
}
|
||||
|
||||
.TableStyle__td {
|
||||
padding: ${({ rowFooter }) => (rowFooter ? '4px 0 32px 0' : '12px 0')};
|
||||
margin: 0 0 0 2px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.TableStyle_line {
|
||||
position: absolute;
|
||||
top: 94%;
|
||||
bottom: ${({ bottom }) => (bottom ? bottom : 'auto')};
|
||||
left: -6px;
|
||||
width: calc(100% + 12px);
|
||||
background-color: ${({ theme }) => theme.colors.dark04};
|
||||
height: 3px;
|
||||
box-shadow: ${({ theme }) => theme.shadow};
|
||||
}
|
||||
|
||||
.TableStyle__tag {
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
background-color: ${({ theme }) => theme.colors.green08};
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.TableStyle__row-footer {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: 55%;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.TableStyle__tags-container {
|
||||
gap: 4px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default TableStyle;
|
||||
|
@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import {Container, FlexColumn, FlexRow, Svg, TransBack} from '../../utils/containers';
|
||||
import {
|
||||
Container,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
Svg,
|
||||
TransBack,
|
||||
} from '../../utils/containers';
|
||||
import { Body, Medium } from '../../utils/fonts';
|
||||
import theme from '../../utils/theme';
|
||||
import userIco from '../../assets/user_ico.svg';
|
||||
@ -48,31 +54,55 @@ const LoggedBarStyle = styled(FlexColumn)`
|
||||
|
||||
const LoggedBar = (props) => {
|
||||
return (
|
||||
<TransBack transition='transform' translateX={props.visible}
|
||||
onClick={props.loggedBarVisibleHandler} animTime='0.2s'>
|
||||
<LoggedBarStyle onMouseEnter={props.loggedBarHoverTrue}
|
||||
onMouseLeave={props.loggedBarHoverFalse}>
|
||||
<FlexRow alignmentX='flex-start' alignmentY='flex-end'
|
||||
gap='16px' width='100%' padding='12px 16px'>
|
||||
<Svg src={userIco} width='32px' height='32px' backgroundColor={theme.colors.dark} size='cover'/>
|
||||
<Medium as='p'>
|
||||
{props.username}
|
||||
</Medium>
|
||||
<TransBack
|
||||
transition="transform"
|
||||
translateX={props.visible}
|
||||
onClick={props.loggedBarVisibleHandler}
|
||||
animTime="0.2s"
|
||||
>
|
||||
<LoggedBarStyle
|
||||
onMouseEnter={props.loggedBarHoverTrue}
|
||||
onMouseLeave={props.loggedBarHoverFalse}
|
||||
>
|
||||
<FlexRow
|
||||
alignmentX="flex-start"
|
||||
alignmentY="flex-end"
|
||||
gap="16px"
|
||||
width="100%"
|
||||
padding="12px 16px"
|
||||
>
|
||||
<Svg
|
||||
src={userIco}
|
||||
width="32px"
|
||||
height="32px"
|
||||
backgroundColor={theme.colors.dark}
|
||||
size="cover"
|
||||
/>
|
||||
<Medium as="p">{props.username}</Medium>
|
||||
</FlexRow>
|
||||
<Container width='90%' backgroundColor={theme.colors.dark05} height='1px'/>
|
||||
<FlexColumn as='ul' onClick={() => console.log('profile click')}
|
||||
gap='24px' padding='32px 24px' alignmentX='flex-start'>
|
||||
<FlexRow as='button' gap='16px'>
|
||||
<Svg width='16px' height='16px' src={userIco} size='cover'/>
|
||||
<Body as='li'>
|
||||
Profile
|
||||
</Body>
|
||||
<Container
|
||||
width="90%"
|
||||
backgroundColor={theme.colors.dark05}
|
||||
height="1px"
|
||||
/>
|
||||
<FlexColumn
|
||||
as="ul"
|
||||
onClick={KeyCloakService.goToProfile}
|
||||
gap="24px"
|
||||
padding="32px 24px"
|
||||
alignmentX="flex-start"
|
||||
>
|
||||
<FlexRow as="button" gap="16px">
|
||||
<Svg width="16px" height="16px" src={userIco} size="cover" />
|
||||
<Body as="li">Profile</Body>
|
||||
</FlexRow>
|
||||
<FlexRow as='button' onClick={props.visible === '0' ? KeyCloakService.doLogout : null} gap='16px'>
|
||||
<Svg width='16px' height='16px' src={loginIco} rotate='180deg'/>
|
||||
<Body as='li'>
|
||||
Sign out
|
||||
</Body>
|
||||
<FlexRow
|
||||
as="button"
|
||||
onClick={props.visible === '0' ? KeyCloakService.doLogout : null}
|
||||
gap="16px"
|
||||
>
|
||||
<Svg width="16px" height="16px" src={loginIco} rotate="180deg" />
|
||||
<Body as="li">Sign out</Body>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</LoggedBarStyle>
|
||||
|
@ -1,77 +1,69 @@
|
||||
import React from 'react';
|
||||
import theme from '../../utils/theme';
|
||||
import Media from 'react-media';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
RENDER_WHEN,
|
||||
IS_MOBILE,
|
||||
} from '../../utils/globals';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import Table from '../../components/generic/Table/Table';
|
||||
import Search from '../../components/generic/Search';
|
||||
import allEntriesSearchQueryHandler from './allEntriesSearchQueryHandler';
|
||||
import getAllEntries from '../../api/getAllEntries';
|
||||
import getEntries from '../../api/getEntries';
|
||||
import Table from '../../components/generic/Table';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import { CALC_PAGES, ELEMENTS_PER_PAGE } from '../../utils/globals';
|
||||
import searchQueryHandler from './searchHandler';
|
||||
import orderKeys from './orderKeys';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
|
||||
const AllEntries = (props) => {
|
||||
const [entriesFromApi, setEntriesFromApi] = React.useState([]);
|
||||
const [entriesAll, setEntriesAll] = React.useState([]);
|
||||
const [entries, setEntries] = React.useState([]);
|
||||
const [pageNr, setPageNr] = React.useState(1);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [idSorted, setIdSorted] = React.useState([]);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
const [submitterSorted, setSubmitterSorted] = React.useState(false);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
const [profileInfo, setProfileInfo] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (props.challengeName) challengeDataRequest(props.challengeName);
|
||||
}, [props.challengeName]);
|
||||
const getProfileInfo = () => {
|
||||
if (KeyCloakService.isLoggedIn()) {
|
||||
KeyCloakService.getProfileInfo(setProfileInfo);
|
||||
} else {
|
||||
setProfileInfo(false);
|
||||
}
|
||||
};
|
||||
|
||||
const challengeDataRequest = (challengeName) => {
|
||||
getAllEntries(challengeName, setEntriesFromApi, setEntriesAll);
|
||||
getAllEntries(
|
||||
challengeName,
|
||||
undefined,
|
||||
setEntries,
|
||||
React.useMemo(() => {
|
||||
if (props.challengeName) {
|
||||
getEntries(
|
||||
'challenge-all-submissions',
|
||||
props.challengeName,
|
||||
[setEntries, setEntriesAll],
|
||||
setLoading,
|
||||
setScoresSorted
|
||||
);
|
||||
};
|
||||
|
||||
const getPossibleMetrics = () => {
|
||||
let metrics = [];
|
||||
if (entriesFromApi.tests) {
|
||||
for (let test of entriesFromApi.tests) {
|
||||
let myEval = `${test.metric}.${test.name}`;
|
||||
if (myEval && !metrics.includes(myEval)) {
|
||||
metrics.push(myEval);
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
getProfileInfo();
|
||||
}, [props.challengeName]);
|
||||
|
||||
const getAllEntriesHeader = () => {
|
||||
let header = ['#', 'submitter'];
|
||||
if (IS_MOBILE()) header.push('when');
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
if (!IS_MOBILE()) header.push('when');
|
||||
return header;
|
||||
};
|
||||
|
||||
const searchQueryHandler = (event) => {
|
||||
allEntriesSearchQueryHandler(event, entriesAll, setPageNr, setEntries);
|
||||
};
|
||||
|
||||
const sortByUpdate = (elem, i) => {
|
||||
let newEntries = entries;
|
||||
const sortByUpdate = React.useCallback(
|
||||
(elem) => {
|
||||
let newEntries = entries.slice();
|
||||
const possibleMetrics = orderKeys(entries[0]).filter(
|
||||
(key) => !['id', 'submitter', 'when'].includes(key)
|
||||
);
|
||||
let metricIndex = possibleMetrics.indexOf(elem);
|
||||
let newScoresSorted = scoresSorted.slice();
|
||||
switch (elem) {
|
||||
case '#':
|
||||
case 'id':
|
||||
if (idSorted) {
|
||||
setIdSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id > b.id ? 1 : b.id > a.id ? -1 : 0
|
||||
);
|
||||
} else {
|
||||
setIdSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id < b.id ? 1 : b.id < a.id ? -1 : 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'submitter':
|
||||
if (submitterSorted) {
|
||||
@ -108,23 +100,15 @@ const AllEntries = (props) => {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let metricIndex = getPossibleMetrics().indexOf(elem);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let newScoresSorted = scoresSorted;
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(b.evaluations ? b.evaluations[elem] : -1) -
|
||||
(a.evaluations ? a.evaluations[elem] : -1)
|
||||
(a, b) => (b ? b[elem] : -1) - (a ? a[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(a.evaluations ? a.evaluations[elem] : -1) -
|
||||
(b.evaluations ? b.evaluations[elem] : -1)
|
||||
(a, b) => (a ? a[elem] : -1) - (b ? b[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
@ -132,91 +116,40 @@ const AllEntries = (props) => {
|
||||
break;
|
||||
}
|
||||
setEntries(newEntries);
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 12px 0">
|
||||
All Entries
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getAllEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
tableType="allEntries"
|
||||
gridTemplateColumns={
|
||||
'1fr ' + '4fr '.repeat(getAllEntriesHeader().length - 1)
|
||||
}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="48px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
},
|
||||
[entries, idSorted, scoresSorted, submitterSorted, whenSorted]
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
const n = (pageNr - 1) * (ELEMENTS_PER_PAGE * 2);
|
||||
const elements = entries.slice(n, n + ELEMENTS_PER_PAGE * 2);
|
||||
|
||||
return (
|
||||
<FlexColumn padding="24px" as="section" width="100%" maxWidth="1600px">
|
||||
<H2 as="h2" margin="0 0 32px 0">
|
||||
All Entries
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<FlexColumn
|
||||
as="section"
|
||||
padding="24px"
|
||||
gap="32px"
|
||||
width="100%"
|
||||
maxWidth="1600px"
|
||||
>
|
||||
<H2 as="h2">All Entries</H2>
|
||||
{!loading && (profileInfo !== null) ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getAllEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
gridTemplateColumns={
|
||||
'1fr 3fr ' + '3fr '.repeat(getPossibleMetrics().length) + ' 3fr'
|
||||
<Search
|
||||
searchQueryHandler={(event) =>
|
||||
searchQueryHandler(event, entriesAll, setPageNr, setEntries)
|
||||
}
|
||||
user={props.user}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
metrics={getPossibleMetrics()}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
{elements.length > 0 && entries[0] && (
|
||||
<div style={{ width: '100%', overflowX: 'auto' }}>
|
||||
<Table
|
||||
items={elements}
|
||||
orderedKeys={orderKeys(entries[0])}
|
||||
sortByUpdate={sortByUpdate}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
profileInfo={profileInfo}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
@ -233,12 +166,5 @@ const AllEntries = (props) => {
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllEntries;
|
||||
|
@ -1,32 +0,0 @@
|
||||
const allEntriesSearchQueryHandler = (
|
||||
event,
|
||||
entriesFromApi,
|
||||
setPageNr,
|
||||
setEntries
|
||||
) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesFromApi);
|
||||
else {
|
||||
for (let entry of entriesFromApi) {
|
||||
const { id, when, submitter } = entry;
|
||||
console.log(entry);
|
||||
let evaluations = '';
|
||||
if (entry.evaluations) {
|
||||
for (let evaluation of Object.values(entry.evaluations)) {
|
||||
evaluations += ` ${evaluation}`;
|
||||
}
|
||||
}
|
||||
const str = `${id} ${submitter} ${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${evaluations}`;
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default allEntriesSearchQueryHandler;
|
27
src/pages/AllEntries/orderKeys.js
Normal file
27
src/pages/AllEntries/orderKeys.js
Normal file
@ -0,0 +1,27 @@
|
||||
const orderKeys = (elem) => {
|
||||
if (elem) {
|
||||
let result = ['id', 'submitter', 'description'];
|
||||
const elemKeys = Object.keys(elem);
|
||||
const dev0keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-0')
|
||||
.sort();
|
||||
const dev1keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-1')
|
||||
.sort();
|
||||
const testAkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-A')
|
||||
.sort();
|
||||
const testBkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-B')
|
||||
.sort();
|
||||
result = result.concat(dev0keys);
|
||||
result = result.concat(dev1keys);
|
||||
result = result.concat(testAkeys);
|
||||
result = result.concat(testBkeys);
|
||||
result.push('when');
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default orderKeys;
|
28
src/pages/AllEntries/searchHandler.js
Normal file
28
src/pages/AllEntries/searchHandler.js
Normal file
@ -0,0 +1,28 @@
|
||||
const searchQueryHandler = (event, entriesAll, setPageNr, setEntries) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesAll);
|
||||
else {
|
||||
for (let entry of entriesAll) {
|
||||
let { when, tags } = entry;
|
||||
tags = Object.values(tags)
|
||||
.map((tag) => tag.name)
|
||||
.join(' ');
|
||||
const otherKeys = Object.values(entry)
|
||||
.join(' ')
|
||||
.replaceAll(-999999999, 'N/A')
|
||||
.replaceAll('[object Object]', '')
|
||||
.replaceAll(',', '');
|
||||
const str = `${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${otherKeys} ${tags}`;
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default searchQueryHandler;
|
@ -33,7 +33,7 @@ const Challenges = () => {
|
||||
statusFilterHandle(state.statusFilter, state.challenges, dispatch);
|
||||
}, [state.statusFilter, state.challenges]);
|
||||
|
||||
const setPage = React.useCallback((value) => {
|
||||
const setPageNr = React.useCallback((value) => {
|
||||
dispatch({ type: CHALLENGES_ACTION.SET_PAGE, payload: value });
|
||||
}, []);
|
||||
|
||||
@ -73,7 +73,7 @@ const Challenges = () => {
|
||||
dispatch={dispatch}
|
||||
filtersMenuRender={filtersMenuRender}
|
||||
searchQueryHandler={searchQueryHandler}
|
||||
setPage={setPage}
|
||||
setPageNr={setPageNr}
|
||||
filtersMenu={state.filtersMenu}
|
||||
loading={state.loading}
|
||||
pageNr={state.pageNr}
|
||||
@ -85,7 +85,7 @@ const Challenges = () => {
|
||||
dispatch={dispatch}
|
||||
filtersMenuRender={filtersMenuRender}
|
||||
searchQueryHandler={searchQueryHandler}
|
||||
setPage={setPage}
|
||||
setPageNr={setPageNr}
|
||||
filtersMenu={state.filtersMenu}
|
||||
loading={state.loading}
|
||||
pageNr={state.pageNr}
|
||||
|
@ -35,7 +35,7 @@ const ChallengesDesktop = (props) => {
|
||||
{!props.loading && (
|
||||
<Pager
|
||||
pageNr={props.pageNr}
|
||||
setPage={props.setPage}
|
||||
setPageNr={props.setPageNr}
|
||||
elements={props.challengesFiltered}
|
||||
pages={CALC_PAGES(props.challengesFiltered)}
|
||||
width="72px"
|
||||
|
@ -4,10 +4,10 @@ import { useParams } from 'react-router-dom';
|
||||
import { H1, Medium } from '../../utils/fonts';
|
||||
import theme from '../../utils/theme';
|
||||
import MobileChallengeMenu from './components/MobileChallengeMenu';
|
||||
import Leaderboard from '../Leaderboard/Leaderboard';
|
||||
import Leaderboard from '../Leaderboard';
|
||||
import Readme from '../Readme';
|
||||
import HowTo from '../HowTo/HowTo';
|
||||
import MyEntries from '../MyEntries/MyEntries';
|
||||
import HowTo from '../HowTo';
|
||||
import MyEntries from '../MyEntries';
|
||||
import Submit from '../Submit';
|
||||
import Media from 'react-media';
|
||||
import DesktopChallengeMenu from './components/DesktopChallengeMenu';
|
||||
@ -15,18 +15,15 @@ import { CHALLENGE_SECTIONS, RENDER_ICO } from '../../utils/globals';
|
||||
import textIco from '../../assets/text_ico.svg';
|
||||
import getChallengeInfo from '../../api/getChallengeInfo';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import getUser from '../../api/getUser';
|
||||
import AllEntries from '../AllEntries/AllEntries';
|
||||
import AllEntries from '../AllEntries';
|
||||
|
||||
const Challenge = (props) => {
|
||||
const challengeName = useParams().challengeId;
|
||||
const [challenge, setChallenge] = React.useState([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [user, setUser] = React.useState('');
|
||||
|
||||
React.useEffect(() => {
|
||||
getChallengeInfo(setChallenge, setLoading, challengeName);
|
||||
getUser(setUser);
|
||||
}, [challengeName]);
|
||||
|
||||
const sectionRender = () => {
|
||||
@ -36,12 +33,15 @@ const Challenge = (props) => {
|
||||
<Leaderboard
|
||||
challengeName={challengeName}
|
||||
mainMetric={challenge.mainMetric}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.ALL_ENTRIES:
|
||||
return (
|
||||
<AllEntries challengeName={challengeName} setLoading={setLoading} />
|
||||
<AllEntries
|
||||
challengeName={challengeName}
|
||||
setLoading={setLoading}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.README:
|
||||
return (
|
||||
@ -57,11 +57,15 @@ const Challenge = (props) => {
|
||||
<HowTo
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
challengeName={challengeName}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.MY_ENTRIES:
|
||||
return <MyEntries challengeName={challengeName} />;
|
||||
return (
|
||||
<MyEntries
|
||||
challengeName={challengeName}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.SUBMIT:
|
||||
return <Submit challengeName={challengeName} setLoading={setLoading} />;
|
||||
default:
|
||||
|
@ -7,10 +7,11 @@ import HowToContent from './components/HowToContent';
|
||||
|
||||
const HowTo = (props) => {
|
||||
const [userFullInfo, setUserFullInfo] = React.useState(null);
|
||||
const username = KeyCloakService.getUsername();
|
||||
|
||||
React.useEffect(() => {
|
||||
getFullUser(setUserFullInfo);
|
||||
|
||||
React.useMemo(async () => {
|
||||
await getFullUser(setUserFullInfo);
|
||||
setTimeout(() => {
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
props.popUpMessageHandler(
|
||||
'Please log in',
|
||||
@ -18,6 +19,7 @@ const HowTo = (props) => {
|
||||
() => KeyCloakService.doLogin
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
@ -31,7 +33,7 @@ const HowTo = (props) => {
|
||||
<FlexColumn maxWidth="680px" alignmentX="flex-start" gap="48px">
|
||||
<HowToContent
|
||||
userFullInfo={userFullInfo}
|
||||
user={props.user ? props.user : 'yourID'}
|
||||
user={username ? username : 'yourID'}
|
||||
challengeName={props.challengeName}
|
||||
/>
|
||||
</FlexColumn>
|
||||
|
@ -1,20 +1,15 @@
|
||||
import React from 'react';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import Table from '../../components/generic/Table/Table';
|
||||
import PropsTypes from 'prop-types';
|
||||
import getChallengeLeaderboard from '../../api/getChallengeLeaderboard';
|
||||
import leaderboardSearchQueryHandler from './leaderboardSearchQueryHandler';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
RENDER_WHEN,
|
||||
} from '../../utils/globals';
|
||||
import { CALC_PAGES } from '../../utils/globals';
|
||||
import Search from '../../components/generic/Search';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import orderKeys from './orderKeys';
|
||||
import { ELEMENTS_PER_PAGE } from '../../utils/globals';
|
||||
|
||||
const Leaderboard = (props) => {
|
||||
const [entriesFromApi, setEntriesFromApi] = React.useState([]);
|
||||
@ -26,68 +21,48 @@ const Leaderboard = (props) => {
|
||||
const [entriesSorted, setEntriesSorted] = React.useState(false);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
const [idSorted, setIdSorted] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
challengeDataRequest(props.challengeName);
|
||||
React.useMemo(() => {
|
||||
getChallengeLeaderboard(
|
||||
'leaderboard',
|
||||
props.challengeName,
|
||||
[setEntries, setEntriesFromApi],
|
||||
setLoading,
|
||||
setScoresSorted
|
||||
);
|
||||
}, [props.challengeName]);
|
||||
|
||||
const challengeDataRequest = (challengeName) => {
|
||||
getChallengeLeaderboard(setEntriesFromApi, challengeName);
|
||||
getChallengeLeaderboard(setEntries, challengeName, setLoading);
|
||||
};
|
||||
|
||||
const getMetricIndex = (metricName) => {
|
||||
let i = 0;
|
||||
for (let evaluation of entriesFromApi[0].evaluations) {
|
||||
if (`${evaluation.test.metric}.${evaluation.test.name}` === metricName) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
};
|
||||
|
||||
const searchQueryHandler = (event) => {
|
||||
leaderboardSearchQueryHandler(event, entriesFromApi, setPageNr, setEntries);
|
||||
};
|
||||
|
||||
const getPossibleMetrics = () => {
|
||||
let metrics = [];
|
||||
for (let entry of entriesFromApi) {
|
||||
for (let evaluation of entry.evaluations) {
|
||||
let metric = evaluation.test.metric;
|
||||
let name = evaluation.test.name;
|
||||
if (metric && !metrics.includes(`${metric}.${name}`)) {
|
||||
metrics.push(`${metric}.${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
|
||||
const getLeaderboardHeader = () => {
|
||||
let header = ['#', 'submitter', 'description'];
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
header.push('entries');
|
||||
header.push('when');
|
||||
return header;
|
||||
};
|
||||
|
||||
const getLeaderboardHeaderMobile = () => {
|
||||
let header = ['#', 'submitter', 'description', 'entries', 'when'];
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
return header;
|
||||
};
|
||||
|
||||
const sortByUpdate = (elem) => {
|
||||
let metricIndex = 0;
|
||||
let newEntries = entries;
|
||||
const sortByUpdate = React.useCallback(
|
||||
(elem) => {
|
||||
let newEntries = entries.slice();
|
||||
const possibleMetrics = orderKeys(entries[0]).filter(
|
||||
(key) =>
|
||||
!['id', 'submitter', 'when', 'description', 'times'].includes(key)
|
||||
);
|
||||
let metricIndex = possibleMetrics.indexOf(elem);
|
||||
let newScoresSorted = scoresSorted.slice();
|
||||
switch (elem) {
|
||||
case 'id':
|
||||
if (idSorted) {
|
||||
setIdSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id > b.id ? 1 : b.id > a.id ? -1 : 0
|
||||
);
|
||||
} else {
|
||||
setIdSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id < b.id ? 1 : b.id < a.id ? -1 : 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'submitter':
|
||||
if (submitterSorted) {
|
||||
setSubmitterSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() < b.submitter.toLowerCase()
|
||||
? 1
|
||||
@ -95,8 +70,8 @@ const Leaderboard = (props) => {
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
setSubmitterSorted(false);
|
||||
} else {
|
||||
setSubmitterSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() > b.submitter.toLowerCase()
|
||||
? 1
|
||||
@ -104,7 +79,6 @@ const Leaderboard = (props) => {
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
setSubmitterSorted(true);
|
||||
}
|
||||
break;
|
||||
case 'description':
|
||||
@ -128,45 +102,42 @@ const Leaderboard = (props) => {
|
||||
setDescriptionSorted(true);
|
||||
}
|
||||
break;
|
||||
case 'entries':
|
||||
case 'times':
|
||||
if (entriesSorted) {
|
||||
newEntries = newEntries.sort((a, b) => b.times - a.times);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.times > b.times ? 1 : b.times > a.times ? -1 : 0
|
||||
);
|
||||
setEntriesSorted(false);
|
||||
} else {
|
||||
newEntries = newEntries.sort((a, b) => a.times - b.times);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.times < b.times ? 1 : b.times < a.times ? -1 : 0
|
||||
);
|
||||
setEntriesSorted(true);
|
||||
}
|
||||
break;
|
||||
case 'when':
|
||||
if (whenSorted) {
|
||||
setWhenSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when < b.when ? 1 : b.when < a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(false);
|
||||
} else {
|
||||
setWhenSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when > b.when ? 1 : b.when > a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
metricIndex = getMetricIndex(elem);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let newScoresSorted = scoresSorted;
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
b.evaluations[metricIndex].score -
|
||||
a.evaluations[metricIndex].score
|
||||
(a, b) => (b ? b[elem] : -1) - (a ? a[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
a.evaluations[metricIndex].score -
|
||||
b.evaluations[metricIndex].score
|
||||
(a, b) => (a ? a[elem] : -1) - (b ? b[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
@ -174,103 +145,47 @@ const Leaderboard = (props) => {
|
||||
break;
|
||||
}
|
||||
setEntries(newEntries);
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 12px 0">
|
||||
Leaderboard
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getLeaderboardHeaderMobile()}
|
||||
tableType="leaderboard"
|
||||
gridTemplateColumns={
|
||||
entries[0]
|
||||
? '1fr 2fr 3fr ' +
|
||||
'2fr '.repeat(entries[0].evaluations.length) +
|
||||
'1fr 2fr'
|
||||
: ''
|
||||
}
|
||||
user={props.user}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'description', format: null, order: 3, align: 'left' },
|
||||
{ name: 'times', format: null, order: 4, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
metrics={getPossibleMetrics()}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="48px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
},
|
||||
[
|
||||
descriptionSorted,
|
||||
entries,
|
||||
entriesSorted,
|
||||
idSorted,
|
||||
scoresSorted,
|
||||
submitterSorted,
|
||||
whenSorted,
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
const n = (pageNr - 1) * (ELEMENTS_PER_PAGE * 2);
|
||||
const elements = entries.slice(n, n + ELEMENTS_PER_PAGE * 2);
|
||||
|
||||
return (
|
||||
<FlexColumn padding="24px" as="section" width="100%" maxWidth="1600px">
|
||||
<H2 as="h2" margin="0 0 32px 0">
|
||||
Leaderboard
|
||||
</H2>
|
||||
<FlexColumn
|
||||
padding="24px"
|
||||
gap="32px"
|
||||
as="section"
|
||||
width="100%"
|
||||
maxWidth="1600px"
|
||||
>
|
||||
<H2 as="h2">Leaderboard</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getLeaderboardHeader()}
|
||||
gridTemplateColumns={
|
||||
entries[0]
|
||||
? '1fr 2fr 3fr ' +
|
||||
'2fr '.repeat(entries[0].evaluations.length) +
|
||||
'1fr 2fr'
|
||||
: ''
|
||||
<Search
|
||||
searchQueryHandler={(event) =>
|
||||
searchQueryHandler(event, entries, setPageNr, setEntries)
|
||||
}
|
||||
user={props.user}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'description', format: null, order: 3, align: 'left' },
|
||||
{ name: 'times', format: null, order: 4, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
metrics={getPossibleMetrics()}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
{elements.length > 0 && entries[0] && (
|
||||
<div style={{ width: '100%', overflowX: 'auto' }}>
|
||||
<Table
|
||||
items={elements}
|
||||
orderedKeys={orderKeys(entries[0])}
|
||||
sortByUpdate={sortByUpdate}
|
||||
rowFooter={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
@ -288,20 +203,4 @@ const Leaderboard = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Leaderboard.propsTypes = {
|
||||
challengeName: PropsTypes.string,
|
||||
};
|
||||
|
||||
Leaderboard.defaultProps = {
|
||||
challengeName: '',
|
||||
};
|
||||
|
||||
export default Leaderboard;
|
||||
|
27
src/pages/Leaderboard/orderKeys.js
Normal file
27
src/pages/Leaderboard/orderKeys.js
Normal file
@ -0,0 +1,27 @@
|
||||
const orderKeys = (elem) => {
|
||||
if (elem) {
|
||||
let result = ['#', 'submitter', 'description'];
|
||||
const elemKeys = Object.keys(elem);
|
||||
const dev0keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-0')
|
||||
.sort();
|
||||
const dev1keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-1')
|
||||
.sort();
|
||||
const testAkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-A')
|
||||
.sort();
|
||||
const testBkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-B')
|
||||
.sort();
|
||||
result = result.concat(dev0keys);
|
||||
result = result.concat(dev1keys);
|
||||
result = result.concat(testAkeys);
|
||||
result = result.concat(testBkeys);
|
||||
result.push('times', 'when');
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default orderKeys;
|
@ -1,107 +1,81 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import getMyEntries from '../../api/getMyEntries';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
IS_MOBILE,
|
||||
RENDER_WHEN,
|
||||
} from '../../utils/globals';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import { CALC_PAGES } from '../../utils/globals';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import Table from '../../components/generic/Table/Table';
|
||||
import myEntriesSearchQueryHandler from './myEntriesSearchQueryHandler';
|
||||
import Search from '../../components/generic/Search';
|
||||
import orderKeys from './orderKeys';
|
||||
import { ELEMENTS_PER_PAGE } from '../../utils/globals';
|
||||
import getEntries from '../../api/getEntries';
|
||||
import searchHandler from './searchHandler';
|
||||
|
||||
const MyEntries = (props) => {
|
||||
const [myEntriesFromAPI, setMyEntriesFromAPI] = React.useState({});
|
||||
const [myEntriesAll, setMyEntriesAll] = React.useState({});
|
||||
const [myEntries, setMyEntries] = React.useState({});
|
||||
// const [myEntriesFromAPI, setMyEntriesFromAPI] = React.useState({});
|
||||
const [myEntriesAll, setMyEntriesAll] = React.useState([]);
|
||||
const [myEntries, setMyEntries] = React.useState([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [pageNr, setPageNr] = React.useState(1);
|
||||
const [idSorted, setIdSorted] = React.useState([]);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
challengesRequest();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const searchQueryHandler = (event) => {
|
||||
myEntriesSearchQueryHandler(event, myEntriesAll, setPageNr, setMyEntries);
|
||||
};
|
||||
|
||||
const getPossibleMetrics = () => {
|
||||
let metrics = [];
|
||||
for (let test of myEntriesFromAPI.tests) {
|
||||
let myEval = `${test.metric}.${test.name}`;
|
||||
if (myEval && !metrics.includes(myEval)) {
|
||||
metrics.push(myEval);
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
|
||||
const getMyEntriesHeader = () => {
|
||||
let header = ['#'];
|
||||
if (IS_MOBILE()) header.push('when');
|
||||
for (let myEval of getPossibleMetrics()) {
|
||||
header.push(myEval);
|
||||
}
|
||||
if (!IS_MOBILE()) header.push('when');
|
||||
return header;
|
||||
};
|
||||
|
||||
const challengesRequest = () => {
|
||||
getMyEntries(
|
||||
React.useMemo(() => {
|
||||
getEntries(
|
||||
'challenge-my-submissions',
|
||||
props.challengeName,
|
||||
setMyEntriesFromAPI,
|
||||
setMyEntriesAll,
|
||||
setMyEntries,
|
||||
[setMyEntries, setMyEntriesAll],
|
||||
setLoading,
|
||||
setScoresSorted
|
||||
);
|
||||
};
|
||||
}, [props.challengeName]);
|
||||
|
||||
const sortByUpdate = (elem, i) => {
|
||||
let newEntries = myEntries;
|
||||
const sortByUpdate = React.useCallback(
|
||||
(elem) => {
|
||||
let newEntries = myEntries.slice();
|
||||
const possibleMetrics = orderKeys(myEntries[0]).filter(
|
||||
(key) => !['id', 'submitter', 'when'].includes(key)
|
||||
);
|
||||
let metricIndex = possibleMetrics.indexOf(elem);
|
||||
let newScoresSorted = scoresSorted.slice();
|
||||
switch (elem) {
|
||||
case '#':
|
||||
case 'id':
|
||||
if (idSorted) {
|
||||
setIdSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id > b.id ? 1 : b.id > a.id ? -1 : 0
|
||||
);
|
||||
} else {
|
||||
setIdSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id < b.id ? 1 : b.id < a.id ? -1 : 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'when':
|
||||
if (whenSorted) {
|
||||
setWhenSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when < b.when ? 1 : b.when < a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(false);
|
||||
} else {
|
||||
setWhenSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when > b.when ? 1 : b.when > a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let metricIndex = getPossibleMetrics().indexOf(elem);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let newScoresSorted = scoresSorted;
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(b.evaluations ? b.evaluations[elem] : -1) -
|
||||
(a.evaluations ? a.evaluations[elem] : -1)
|
||||
(a, b) => (b ? b[elem] : -1) - (a ? a[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(a.evaluations ? a.evaluations[elem] : -1) -
|
||||
(b.evaluations ? b.evaluations[elem] : -1)
|
||||
(a, b) => (a ? a[elem] : -1) - (b ? b[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
@ -109,95 +83,44 @@ const MyEntries = (props) => {
|
||||
break;
|
||||
}
|
||||
setMyEntries(newEntries);
|
||||
};
|
||||
},
|
||||
[idSorted, myEntries, scoresSorted, whenSorted]
|
||||
);
|
||||
|
||||
const n = (pageNr - 1) * (ELEMENTS_PER_PAGE * 2);
|
||||
let elements = myEntries.slice(n, n + ELEMENTS_PER_PAGE * 2);
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 12px 0">
|
||||
My Entries
|
||||
</H2>
|
||||
<FlexColumn
|
||||
padding="24px"
|
||||
gap="32px"
|
||||
as="section"
|
||||
width="100%"
|
||||
maxWidth="1600px"
|
||||
>
|
||||
<H2 as="h2">My Entries</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getMyEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
tableType="myEntries"
|
||||
gridTemplateColumns={
|
||||
'1fr ' + '4fr '.repeat(getMyEntriesHeader().length - 1)
|
||||
<Search
|
||||
searchQueryHandler={(event) =>
|
||||
searchHandler(event, myEntriesAll, setPageNr, setMyEntries)
|
||||
}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 3, align: 'right' },
|
||||
]}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 2,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
/>
|
||||
{elements.length > 0 && myEntries[0] && (
|
||||
<div style={{ width: '100%', overflowX: 'auto' }}>
|
||||
<Table
|
||||
items={elements}
|
||||
orderedKeys={orderKeys(myEntries[0])}
|
||||
sortByUpdate={sortByUpdate}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
setPageNr={setPageNr}
|
||||
width="48px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(myEntries)}
|
||||
number={`${pageNr} / ${CALC_PAGES(myEntries)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px" as="section" width="100%" maxWidth="1600px">
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 32px 0">
|
||||
My Entries
|
||||
</H2>
|
||||
</FlexColumn>
|
||||
{myEntries && !loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getMyEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
gridTemplateColumns={
|
||||
'1fr ' + '3fr '.repeat(getMyEntriesHeader().length - 2) + ' 4fr'
|
||||
}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 3, align: 'right' },
|
||||
]}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 2,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
myEntries
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
setPageNr={setPageNr}
|
||||
width="72px"
|
||||
mobileRender
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(myEntries, 2)}
|
||||
number={`${pageNr} / ${CALC_PAGES(myEntries, 2)}`}
|
||||
@ -210,12 +133,4 @@ const MyEntries = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyEntries;
|
||||
|
@ -1,33 +0,0 @@
|
||||
const myEntriesSearchQueryHandler = (
|
||||
event,
|
||||
entriesFromApi,
|
||||
setPageNr,
|
||||
setEntries
|
||||
) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesFromApi);
|
||||
else {
|
||||
for (let entry of entriesFromApi) {
|
||||
const { id, when } = entry;
|
||||
let evaluations = '';
|
||||
if (entry.evaluations) {
|
||||
for (let evaluation of Object.values(entry.evaluations)) {
|
||||
evaluations += ` ${evaluation}`;
|
||||
}
|
||||
}
|
||||
const str = `${id} ${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${evaluations}`;
|
||||
console.log(entry);
|
||||
console.log(str);
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default myEntriesSearchQueryHandler;
|
27
src/pages/MyEntries/orderKeys.js
Normal file
27
src/pages/MyEntries/orderKeys.js
Normal file
@ -0,0 +1,27 @@
|
||||
const orderKeys = (elem) => {
|
||||
if (elem) {
|
||||
let result = ['id'];
|
||||
const elemKeys = Object.keys(elem);
|
||||
const dev0keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-0')
|
||||
.sort();
|
||||
const dev1keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-1')
|
||||
.sort();
|
||||
const testAkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-A')
|
||||
.sort();
|
||||
const testBkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-B')
|
||||
.sort();
|
||||
result = result.concat(dev0keys);
|
||||
result = result.concat(dev1keys);
|
||||
result = result.concat(testAkeys);
|
||||
result = result.concat(testBkeys);
|
||||
result.push('when');
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default orderKeys;
|
28
src/pages/MyEntries/searchHandler.js
Normal file
28
src/pages/MyEntries/searchHandler.js
Normal file
@ -0,0 +1,28 @@
|
||||
const searchQueryHandler = (event, entriesAll, setPageNr, setEntries) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesAll);
|
||||
else {
|
||||
for (let entry of entriesAll) {
|
||||
let { when, tags } = entry;
|
||||
tags = Object.values(tags)
|
||||
.map((tag) => tag.name)
|
||||
.join(' ');
|
||||
const otherKeys = Object.values(entry)
|
||||
.join(' ')
|
||||
.replaceAll(-999999999, 'N/A')
|
||||
.replaceAll('[object Object]', '')
|
||||
.replaceAll(',', '');
|
||||
const str = `${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${otherKeys} ${tags}`;
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default searchQueryHandler;
|
@ -1,65 +1,13 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../utils/containers';
|
||||
import { Body, H2 } from '../utils/fonts';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import Media from 'react-media';
|
||||
import theme from '../utils/theme';
|
||||
import getChallengeFullDescription from '../api/getChallengeFullDescription';
|
||||
import styled from 'styled-components';
|
||||
import InfoList from '../components/generic/InfoList';
|
||||
import Loading from '../components/generic/Loading';
|
||||
import theme from '../../utils/theme';
|
||||
import getChallengeFullDescription from '../../api/getChallengeFullDescription';
|
||||
import InfoList from '../../components/generic/InfoList';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import { marked } from 'marked';
|
||||
|
||||
const ReadmeStyle = styled(Body)`
|
||||
* {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Kanit', sans-serif;
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: 'Kanit', sans-serif;
|
||||
font-weight: inherit;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
margin: 24px 0;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.colors.dark};
|
||||
text-decoration: none;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
`;
|
||||
import ReadmeStyle from './ReadmeStyle';
|
||||
|
||||
const Readme = (props) => {
|
||||
const [fullDescription, setFullDescription] = React.useState('');
|
||||
@ -117,8 +65,13 @@ const Readme = (props) => {
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexColumn as="section" padding="20px" gap="64px">
|
||||
<FlexColumn gap="32px">
|
||||
<FlexColumn
|
||||
as="section"
|
||||
className="Readme__section"
|
||||
padding="20px"
|
||||
gap="64px"
|
||||
>
|
||||
<FlexColumn className="Readme__info" gap="32px">
|
||||
<H2 as="h2">Info</H2>
|
||||
<InfoList
|
||||
iconsSize="32px"
|
||||
@ -126,7 +79,12 @@ const Readme = (props) => {
|
||||
deadline={props.deadline}
|
||||
/>
|
||||
</FlexColumn>
|
||||
<FlexColumn alignmentX="flex-start" width="80%" maxWidth="1200px">
|
||||
<FlexColumn
|
||||
className="Readme__container"
|
||||
alignmentX="flex-start"
|
||||
width="80%"
|
||||
maxWidth="1200px"
|
||||
>
|
||||
<ReadmeStyle
|
||||
as={fullDescription ? 'section' : 'p'}
|
||||
dangerouslySetInnerHTML={{
|
64
src/pages/Readme/ReadmeStyle.js
Normal file
64
src/pages/Readme/ReadmeStyle.js
Normal file
@ -0,0 +1,64 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ReadmeStyle = styled.section`
|
||||
padding: 20px;
|
||||
gap: 24px;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
gap: 64px;
|
||||
}
|
||||
|
||||
/* .ReadmeStyle__content { */
|
||||
* {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Kanit', sans-serif;
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: 'Kanit', sans-serif;
|
||||
font-weight: inherit;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
margin: 24px 0;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.colors.dark};
|
||||
text-decoration: none;
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
/* } */
|
||||
`;
|
||||
|
||||
export default ReadmeStyle;
|
32
src/pages/Readme/components/ReadmeDesktop.js
Normal file
32
src/pages/Readme/components/ReadmeDesktop.js
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H2 } from '../../../utils/fonts';
|
||||
import InfoList from '../../../components/generic/InfoList';
|
||||
import ReadmeStyle from '../ReadmeStyle';
|
||||
|
||||
const ReadmeDesktop = (props) => {
|
||||
return (
|
||||
<FlexColumn as="section" padding="20px" gap="64px">
|
||||
<FlexColumn gap="32px">
|
||||
<H2 as="h2">Info</H2>
|
||||
<InfoList
|
||||
iconsSize="32px"
|
||||
metric={props.metric}
|
||||
deadline={props.deadline}
|
||||
/>
|
||||
</FlexColumn>
|
||||
<FlexColumn alignmentX="flex-start" width="80%" maxWidth="1200px">
|
||||
<ReadmeStyle
|
||||
as={props.fullDescription ? 'section' : 'p'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: props.fullDescription
|
||||
? props.parseMarkdownResponse(props.fullDescription)
|
||||
: props.description,
|
||||
}}
|
||||
/>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReadmeDesktop;
|
32
src/pages/Readme/components/ReadmeMobile.js
Normal file
32
src/pages/Readme/components/ReadmeMobile.js
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H2 } from '../../../utils/fonts';
|
||||
import InfoList from '../../../components/generic/InfoList';
|
||||
import ReadmeStyle from '../ReadmeStyle';
|
||||
|
||||
const ReadmeMobile = (props) => {
|
||||
return (
|
||||
<FlexColumn as="section" padding="20px" gap="24px">
|
||||
<FlexColumn gap="12px" alignmentX="flex-start">
|
||||
<H2 as="h2">Info</H2>
|
||||
<InfoList
|
||||
iconsSize="24px"
|
||||
metric={props.metric}
|
||||
deadline={props.deadline}
|
||||
/>
|
||||
</FlexColumn>
|
||||
<FlexColumn alignmentX="flex-start" maxWidth="260px">
|
||||
<ReadmeStyle
|
||||
as={props.fullDescription ? 'article' : 'p'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: props.fullDescription
|
||||
? props.parseMarkdownResponse(props.fullDescription)
|
||||
: props.description,
|
||||
}}
|
||||
/>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReadmeMobile;
|
1
src/pages/Readme/index.js
Normal file
1
src/pages/Readme/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Readme';
|
@ -24,7 +24,7 @@ const Submit = (props) => {
|
||||
});
|
||||
|
||||
React.useMemo(() => {
|
||||
getTags(dispatch);
|
||||
getTags((data) => dispatch({ type: SUBMIT_ACTION.LOAD_TAGS, payload: data }));
|
||||
}, []);
|
||||
|
||||
const challengeSubmissionSubmit = React.useCallback(() => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import SUBMIT_ACTION from './SubmitActionEnum';
|
||||
|
||||
const SubmitReducer = (state, action) => {
|
||||
console.log(`SubmitReducer: ${action.type}`);
|
||||
let newTags = state.tags;
|
||||
switch (action.type) {
|
||||
case SUBMIT_ACTION.SET_DESCRIPTION:
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Keycloak from 'keycloak-js';
|
||||
import { POLICY_PRIVACY_PAGE, ROOT_URL } from '../utils/globals';
|
||||
import SESSION_STORAGE from '../utils/sessionStorage';
|
||||
|
||||
const _kc = new Keycloak({
|
||||
url: process.env.REACT_APP_KC_URL,
|
||||
@ -30,14 +31,17 @@ const doLogin = () => {
|
||||
if (privacyPolicyAccept !== 'accept') {
|
||||
window.location.replace(`${ROOT_URL}${POLICY_PRIVACY_PAGE}/login`);
|
||||
} else {
|
||||
sessionStorage.setItem('logout', '');
|
||||
sessionStorage.setItem(SESSION_STORAGE.LOGOUT, '');
|
||||
_kc.login();
|
||||
}
|
||||
};
|
||||
|
||||
const doLogout = () => {
|
||||
sessionStorage.clear();
|
||||
sessionStorage.setItem('logout', 'yes');
|
||||
sessionStorage.setItem(
|
||||
SESSION_STORAGE.LOGOUT,
|
||||
SESSION_STORAGE.STATIC_VALUE.YES
|
||||
);
|
||||
_kc.logout();
|
||||
};
|
||||
|
||||
@ -61,6 +65,14 @@ const getUsername = () => _kc.tokenParsed?.preferred_username;
|
||||
|
||||
const hasRole = (roles) => roles.some((role) => _kc.hasRealmRole(role));
|
||||
|
||||
const goToProfile = () => {
|
||||
_kc.accountManagement();
|
||||
};
|
||||
|
||||
const getProfileInfo = async (setProfileInfo) => {
|
||||
_kc.loadUserInfo().then((response) => setProfileInfo(response));
|
||||
};
|
||||
|
||||
const KeyCloakService = {
|
||||
initKeycloak,
|
||||
doLogin,
|
||||
@ -71,6 +83,8 @@ const KeyCloakService = {
|
||||
getUsername,
|
||||
hasRole,
|
||||
doRegister,
|
||||
goToProfile,
|
||||
getProfileInfo,
|
||||
};
|
||||
|
||||
export default KeyCloakService;
|
||||
|
@ -2,9 +2,13 @@ const colors = {
|
||||
white: '#FCFCFC',
|
||||
green: '#1B998B',
|
||||
blue: '#4B8FF0',
|
||||
red: '#FF1B1C',
|
||||
green03: 'rgba(27, 153, 139, 0.3)',
|
||||
green05: 'rgba(27, 153, 139, 0.5)',
|
||||
green08: 'rgba(27, 153, 139, 0.8)',
|
||||
dark: '#343434',
|
||||
dark003: 'rgba(52, 52, 52, 0.03)',
|
||||
dark005: 'rgba(52, 52, 52, 0.05)',
|
||||
dark01: 'rgba(52, 52, 52, 0.1)',
|
||||
dark03: 'rgba(52, 52, 52, 0.3)',
|
||||
dark04: 'rgba(52, 52, 52, 0.4)',
|
||||
|
@ -8,7 +8,7 @@ import imageIco from '../assets/image_ico.svg';
|
||||
import tabularIco from '../assets/tabular_ico.svg';
|
||||
import React from 'react';
|
||||
|
||||
const ELEMENTS_PER_PAGE = 12;
|
||||
const ELEMENTS_PER_PAGE = 10;
|
||||
const MINI_DESCRIPTION_LENGTH = 70;
|
||||
const API = process.env.REACT_APP_API;
|
||||
const CHALLENGES_PAGE = '/challenges';
|
||||
@ -111,6 +111,11 @@ const RENDER_WHEN = (when) => {
|
||||
return `${when.slice(0, 10)} ${when.slice(11, 16)}`;
|
||||
};
|
||||
|
||||
const RENDER_METRIC_VALUE = (value) => {
|
||||
if (value <= -999999999) return 'N/A';
|
||||
else return value;
|
||||
};
|
||||
|
||||
const EVALUATIONS_FORMAT = (evaluate) => {
|
||||
if (Object.hasOwn(evaluate, 'score')) return evaluate.score.slice(0, 7);
|
||||
return evaluate.slice(0, 7);
|
||||
@ -146,6 +151,7 @@ export {
|
||||
RENDER_ICO,
|
||||
CALC_PAGES,
|
||||
RENDER_DEADLINE_TIME,
|
||||
RENDER_METRIC_VALUE,
|
||||
IS_MOBILE,
|
||||
RENDER_WHEN,
|
||||
EVALUATIONS_FORMAT,
|
||||
|
Loading…
Reference in New Issue
Block a user