Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/GH-130: Add action item life duration status #132

Merged
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
37 changes: 35 additions & 2 deletions app/controllers/api/action_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
module API
class ActionItemsController < API::ApplicationController
before_action :set_board, :set_action_item
before_action do
authorize! @action_item, context: { board: @board }
end

def update
authorize! @action_item
if @action_item.update(body: params.permit(:edited_body)[:edited_body])
render json: { updated_body: @action_item.body }, status: :ok
else
Expand All @@ -14,14 +16,45 @@ def update
end

def destroy
authorize! @action_item
if @action_item.destroy
head :no_content
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
end

def move
if @action_item.move!(@board)
head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
end

def close
if @action_item.close!
head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
end

def complete
if @action_item.complete!
head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
end

def reopen
if @action_item.reopen!
head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
end

private

def set_board
Expand Down
40 changes: 0 additions & 40 deletions app/controllers/boards/action_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

module Boards
class ActionItemsController < Boards::ApplicationController
before_action :set_action_item, except: :create
authorize :board, through: :current_board
before_action except: :create do
authorize! @action_item
end

def create
action_item = @board.action_items.build(action_item_params)
Expand All @@ -18,38 +14,6 @@ def create
end
end

def move
if @action_item.move!(@board)
redirect_to @board, notice: 'Action Item was successfully moved'
else
redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
end
end

def close
if @action_item.close!
redirect_to @board, notice: 'Action Item was successfully closed'
else
redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
end
end

def complete
if @action_item.complete!
redirect_to @board, notice: 'Action Item was successfully completed'
else
redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
end
end

def reopen
if @action_item.reopen!
redirect_to @board, notice: 'Action Item was successfully reopend'
else
redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
end
end

private

def current_board
Expand All @@ -59,9 +23,5 @@ def current_board
def action_item_params
params.require(:action_item).permit(:status, :body)
end

def set_action_item
@action_item = ActionItem.find(params[:id])
end
end
end
7 changes: 7 additions & 0 deletions app/javascript/components/ActionItem/ActionItem.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
.box { margin-bottom: 1.5rem; }

.green_font {color: hsl(141, 71%, 48%)}
.yellow_font {color: hsl(48, 100%, 67%)}
.red_font {color: hsl(348, 100%, 61%)}

.green_bg {background-color: hsl(141, 71%, 48%)}
.yellow_bg {background-color: hsl(48, 100%, 67%)}
.red_bg {background-color: hsl(348, 100%, 61%)}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
textarea {
overflow: hidden;
resize: none;
width: 100%;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: hsl(0, 0%, 29%);
}
textarea {
overflow: hidden;
resize: none;
width: 100%;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: hsl(0, 0%, 29%);
}

