273 lines
8.1 KiB
TypeScript
273 lines
8.1 KiB
TypeScript
import React, { Fragment } from 'react';
|
|
import { Page } from '../Page';
|
|
import { connect, DispatchProp } from 'react-redux';
|
|
import Board from '@lourenci/react-kanban';
|
|
import { fetchBoards } from '../../store/actions/boards';
|
|
import { createIssue } from '../../store/actions/issues';
|
|
import { buildKanboard, moveCard } from '../../store/actions/kanboards';
|
|
import { Loader } from '../Loader';
|
|
import { IssueCard } from './IssueCard';
|
|
import { Modal } from '../Modal';
|
|
|
|
export interface BoardPageProps extends DispatchProp {
|
|
board: any
|
|
kanboard: any
|
|
}
|
|
export class BoardPage extends React.Component<BoardPageProps> {
|
|
|
|
state = {
|
|
newCardModalActive: false,
|
|
newCardLaneID: 0,
|
|
newCard: {
|
|
title: "",
|
|
body: "",
|
|
project: ""
|
|
},
|
|
compactMode: true,
|
|
}
|
|
|
|
onNewCardTitleChange: (evt: any) => void;
|
|
onNewCardBodyChange: (evt: any) => void;
|
|
onNewCardProjectChange: (evt: any) => void;
|
|
|
|
constructor(props: BoardPageProps) {
|
|
super(props);
|
|
this.renderLaneHeader = this.renderLaneHeader.bind(this);
|
|
this.onNewCardClick = this.onNewCardClick.bind(this);
|
|
this.onNewCardCloseClick = this.onNewCardCloseClick.bind(this);
|
|
this.onNewCardTitleChange = this.onNewCardAttrChange.bind(this, 'title');
|
|
this.onNewCardBodyChange = this.onNewCardAttrChange.bind(this, 'body');
|
|
this.onNewCardProjectChange = this.onNewCardAttrChange.bind(this, 'project');
|
|
this.onNewCardSaveClick = this.onNewCardSaveClick.bind(this);
|
|
}
|
|
|
|
render() {
|
|
const { board } = this.props;
|
|
return (
|
|
<Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}>
|
|
<div className="container is-fluid">
|
|
{this.renderBoard()}
|
|
</div>
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
renderBoard() {
|
|
const { kanboard } = this.props;
|
|
|
|
if (!kanboard) {
|
|
return <Loader></Loader>
|
|
}
|
|
|
|
return (
|
|
<Fragment>
|
|
<div className="is-clearfix has-margin-top-normal">
|
|
<div className="is-pulled-right">
|
|
<div className="field">
|
|
<input id="compactMode"
|
|
checked={this.state.compactMode}
|
|
onChange={this.onCompactModeChange.bind(this)}
|
|
type="checkbox"
|
|
className="switch is-rtl"
|
|
name="compactMode"
|
|
/>
|
|
<label htmlFor="compactMode">Mode compact</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="kanboard-container is-fullheight">
|
|
<Board disableLaneDrag={true}
|
|
renderCard={this.renderCard.bind(this)}
|
|
renderLaneHeader={this.renderLaneHeader}
|
|
onCardDragEnd={this.onCardDragEnd.bind(this)}>
|
|
{kanboard}
|
|
</Board>
|
|
{ this.renderNewCardModal() }
|
|
</div>
|
|
</Fragment>
|
|
|
|
);
|
|
}
|
|
|
|
renderNewCardModal() {
|
|
const { newCardModalActive, newCardLaneID } = this.state;
|
|
const { board } = this.props;
|
|
if (!board || !newCardLaneID) return null;
|
|
return (
|
|
<Modal active={newCardModalActive}>
|
|
<div className="new-card-modal">
|
|
<article className="message">
|
|
<div className="message-header">
|
|
<p><span>"{board.lanes[newCardLaneID].title}" - Nouveau ticket</span></p>
|
|
<button className="delete" aria-label="delete" onClick={this.onNewCardCloseClick}></button>
|
|
</div>
|
|
<div className="message-body">
|
|
<div className="field">
|
|
<div className="control">
|
|
<input className="input is-medium" type="text"
|
|
placeholder="Titre de votre ticket..."
|
|
value={this.state.newCard.title}
|
|
onChange={this.onNewCardTitleChange} />
|
|
</div>
|
|
</div>
|
|
<div className="field">
|
|
<div className="control">
|
|
<textarea className="textarea"
|
|
placeholder="Description du nouveau ticket..."
|
|
value={this.state.newCard.body}
|
|
onChange={this.onNewCardBodyChange}
|
|
rows={10}>
|
|
</textarea>
|
|
</div>
|
|
</div>
|
|
<div className="field">
|
|
<div className="control is-expanded">
|
|
<div className="select is-fullwidth">
|
|
<select
|
|
value={this.state.newCard.project}
|
|
onChange={this.onNewCardProjectChange}>
|
|
{
|
|
board.projects.map((p: any, i: number) => {
|
|
return <option key={`new-card-project-${i}`} value={p}>{p}</option>
|
|
})
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="field is-grouped is-grouped-right">
|
|
<p className="control">
|
|
<a className="button is-light"
|
|
onClick={this.onNewCardCloseClick}>
|
|
Annuler
|
|
</a>
|
|
</p>
|
|
<p className="control">
|
|
<a className="button is-primary"
|
|
onClick={this.onNewCardSaveClick}>
|
|
Enregistrer
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
renderCard(card: any) {
|
|
return <IssueCard compact={this.state.compactMode} card={card} />;
|
|
}
|
|
|
|
renderLaneHeader(lane: any) {
|
|
return (
|
|
<div className="kanboard-lane">
|
|
<div className="level">
|
|
<div className="level-left">
|
|
<h3 className="level-item is-size-3">{lane.title}</h3>
|
|
</div>
|
|
<div className="level-right">
|
|
<button className="button is-light level-item is-medium"
|
|
onClick={this.onNewCardClick.bind(this, lane.id)}>
|
|
<span className="icon">
|
|
<i className="fas fa-plus" aria-hidden="true"></i>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
onCardDragEnd(source: any, dest: any) {
|
|
const { board } = this.props;
|
|
this.props.dispatch(moveCard(
|
|
board.id,
|
|
source.fromLaneId,
|
|
source.fromPosition,
|
|
dest.toLaneId,
|
|
dest.toPosition
|
|
));
|
|
}
|
|
|
|
componentDidMount() {
|
|
const { board } = this.props;
|
|
if (!board) {
|
|
this.requestBoardsUpdate();
|
|
return
|
|
}
|
|
|
|
this.requestBuildKanboard();
|
|
}
|
|
|
|
onNewCardClick(laneID: string) {
|
|
const { board } = this.props;
|
|
this.setState({
|
|
newCardModalActive: true,
|
|
newCardLaneID: laneID,
|
|
newCard: {
|
|
title: "",
|
|
body: "",
|
|
project: board.projects[0],
|
|
}
|
|
});
|
|
}
|
|
|
|
onNewCardCloseClick() {
|
|
this.setState({ newCardModalActive: false });
|
|
}
|
|
|
|
onNewCardAttrChange(attrName: string, evt: React.ChangeEvent) {
|
|
const value = (evt.target as HTMLInputElement).value;
|
|
this.setState((state: any) => {
|
|
return {
|
|
...state,
|
|
newCard: {
|
|
...state.newCard,
|
|
[attrName]: value,
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
onNewCardSaveClick() {
|
|
const { newCard, newCardLaneID } = this.state;
|
|
const { board } = this.props;
|
|
this.setState({ newCardModalActive: false });
|
|
this.props.dispatch(createIssue(
|
|
newCard.project,
|
|
newCard.title,
|
|
newCard.body,
|
|
board.lanes[newCardLaneID].issueLabel
|
|
));
|
|
}
|
|
|
|
componentDidUpdate(prevProps: any) {
|
|
if (prevProps.board !== this.props.board) this.requestBuildKanboard();
|
|
}
|
|
|
|
requestBoardsUpdate() {
|
|
this.props.dispatch(fetchBoards());
|
|
}
|
|
|
|
requestBuildKanboard() {
|
|
const { board } = this.props;
|
|
if (!board) return;
|
|
this.props.dispatch(buildKanboard(board));
|
|
}
|
|
|
|
onCompactModeChange(evt: React.ChangeEvent) {
|
|
const checked = (evt.currentTarget as HTMLInputElement).checked;
|
|
this.setState(state => ({...state, compactMode: checked }));
|
|
}
|
|
|
|
}
|
|
|
|
export const ConnectedBoardPage = connect(function(state: any, props: any) {
|
|
const boardID = props.match.params.id;
|
|
return {
|
|
board: state.boards.byID[boardID],
|
|
kanboard: state.kanboards.byID[boardID]
|
|
};
|
|
})(BoardPage); |