Commit 4e0037a2 by Jan Hrabal

UI improvements

parent ef6d3802
...@@ -76,6 +76,18 @@ ...@@ -76,6 +76,18 @@
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
</dependency> </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 --> <!-- testing -->
<dependency> <dependency>
<groupId>junit</groupId> <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; package com.jh.memsource.project;
import org.springframework.beans.factory.annotation.Autowired; 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.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import com.jh.memsource.model.PagedList; import com.jh.memsource.model.PagedList;
import com.jh.memsource.model.Project; import com.jh.memsource.model.Project;
import com.jh.memsource.model.Settings;
import com.jh.memsource.settings.SettingsService; import com.jh.memsource.settings.SettingsService;
@RestController @RestController
...@@ -20,8 +23,12 @@ public class ProjectApiController { ...@@ -20,8 +23,12 @@ public class ProjectApiController {
@GetMapping("/projects") @GetMapping("/projects")
public @ResponseBody PagedList<Project> getSettings() { public @ResponseBody PagedList<Project> getSettings(Integer page, Integer pageSize) {
return service.getProjects(settingsService.getSettings()); 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 { ...@@ -32,9 +32,9 @@ public class SettingsApiController {
@PutMapping("/settings") @PutMapping("/settings")
@ResponseStatus(HttpStatus.ACCEPTED) @ResponseStatus(HttpStatus.ACCEPTED)
public @ResponseBody String saveSettings(@RequestBody Settings settings) { public @ResponseBody Settings saveSettings(@RequestBody Settings settings) {
service.saveSettings(settings); service.saveSettings(settings);
return "OK"; return settings;
} }
} }
...@@ -11,3 +11,5 @@ spring.jpa.properties.hibernate.ddl-auto=none ...@@ -11,3 +11,5 @@ spring.jpa.properties.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 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 ...@@ -11,3 +11,5 @@ spring.jpa.properties.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 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'; ...@@ -17,7 +17,7 @@ import injectSettings from 'lib/settings';
const App = injectSettings((props) => { const App = injectSettings((props) => {
const settings = props.settings; const settings = props.settings;
if (settings && settings.fetching) { if (settings && settings.fetching) {
return <CircularProgress size={ 60 } /> return <div key="progress" style={{display: 'flex', justifyContent: 'center'}}><CircularProgress size={ 60 } /></div>
} }
return ( return (
......
...@@ -8,13 +8,13 @@ import backend from 'lib/backend'; ...@@ -8,13 +8,13 @@ import backend from 'lib/backend';
export const fetchSettings = async () => { export const fetchSettings = async () => {
let response = await backend("/settings"); let response = await backend("/api/settings");
return response.body; return response.body;
} }
export const saveSettings = async ({ username, password }) => { export const saveSettings = async ({ username, password }) => {
await backend("/settings", "PUT", { await backend("/api/settings", "PUT", {
body: { body: {
username, username,
password password
...@@ -53,13 +53,18 @@ export default (Wrapped) => (props) => { ...@@ -53,13 +53,18 @@ export default (Wrapped) => (props) => {
updateSettings({ ...settings, ...response, fetching: false, fetched: true }); updateSettings({ ...settings, ...response, fetching: false, fetched: true });
} catch (e) { } catch (e) {
updateSettings({ ...settings, fetching: false, fetched: false }); updateSettings({ ...settings, fetching: false, fetched: false });
console.error(e);
} }
} }
async function save(values) { async function save(values) {
updateSettings({ ...settings, saving: true }); updateSettings({ ...settings, saving: true });
let response = await saveSettings(values); try {
updateSettings({ ...settings, ...values, saving: false }); let response = await saveSettings(values);
updateSettings({ ...settings, ...values, saving: false, fetched: true });
} catch (e) {
console.error(e);
}
} }
//try to fetch settings once the component is mounted //try to fetch settings once the component is mounted
......
...@@ -9,6 +9,10 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar'; ...@@ -9,6 +9,10 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
import WorkIcon from '@material-ui/icons/Work'; import WorkIcon from '@material-ui/icons/Work';
import Typography from '@material-ui/core/Typography'; 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 Container from '@material-ui/core/Container';
import Box from '@material-ui/core/Box'; import Box from '@material-ui/core/Box';
...@@ -30,7 +34,7 @@ const useStyles = makeStyles(theme => ({ ...@@ -30,7 +34,7 @@ const useStyles = makeStyles(theme => ({
/*className={classes.inline}*/ /*className={classes.inline}*/
const Project = (props) => { const Project = ({ project }) => {
return ( return (
<ListItem alignItems="flex-start"> <ListItem alignItems="flex-start">
...@@ -40,7 +44,23 @@ const Project = (props) => { ...@@ -40,7 +44,23 @@ const Project = (props) => {
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <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={ secondary={
<React.Fragment> <React.Fragment>
<Typography <Typography
...@@ -48,9 +68,9 @@ const Project = (props) => { ...@@ -48,9 +68,9 @@ const Project = (props) => {
variant="body2" variant="body2"
color="textPrimary" color="textPrimary"
> >
to Scott, Alex, Jennifer To languages:
</Typography> </Typography>
<p>xxx</p> { Array.isArray(project.targetLanguages) && project.targetLanguages.map(l => (<Chip key={ l } label={ l }/>)) }
</React.Fragment> </React.Fragment>
} }
/> />
...@@ -67,11 +87,11 @@ export default (props) => { ...@@ -67,11 +87,11 @@ export default (props) => {
const fetchProjects = async () => { const fetchProjects = async () => {
setLoading(true); setLoading(true);
try { try {
let response = await backend("/projects"); let response = await backend("/api/projects");
setProjects(response.body.items); setProjects(response.body.items);
} catch (e) { } catch (e) {
//TODO //TODO
setProjects(false); setProjects(e);
} }
setLoading(false); setLoading(false);
}; };
...@@ -80,20 +100,28 @@ export default (props) => { ...@@ -80,20 +100,28 @@ export default (props) => {
fetchProjects(); fetchProjects();
}, []); }, []);
if (!Array.isArray(projects)) { if (!Array.isArray(projects) && projects) {
//an error occured //an error occured - TODO display properly
return <div>error</div> return <div>{ projects.message || projects.msg }</div>
} }
//construct list items //construct list items
const list = []; const list = [];
for (let i = 0; i < projects.length; i++) { 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) { if (i < projects.length - 1) {
list.push(<Divider key={ `divider-${i}` } />); list.push(<Divider key={ `divider-${i}` } />);
} }
} }
if (loading) {
list.push(
<div key="progress" style={{display: 'flex', justifyContent: 'center'}}>
<CircularProgress />
</div>
);
}
return ( return (
<Container maxWidth="md"> <Container maxWidth="md">
<Box m={ 2 } className={ classes.root }> <Box m={ 2 } className={ classes.root }>
...@@ -101,6 +129,24 @@ export default (props) => { ...@@ -101,6 +129,24 @@ export default (props) => {
<List> <List>
{ list } { 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> </Paper>
</Box> </Box>
</Container> </Container>
......
...@@ -6,7 +6,7 @@ import { Container, Box, Paper, Button, TextField } from '@material-ui/core'; ...@@ -6,7 +6,7 @@ import { Container, Box, Paper, Button, TextField } from '@material-ui/core';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { SettingsContext } from 'lib/settings'; import { SettingsContext } from 'lib/settings';
import { withRouter } from 'react-router-dom';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
...@@ -17,25 +17,25 @@ const useStyles = makeStyles(theme => ({ ...@@ -17,25 +17,25 @@ const useStyles = makeStyles(theme => ({
inline: { inline: {
display: 'inline', display: 'inline',
}, },
buttonBar: {
paddingTop: theme.spacing(1),
textAlign: "right"
},
button: { button: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
textField: { form: {
marginLeft: theme.spacing(1), padding: theme.spacing(1)
marginRight: theme.spacing(1),
}, },
})); }));
export default (props) => { export default withRouter((props) => {
const classes = useStyles(); const classes = useStyles();
const settings = useContext(SettingsContext); const settings = useContext(SettingsContext);
console.info(settings);
return ( return (
<Formik <Formik
initialValues={{ username: settings.username || "", password: settings.password || "" }} initialValues={{ username: settings.username || "", password: settings.password || "" }}
...@@ -74,36 +74,46 @@ export default (props) => { ...@@ -74,36 +74,46 @@ export default (props) => {
<Box m={ 2 }> <Box m={ 2 }>
<Paper> <Paper>
<form onSubmit={ handleSubmit }> <form onSubmit={ handleSubmit }>
<TextField <Box className={ classes.form }>
label="Username" <TextField
className={ classes.textField } label="Username"
name="username" className={ classes.textField }
value={ values.username } name="username"
onChange={ handleChange } value={ values.username }
margin="normal" onChange={ handleChange }
disabled={ isSubmitting }
fullWidth
helperText={ errors.username } margin="normal"
error={ !!errors.username }
/> disabled={ isSubmitting }
<TextField helperText={ errors.username }
label="Password" error={ !!errors.username }
className={ classes.textField } />
name="password" <TextField
value={ values.password } label="Password"
onChange={ handleChange } className={ classes.textField }
margin="normal" name="password"
disabled={ isSubmitting } type="password"
value={ values.password }
helperText={ errors.email && touched.email && errors.email } onChange={ handleChange }
/>
fullWidth
margin="normal"
disabled={ isSubmitting }
helperText={ errors.email }
error={ !!errors.username }
/>
</Box>
<Button className={ classes.button } disabled={ isSubmitting }> <Box className={ classes.buttonBar }>
Cancel <Button className={ classes.button } disabled={ isSubmitting } onClick={ () => props.history.push("/") }>
</Button> Cancel
<Button type="submit" variant="contained" color="primary" className={classes.button} disabled={ isSubmitting }> </Button>
Ok <Button type="submit" variant="contained" color="primary" className={classes.button} disabled={ isSubmitting }>
</Button> Ok
</Button>
</Box>
</form> </form>
</Paper> </Paper>
</Box> </Box>
...@@ -111,4 +121,4 @@ export default (props) => { ...@@ -111,4 +121,4 @@ export default (props) => {
) } ) }
</Formik> </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 { 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 => ({ const useStyles = makeStyles(theme => ({
root: { root: {
width: '100%', textAlign: "center"
backgroundColor: theme.palette.background.paper, },
p: {
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2)
}, },
button: {
margin: theme.spacing(1)
}
})); }));
export default () => { export default withRouter(({ history }) => {
const classes = useStyles(); const classes = useStyles();
const settings = useContext(SettingsContext);
return ( return (
<Container maxWidth="md"> <Container maxWidth="md">
<Box m={ 2 } className={ classes.root }> <Box m={ 2 } className={ classes.root }>
<Paper> <Typography variant="h2" className={ classes.title }>
XXX Welcome
</Paper> </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> </Box>
</Container> </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