.text { overflow-wrap: break-word; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.fa-chevron-right {
margin-right: -2px;
margin-left: -2px;
font-size: 16px;
stroke: white;
stroke-width: 10;
}
62 changes: 57 additions & 5 deletions app/javascript/components/ActionItem/ActionItemFooter/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from "react"

import TransitionButton from "../TransitionButton"
import "./ActionItemFooter.css"

class ActionItemFooter extends React.Component {
constructor(props) {
super(props);
}

handleClick = () => {
handleDeleteClick = () => {
fetch(`/api/${window.location.pathname}/action_items/${this.props.id}`, {
method: 'DELETE',
headers: {
Expand All @@ -25,18 +28,67 @@ class ActionItemFooter extends React.Component {
});
}

handleMoveClick = () => {
fetch(`/api/${window.location.pathname}/action_items/${this.props.id}/move`, {
Copy link

Choose a reason for hiding this comment

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

хорошая практика - выносить запросы к api в отдельный метод в отдельном файле в папке компонента
например requests.js или что-то подобное, а внутри него него должна быть функция move(), которую уже можно будет вызвать отсюда

Copy link
Contributor

Choose a reason for hiding this comment

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

хорошая практика - выносить запросы к api в отдельный метод в отдельном файле в папке компонента
например requests.js или что-то подобное, а внутри него него должна быть функция move(), которую уже можно будет вызвать отсюда

уже писал об этом, ребята решили сделать это отдельным PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

understood! спасибо за подробное разьяснение по расположению. там где-то 15 таких фетчей. я возьму следующим таском их все разобрать.

Copy link

Choose a reason for hiding this comment

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

огонь!

method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").getAttribute('content')
}
}).then((result) => {
if (result.status == 200) {
window.location.reload();
}
else { throw result }
}).catch((error) => {
error.json().then( errorHash => {
console.log(errorHash.error)
})
});
}
nikolaiprivalov marked this conversation as resolved.
Show resolved Hide resolved

pickColor(num) {
nikolaiprivalov marked this conversation as resolved.
Show resolved Hide resolved
switch(true) {
case [1,2].includes(num):
return 'green';
case [3].includes(num):
return 'yellow';
default:
return 'red';
}
}

generateChevrons = () => {
const times_moved = this.props.times_moved;
const icon = <i className={`fas fa-chevron-right ${this.pickColor(times_moved)}_font`}></i>;
const chevrons = Array.from({ length: times_moved }, () => icon)
return chevrons
nikolaiprivalov marked this conversation as resolved.
Show resolved Hide resolved
};

render () {
const { deletable } = this.props;
const confirmMessage = 'Are you sure you want to delete this ActionItem?';
const { id, deletable, movable, transitionable } = this.props;
const confirmDeleteMessage = 'Are you sure you want to delete this ActionItem?';
const confirmMoveMessage = 'Are you sure you want to move this ActionItem?';

return (
<div>
<hr style={{margin: '0.5rem'}}/>
<div className='chevrons'>{this.generateChevrons()}</div>

{transitionable && transitionable.can_close && <TransitionButton id={id} action='close'/>}
{transitionable && transitionable.can_complete && <TransitionButton id={id} action='complete'/>}
{transitionable && transitionable.can_reopen && <TransitionButton id={id} action='reopen'/>}
{movable && <button onClick={() => {window.confirm(confirmMoveMessage) && this.handleMoveClick()}}>
move
</button>}

<div>
<a onClick={() => {window.confirm(confirmMessage) && this.handleClick()}}>
{deletable && <a onClick={() => {window.confirm(confirmDeleteMessage) && this.handleDeleteClick()}}>
delete
</a>
</a>}
</div>

</div>
);
}
Expand Down
38 changes: 38 additions & 0 deletions app/javascript/components/ActionItem/TransitionButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react"

class TransitionButton extends React.Component {
constructor(props) {
super(props)
this.state = {}
}

handleClick = () => {
fetch(`/api/${window.location.pathname}/action_items/${this.props.id}/${this.props.action}`, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").getAttribute('content')
}
}).then((result) => {
if (result.status == 200) {
window.location.reload();
}
else { throw result }
}).catch((error) => {
error.json().then( errorHash => {
console.log(errorHash.error)
})
});
}
nikolaiprivalov marked this conversation as resolved.
Show resolved Hide resolved

render () {
return (
<button onClick={() => {this.handleClick()}}>
{this.props.action}
</button>
);
}
}

export default TransitionButton
28 changes: 23 additions & 5 deletions app/javascript/components/ActionItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,34 @@ class ActionItem extends React.Component {
hideActionItem = () => {
this.setState({ActionItemStyle: {display: 'none'}});
}

pickColor = () => {
nikolaiprivalov marked this conversation as resolved.
Show resolved Hide resolved
switch(this.props.status) {
case 'done':
return 'green';
case 'closed':
return 'red';
default:
return null;
}
}

render () {
const { id, body, deletable, editable } = this.props;
const { id, body, times_moved, deletable, editable, movable, transitionable } = this.props;
const footerNotEmpty = deletable || movable || transitionable || (times_moved != 0);

return (
<div className='box' style={this.state.ActionItemStyle}>
<div className={`box ${this.pickColor()}_bg`} style={this.state.ActionItemStyle}>
<ActionItemBody id={id}
editable={editable}
body={body}/>
{deletable && <ActionItemFooter id={id} hideActionItem={this.hideActionItem}/>}
editable={editable}
body={body}/>
{footerNotEmpty && <ActionItemFooter id={id}
deletable={deletable}
times_moved={times_moved}
movable={movable}
transitionable={transitionable}
hideActionItem={this.hideActionItem}
paintActionItem={this.paintActionItem}/>}
</div>
);
}
Expand Down
1 change: 0 additions & 1 deletion app/javascript/components/Card/Card.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@
width: 1.5rem;
border-radius: 1rem;
}

16 changes: 8 additions & 8 deletions app/javascript/components/Card/CardBody/CardBody.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
textarea {
overflow: hidden;
resize: none;
width: 100%;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: hsl(0, 0%, 29%);
}
overflow: hidden;
resize: none;
width: 100%;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: hsl(0, 0%, 29%);
}

.text { overflow-wrap: break-word; }
1 change: 1 addition & 0 deletions app/models/action_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ActionItem < ApplicationRecord

def move!(board)
self.board_id = board.id
increment(:times_moved)
save
end
end
20 changes: 19 additions & 1 deletion app/policies/api/action_item_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module API
class ActionItemPolicy < ApplicationPolicy
authorize :board, allow_nil: true

def update?
check?(:user_is_creator?)
end
Expand All @@ -10,8 +12,24 @@ def destroy?
check?(:user_is_creator?)
end

def move?
check?(:user_is_creator?) && record.pending?
end

def close?
check?(:user_is_creator?) && record.may_close?
end

def complete?
check?(:user_is_creator?) && record.may_complete?
end

def reopen?
check?(:user_is_creator?) && record.may_reopen?
end

def user_is_creator?
record.board.creator?(user)
board ? board.creator?(user) : record.board.creator?(user)
end
end
end
Loading