Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/controllers/api/v1/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Api::V1::UsersController < Api::V1::ApplicationController
respond_to :json

def show
user = User.find(params[:id])

respond_with(user, serializer: UserSerializer)
end

def index
users = User.ransack(ransack_params).result.page(page).per(per_page)

respond_with(users, each_serializer: UserSerializer, meta: build_meta(users), root: 'items')
end
end
30 changes: 27 additions & 3 deletions app/javascript/components/AddPopup/AddPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import IconButton from '@material-ui/core/IconButton';
import Modal from '@material-ui/core/Modal';
import TextField from '@material-ui/core/TextField';

import TaskPresenter from 'presenters/TaskPresenter';

import TaskForm from 'forms/TaskForm';
import UserSelect from 'components/UserSelect';

import useStyles from './useStyles';

Expand All @@ -23,6 +26,8 @@ const AddPopup = (props) => {
const [isSaving, setSaving] = useState(false);
const [errors, setErrors] = useState({});

const styles = useStyles();

const handleCreate = () => {
setSaving(true);

Expand All @@ -38,7 +43,8 @@ const AddPopup = (props) => {
};

const handleChangeTextField = (fieldName) => (event) => changeTask({ ...task, [fieldName]: event.target.value });
const styles = useStyles();

const handleSelectChange = (fieldName) => (user) => changeTask({ ...task, [fieldName]: user });

return (
<Modal className={styles.modal} open onClose={onClose}>
Expand All @@ -57,7 +63,7 @@ const AddPopup = (props) => {
error={has('name', errors)}
helperText={errors.name}
onChange={handleChangeTextField('name')}
value={task.name}
value={TaskPresenter.name(task)}
label="Name"
required
margin="dense"
Expand All @@ -66,11 +72,29 @@ const AddPopup = (props) => {
error={has('description', errors)}
helperText={errors.description}
onChange={handleChangeTextField('description')}
value={task.description}
value={TaskPresenter.description(task)}
label="Description"
required
margin="dense"
/>
<UserSelect
isRequired
isDisabled={isSaving}
label="Author"
value={TaskPresenter.author(task)}
error={has('author', errors)}
helperText={errors.author}
onChange={handleSelectChange('author')}
/>
<UserSelect
isRequired
isDisabled={isSaving}
label="Assignee"
value={TaskPresenter.assignee(task)}
error={has('assignee', errors)}
helperText={errors.assignee}
onChange={handleSelectChange('assignee')}
/>
</div>
</CardContent>
<CardActions className={styles.actions}>
Expand Down
43 changes: 25 additions & 18 deletions app/javascript/components/EditPopup/EditPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { isNil } from 'ramda';

import TaskPresenter from 'presenters/TaskPresenter';

import Form from './components/Form';
import Modal from '@material-ui/core/Modal';
import Card from '@material-ui/core/Card';
Expand All @@ -24,35 +26,44 @@ const EditPopup = (props) => {
const styles = useStyles();

const isLoading = isNil(task);
const isEditButtonDisabled = isLoading && isSaving;
const isDataLoading = isLoading && isSaving;

const handleCardUpdate = () => {
const handleCardUpdate = async () => {
setSaving(true);

onCardUpdate(task).catch((error) => {
try {
await onCardUpdate(task);
} catch (error) {
setSaving(false);
setErrors(error || {});

if (error instanceof Error) {
// eslint-disable-next-line no-alert
alert(`Update Failed! Error: ${error.message}`);
}
});
}
};

const handleCardDestroy = () => {
const handleCardDestroy = async () => {
setSaving(true);

onCardDestroy(task).catch((error) => {
try {
await onCardDestroy(task);
} catch (error) {
setSaving(false);

// eslint-disable-next-line no-alert
alert(`Destruction Failed! Error: ${error.message}`);
});
}
};

useEffect(() => {
onCardLoad(cardId).then(setTask);
const initCard = async () => {
const card = await onCardLoad(cardId);
setTask(card);
};

initCard();
}, []);

return (
Expand All @@ -64,29 +75,25 @@ const EditPopup = (props) => {
<CloseIcon />
</IconButton>
}
title={isLoading ? 'Your task is loading. Please be patient.' : `Task # ${task.id} [${task.name}]`}
title={
isLoading ? 'Your task is loading. Please be patient.' : `Task # ${task.id} [${TaskPresenter.name(task)}]`
}
/>
<CardContent>
{isLoading ? (
<div className={styles.loader}>
<CircularProgress />
</div>
) : (
<Form errors={errors} onChange={setTask} task={task} />
<Form isDataLoading={isDataLoading} errors={errors} onChange={setTask} task={task} />
)}
</CardContent>
<CardActions className={styles.actions}>
<Button
disabled={isEditButtonDisabled}
size="small"
variant="contained"
color="primary"
onClick={handleCardUpdate}
>
<Button disabled={isDataLoading} size="small" variant="contained" color="primary" onClick={handleCardUpdate}>
Update
</Button>
<Button
disabled={isEditButtonDisabled}
disabled={isDataLoading}
size="small"
variant="contained"
color="secondary"
Expand Down
38 changes: 33 additions & 5 deletions app/javascript/components/EditPopup/components/Form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import { has } from 'ramda';

import TextField from '@material-ui/core/TextField';

import UserSelect from 'components/UserSelect';

import useStyles from './useStyles';

const Form = ({ errors, onChange, task }) => {
const handleChangeTextField = (fieldName) => (event) => onChange({ ...task, [fieldName]: event.target.value });
import TaskPresenter from 'presenters/TaskPresenter';

const Form = (props) => {
const { isDataLoading, errors, onChange, task } = props;

const styles = useStyles();

const handleChangeTextField = (fieldName) => (event) => onChange({ ...task, [fieldName]: event.target.value });

const handleSelectChange = (fieldName) => (user) => onChange({ ...task, [fieldName]: user });

return (
<form className={styles.root}>
<TextField
error={has('name', errors)}
helperText={errors.name}
onChange={handleChangeTextField('name')}
value={task.name}
value={TaskPresenter.name(task)}
label="Name"
required
margin="dense"
Expand All @@ -25,19 +34,38 @@ const Form = ({ errors, onChange, task }) => {
error={has('description', errors)}
helperText={errors.description}
onChange={handleChangeTextField('description')}
value={task.description}
value={TaskPresenter.description(task)}
label="Description"
required
multiline
margin="dense"
/>
<UserSelect
isRequired
isDisabled={isDataLoading}
label="Author"
value={TaskPresenter.author(task)}
error={has('author', errors)}
helperText={errors.author}
onChange={handleSelectChange('author')}
/>
<UserSelect
isRequired
isDisabled={isDataLoading}
label="Assignee"
value={TaskPresenter.assignee(task)}
error={has('assignee', errors)}
helperText={errors.assignee}
onChange={handleSelectChange('assignee')}
/>
</form>
);
};

Form.propTypes = {
isDataLoading: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
task: PropTypes.shape().isRequired,
task: TaskPresenter.shape().isRequired,
errors: PropTypes.shape({
name: PropTypes.arrayOf(PropTypes.string),
description: PropTypes.arrayOf(PropTypes.string),
Expand Down
8 changes: 5 additions & 3 deletions app/javascript/components/Task/Task.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import EditIcon from '@material-ui/icons/Edit';

import TaskPresenter from 'presenters/TaskPresenter';

import useStyles from './useStyles';

const Task = (props) => {
Expand All @@ -25,18 +27,18 @@ const Task = (props) => {

return (
<Card className={styles.root}>
<CardHeader title={task.name} action={action} />
<CardHeader title={TaskPresenter.name(task)} action={action} />
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
{task.description}
{TaskPresenter.description(task)}
</Typography>
</CardContent>
</Card>
);
};

Task.propTypes = {
task: PropTypes.shape().isRequired,
task: TaskPresenter.shape().isRequired,
onClick: PropTypes.func.isRequired,
};

Expand Down
18 changes: 9 additions & 9 deletions app/javascript/components/TaskBoard/TaskBoard.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Container from '@material-ui/core/Container';
import Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';

import TaskPresenter from 'presenters/TaskPresenter';

import useStyles from './useStyles';

import TaskForm from 'forms/TaskForm';
Expand Down Expand Up @@ -109,22 +111,20 @@ const TaskBoard = () => {
const attributes = TaskForm.attributesToSubmit(task);

return TasksRepository.update(task.id, attributes).then(() => {
loadColumnInitial(task.state);
loadColumnInitial(TaskPresenter.state(task));
handleModalClose();
});
};
Comment thread
alex-ismailov marked this conversation as resolved.

const handleTaskDestroy = (task) => {
const { id, state } = task;

return TasksRepository.destroy(id).then(() => {
loadColumnInitial(state);
const handleTaskDestroy = (task) =>
TasksRepository.destroy(task.id).then(() => {
loadColumnInitial(TaskPresenter.state(task));
handleModalClose();
});
};

const handleCardDragEnd = (task, source, destination) => {
const transition = task.transitions.find(({ to }) => destination.toColumnId === to);
const taskTransitions = TaskPresenter.transitions(task);
const transition = taskTransitions.find(({ to }) => destination.toColumnId === to);

if (!transition) {
return null;
Expand All @@ -147,7 +147,7 @@ const TaskBoard = () => {
const attributes = TaskForm.attributesToSubmit(params);

return TasksRepository.create(attributes).then(({ data: { task } }) => {
loadColumnInitial(task.state);
loadColumnInitial(TaskPresenter.state(task));
setMode(MODES.NONE);
});
};
Expand Down
Loading