Skip to content
Merged
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
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
],
"no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["state"] }],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/require-default-props": "off",
"import/no-unresolved": ["off"],
"import/no-absolute-path": ["off"],
"import/order": ["off"],
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ gem 'bcrypt', '~> 3.1.7'
gem 'bootsnap', '>= 1.4.2', require: false

gem 'active_model_serializers'
gem 'js-routes'
gem 'kaminari'
gem 'ransack'
gem 'responders'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ GEM
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
js-routes (2.2.4)
railties (>= 4)
json (2.6.2)
jsonapi-renderer (0.2.2)
kaminari (1.2.2)
Expand Down Expand Up @@ -278,6 +280,7 @@ DEPENDENCIES
capybara (>= 2.15)
factory_bot_rails
jbuilder (~> 2.7)
js-routes
kaminari
listen (~> 3.2)
pg (>= 0.18, < 2.0)
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/v1/tasks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def index
end

def create
params['task']['author_id'] = current_user.id
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это зачем?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Без этого, не создаются таски из-за отсутствия айди автора:

image

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мы же передаем author_id

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мб я что-то пропустил.
В логах при создании таски вот такие параметры:

web_1  | Started POST "/api/v1/tasks" for 172.30.0.1 at 2022-12-20 05:49:17 +0000
web_1  | Cannot render console from 172.30.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
web_1  | Processing by Api::V1::TasksController#create as JSON
web_1  |   Parameters: {"task"=>{"name"=>"fdas", "description"=>"asdf", "assignee_id"=>nil, "author_id"=>nil}}
web_1  |   User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
web_1  |   ↳ app/controllers/concerns/auth_helper.rb:21:in `current_user'

В следующем блоке точно передаются, если из селекта выбрать автора, а в противном случае тоже nil.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

странно 🤔 ну ладно

task = current_user.my_tasks.new(task_params)
task.save

Expand Down
21 changes: 21 additions & 0 deletions app/javascript/forms/TaskForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { pick, propOr } from 'ramda';

export default {
defaultAttributes(attributes) {
return {
name: '',
description: '',
...attributes,
};
},

attributesToSubmit(task) {
const pertmittedKeys = ['id', 'name', 'description'];

return {
...pick(pertmittedKeys, task),
assigneeId: propOr(null, 'id', task.assignee),
authorId: propOr(null, 'id', task.author),
};
},
};
86 changes: 86 additions & 0 deletions app/javascript/packs/components/AddPopup/AddPopup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { has } from 'ramda';

import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardHeader from '@material-ui/core/CardHeader';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import Modal from '@material-ui/core/Modal';
import TextField from '@material-ui/core/TextField';

import TaskForm from 'forms/TaskForm';

import useStyles from './useStyles';

function AddPopup({ onClose, onCardCreate }) {
const [task, changeTask] = useState(TaskForm.defaultAttributes());
const [isSaving, setSaving] = useState(false);
const [errors, setErrors] = useState({});
const handleCreate = () => {
setSaving(true);

onCardCreate(task).catch((error) => {
setSaving(false);
setErrors(error || {});

if (error instanceof Error) {
alert(`Creation Failed! Error: ${error.message}`);
}
});
};
const handleChangeTextField = (fieldName) => (event) => changeTask({ ...task, [fieldName]: event.target.value });
const styles = useStyles();

return (
<Modal className={styles.modal} open onClose={onClose}>
<Card className={styles.root}>
<CardHeader
action={
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
}
title="Add New Task"
/>
<CardContent>
<div className={styles.form}>
<TextField
error={has('name', errors)}
helperText={errors.name}
onChange={handleChangeTextField('name')}
value={task.name}
label="Name"
required
margin="dense"
/>
<TextField
error={has('description', errors)}
helperText={errors.description}
onChange={handleChangeTextField('description')}
value={task.description}
label="Description"
required
margin="dense"
/>
</div>
</CardContent>
<CardActions className={styles.actions}>
<Button disabled={isSaving} onClick={handleCreate} variant="contained" size="small" color="primary">
Add
</Button>
</CardActions>
</Card>
</Modal>
);
}

AddPopup.propTypes = {
onClose: PropTypes.func.isRequired,
onCardCreate: PropTypes.func.isRequired,
};

export default AddPopup;
3 changes: 3 additions & 0 deletions app/javascript/packs/components/AddPopup/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AddPopup from './AddPopup';

export default AddPopup;
26 changes: 26 additions & 0 deletions app/javascript/packs/components/AddPopup/useStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles(() => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
outline: 0,
},

root: {
width: 465,
},

actions: {
display: 'flex',
justifyContent: 'right',
},

form: {
display: 'flex',
flexDirection: 'column',
},
}));

export default useStyles;
53 changes: 53 additions & 0 deletions app/javascript/packs/components/ColumnHeader/ColumnHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';

import IconButton from '@material-ui/core/IconButton';
import SystemUpdateAltIcon from '@material-ui/icons/SystemUpdateAlt';

import useStyles from './useStyles';

function ColumnHeader({ column, onLoadMore }) {
const styles = useStyles();

const {
id,
title,
cards,
meta: { totalCount, currentPage, totalPages },
} = column;

const count = cards.length;

const handleLoadMore = () => onLoadMore(id, currentPage + 1);

return (
<div className={styles.root}>
<div className={styles.title}>
<b>{title}</b> ({count}/{totalCount || '…'})
</div>
<div className={styles.actions}>
{currentPage >= totalPages ? null : (
<IconButton aria-label="Load more" onClick={() => handleLoadMore()}>
<SystemUpdateAltIcon fontSize="small" />
</IconButton>
)}
</div>
</div>
);
}

ColumnHeader.propTypes = {
column: PropTypes.shape({
id: PropTypes.string,
title: PropTypes.string,
cards: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
meta: PropTypes.shape({
totalCount: PropTypes.number,
currentPage: PropTypes.number,
totalPages: PropTypes.number,
}),
}),
onLoadMore: PropTypes.func,
};

export default ColumnHeader;
3 changes: 3 additions & 0 deletions app/javascript/packs/components/ColumnHeader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ColumnHeader from './ColumnHeader';

export default ColumnHeader;
12 changes: 12 additions & 0 deletions app/javascript/packs/components/ColumnHeader/useStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles(() => ({
root: {
height: 42,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
},
}));

export default useStyles;
100 changes: 100 additions & 0 deletions app/javascript/packs/components/EditPopup/EditPopup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { isNil } from 'ramda';

import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CircularProgress from '@material-ui/core/CircularProgress';
import CardHeader from '@material-ui/core/CardHeader';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import Modal from '@material-ui/core/Modal';

import Form from './components/Form';

import useStyles from './useStyles';

function EditPopup({ cardId, onClose, onCardDestroy, onCardLoad, onCardUpdate }) {
const [task, setTask] = useState(null);
const [isSaving, setSaving] = useState(false);
const [errors, setErrors] = useState({});
const styles = useStyles();

useEffect(() => {
onCardLoad(cardId).then(setTask);
}, []);

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

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

if (error instanceof Error) {
alert(`Update Failed! Error: ${error.message}`);
}
});
};

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

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

alert(`Destrucion Failed! Error: ${error.message}`);
});
};
const isLoading = isNil(task);

return (
<Modal className={styles.modal} open onClose={onClose}>
<Card className={styles.root}>
<CardHeader
action={
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
}
title={isLoading ? 'Your task is loading. Please be patient.' : `Task # ${task.id} [${task.name}]`}
/>
<CardContent>
{isLoading ? (
<div className={styles.loader}>
<CircularProgress />
</div>
) : (
<Form errors={errors} onChange={setTask} task={task} />
)}
</CardContent>
<CardActions className={styles.actions}>
<Button disabled={isLoading || isSaving} onClick={handleCardUpdate} size="small" variant="contained" color="primary">
Update
</Button>
<Button
disabled={isLoading || isSaving}
onClick={handleCardDestroy}
size="small"
variant="contained"
color="secondary"
>
Destroy
</Button>
</CardActions>
</Card>
</Modal>
);
}

EditPopup.propTypes = {
cardId: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
onCardDestroy: PropTypes.func.isRequired,
onCardLoad: PropTypes.func.isRequired,
onCardUpdate: PropTypes.func.isRequired,
};

export default EditPopup;
Loading