Commit 4e0037a2 by Jan Hrabal

UI improvements

parent ef6d3802
......@@ -76,6 +76,18 @@
<artifactId>h2</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- testing -->
<dependency>
<groupId>junit</groupId>
......
package com.jh.memsource;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private ObjectMapper objectMapper;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
converters.add(converter);
}
}
package com.jh.memsource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.jh.memsource"))
.paths(PathSelectors.any())
.build();
}
}
\ No newline at end of file
package com.jh.memsource.project;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import com.jh.memsource.model.PagedList;
import com.jh.memsource.model.Project;
import com.jh.memsource.model.Settings;
import com.jh.memsource.settings.SettingsService;
@RestController
......@@ -20,8 +23,12 @@ public class ProjectApiController {
@GetMapping("/projects")
public @ResponseBody PagedList<Project> getSettings() {
return service.getProjects(settingsService.getSettings());
public @ResponseBody PagedList<Project> getSettings(Integer page, Integer pageSize) {
Settings settings = settingsService.getSettings();
if (settings == null) {
throw new ResponseStatusException(HttpStatus.FAILED_DEPENDENCY, "Settings not found");
}
return service.getProjects(settings);
}
......
......@@ -32,9 +32,9 @@ public class SettingsApiController {
@PutMapping("/settings")
@ResponseStatus(HttpStatus.ACCEPTED)
public @ResponseBody String saveSettings(@RequestBody Settings settings) {
public @ResponseBody Settings saveSettings(@RequestBody Settings settings) {
service.saveSettings(settings);
return "OK";
return settings;
}
}
......@@ -11,3 +11,5 @@ spring.jpa.properties.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
server.servlet.context-path=/api
\ No newline at end of file
......@@ -11,3 +11,5 @@ spring.jpa.properties.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
server.servlet.context-path=/api
\ No newline at end of file
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\ProjectService.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\DbConfig.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\repo\LoginRequest.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\SwaggerConfig.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\settings\SettingsService.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\model\PagedList.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\repo\ProjectRepository.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\model\Project.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\repo\LoginResponse.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\repo\ProjectResponse.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\model\Settings.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\MemsourceConfig.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\MemsourceApplication.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\settings\SettingsApiController.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\ProjectApiController.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\project\repo\ProjectsResponse.java
C:\Users\Jan\git\memsource\backend\src\main\java\com\jh\memsource\settings\repo\SettingsRepository.java
......@@ -17,7 +17,7 @@ import injectSettings from 'lib/settings';
const App = injectSettings((props) => {
const settings = props.settings;
if (settings && settings.fetching) {
return <CircularProgress size={ 60 } />
return <div key="progress" style={{display: 'flex', justifyContent: 'center'}}><CircularProgress size={ 60 } /></div>
}
return (
......
......@@ -8,13 +8,13 @@ import backend from 'lib/backend';
export const fetchSettings = async () => {
let response = await backend("/settings");
let response = await backend("/api/settings");
return response.body;
}
export const saveSettings = async ({ username, password }) => {
await backend("/settings", "PUT", {
await backend("/api/settings", "PUT", {
body: {
username,
password
......@@ -53,13 +53,18 @@ export default (Wrapped) => (props) => {
updateSettings({ ...settings, ...response, fetching: false, fetched: true });
} catch (e) {
updateSettings({ ...settings, fetching: false, fetched: false });
console.error(e);
}
}
async function save(values) {
updateSettings({ ...settings, saving: true });
try {
let response = await saveSettings(values);
updateSettings({ ...settings, ...values, saving: false });
updateSettings({ ...settings, ...values, saving: false, fetched: true });
} catch (e) {
console.error(e);
}
}
//try to fetch settings once the component is mounted
......
......@@ -9,6 +9,10 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import WorkIcon from '@material-ui/icons/Work';
import Typography from '@material-ui/core/Typography';
import TablePagination from '@material-ui/core/TablePagination';
import Chip from '@material-ui/core/Chip';
import { CircularProgress } from '@material-ui/core';
import Container from '@material-ui/core/Container';
import Box from '@material-ui/core/Box';
......@@ -30,7 +34,7 @@ const useStyles = makeStyles(theme => ({
/*className={classes.inline}*/
const Project = (props) => {
const Project = ({ project }) => {
return (
<ListItem alignItems="flex-start">
......@@ -40,7 +44,23 @@ const Project = (props) => {
</Avatar>
</ListItemAvatar>
<ListItemText
primary={ props.name }
primary={ project.name }
/>
<ListItemText
secondary={
<React.Fragment>
<Typography
component="div"
variant="body2"
color="textPrimary"
>
To languages:
</Typography>
{ Array.isArray(project.targetLanguages) && project.targetLanguages.map(l => (<Chip key={ l } label={ l }/>)) }
</React.Fragment>
}
/>
<ListItemText
secondary={
<React.Fragment>
<Typography
......@@ -48,9 +68,9 @@ const Project = (props) => {
variant="body2"
color="textPrimary"
>
to Scott, Alex, Jennifer
To languages:
</Typography>
<p>xxx</p>
{ Array.isArray(project.targetLanguages) && project.targetLanguages.map(l => (<Chip key={ l } label={ l }/>)) }
</React.Fragment>
}
/>
......@@ -67,11 +87,11 @@ export default (props) => {
const fetchProjects = async () => {
setLoading(true);
try {
let response = await backend("/projects");
let response = await backend("/api/projects");
setProjects(response.body.items);
} catch (e) {
//TODO
setProjects(false);
setProjects(e);
}
setLoading(false);
};
......@@ -80,20 +100,28 @@ export default (props) => {
fetchProjects();
}, []);
if (!Array.isArray(projects)) {
//an error occured
return <div>error</div>
if (!Array.isArray(projects) && projects) {
//an error occured - TODO display properly
return <div>{ projects.message || projects.msg }</div>
}
//construct list items
const list = [];
for (let i = 0; i < projects.length; i++) {
list.push(<Project key={ `project-${i}` } name={ projects[i].name } />);
list.push(<Project key={ `project-${i}` } project={ projects[i] } />);
if (i < projects.length - 1) {
list.push(<Divider key={ `divider-${i}` } />);
}
}
if (loading) {
list.push(
<div key="progress" style={{display: 'flex', justifyContent: 'center'}}>
<CircularProgress />
</div>
);
}
return (
<Container maxWidth="md">
<Box m={ 2 } className={ classes.root }>
......@@ -101,6 +129,24 @@ export default (props) => {
<List>
{ list }
</List>
{ projects.length ? (
<TablePagination
rowsPerPageOptions={[ 5, 10, 25 ]}
component="div"
count={ 4 }
rowsPerPage={ 20 }
page={ 0 }
backIconButtonProps={{
'aria-label': 'previous page',
}}
nextIconButtonProps={{
'aria-label': 'next page',
}}
onChangePage={ null }
onChangeRowsPerPage={ null }
/>
) : null }
</Paper>
</Box>
</Container>
......
......@@ -6,7 +6,7 @@ import { Container, Box, Paper, Button, TextField } from '@material-ui/core';
import { Formik } from 'formik';
import { SettingsContext } from 'lib/settings';
import { withRouter } from 'react-router-dom';
const useStyles = makeStyles(theme => ({
root: {
......@@ -17,25 +17,25 @@ const useStyles = makeStyles(theme => ({
inline: {
display: 'inline',
},
buttonBar: {
paddingTop: theme.spacing(1),
textAlign: "right"
},
button: {
margin: theme.spacing(1),
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
form: {
padding: theme.spacing(1)
},
}));
export default (props) => {
export default withRouter((props) => {
const classes = useStyles();
const settings = useContext(SettingsContext);
console.info(settings);
return (
<Formik
initialValues={{ username: settings.username || "", password: settings.password || "" }}
......@@ -74,15 +74,18 @@ export default (props) => {
<Box m={ 2 }>
<Paper>
<form onSubmit={ handleSubmit }>
<Box className={ classes.form }>
<TextField
label="Username"
className={ classes.textField }
name="username"
value={ values.username }
onChange={ handleChange }
fullWidth
margin="normal"
disabled={ isSubmitting }
disabled={ isSubmitting }
helperText={ errors.username }
error={ !!errors.username }
/>
......@@ -90,20 +93,27 @@ export default (props) => {
label="Password"
className={ classes.textField }
name="password"
type="password"
value={ values.password }
onChange={ handleChange }
fullWidth
margin="normal"
disabled={ isSubmitting }
helperText={ errors.email && touched.email && errors.email }
disabled={ isSubmitting }
helperText={ errors.email }
error={ !!errors.username }
/>
</Box>
<Button className={ classes.button } disabled={ isSubmitting }>
<Box className={ classes.buttonBar }>
<Button className={ classes.button } disabled={ isSubmitting } onClick={ () => props.history.push("/") }>
Cancel
</Button>
<Button type="submit" variant="contained" color="primary" className={classes.button} disabled={ isSubmitting }>
Ok
</Button>
</Box>
</form>
</Paper>
</Box>
......@@ -111,4 +121,4 @@ export default (props) => {
) }
</Formik>
);
}
\ No newline at end of file
});
\ No newline at end of file
import React from 'react';
import React, { useContext } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Container, Box, Paper } from '@material-ui/core';
import { Container, Box, Paper, Typography, Button } from '@material-ui/core';
import { SettingsContext } from 'lib/settings';
import { withRouter } from 'react-router-dom';
const useStyles = makeStyles(theme => ({
root: {
width: '100%',
backgroundColor: theme.palette.background.paper,
textAlign: "center"
},
p: {
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2)
},
button: {
margin: theme.spacing(1)
}
}));
export default () => {
export default withRouter(({ history }) => {
const classes = useStyles();
const settings = useContext(SettingsContext);
return (
<Container maxWidth="md">
<Box m={ 2 } className={ classes.root }>
<Paper>
XXX
</Paper>
<Typography variant="h2" className={ classes.title }>
Welcome
</Typography>
{ settings.fetched ? (
<React.Fragment>
<Typography className={ classes.p } component="div">
Start by choosing one of the actions below
</Typography>
<Typography className={ classes.p } component="div">
<Button variant="contained" color="primary" className={classes.button} onClick={ () => history.push("/projects") }>
List projects
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={ () => history.push("/settings") }>
Settings
</Button>
</Typography>
</React.Fragment>
) : (
<React.Fragment>
<Typography className={ classes.p } component="div">
Start by setting credentials first, please
</Typography>
<Typography className={ classes.p } component="div">
<Button variant="contained" color="primary" className={classes.button} onClick={ () => history.push("/settings") }>
Settings
</Button>
</Typography>
</React.Fragment>
)}
</Box>
</Container>
);
};
\ No newline at end of file
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment