Add Boards management to sitesettings (#107)

This commit is contained in:
Riccardo Graziosi
2022-05-08 16:36:35 +02:00
committed by GitHub
parent 7b8a4d6709
commit 6be2394dc5
44 changed files with 1464 additions and 112 deletions

View File

@@ -0,0 +1,121 @@
import * as React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { DescriptionText } from '../../shared/CustomTexts';
import DragZone from '../../shared/DragZone';
import PostBoardLabel from '../../shared/PostBoardLabel';
import Separator from '../../shared/Separator';
import BoardForm from './BoardForm';
interface Props {
id: number;
name: string;
description?: string;
index: number;
settingsAreUpdating: boolean;
handleUpdate(
id: number,
description: string,
name: string,
onSuccess: Function,
): void;
handleDelete(id: number): void;
}
interface State {
editMode: boolean;
}
class BoardsEditable extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
editMode: false,
};
this.toggleEditMode = this.toggleEditMode.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
toggleEditMode() {
this.setState({editMode: !this.state.editMode});
}
handleUpdate(id: number, name: string, description: string) {
this.props.handleUpdate(
id,
name,
description,
() => this.setState({editMode: false}),
);
}
render() {
const {
id,
name,
description,
index,
settingsAreUpdating,
handleDelete,
} = this.props;
const { editMode } = this.state;
return (
<Draggable key={id} draggableId={id.toString()} index={index} isDragDisabled={settingsAreUpdating}>
{(provided, snapshot) => (
<li className={`boardEditable${snapshot.isDragging ? ' dragging' : ''}`} ref={provided.innerRef} {...provided.draggableProps}>
<DragZone dndProvided={provided} isDragDisabled={settingsAreUpdating} />
{ editMode === false ?
<React.Fragment>
<div className="boardInfo">
<div className="boardName">
<PostBoardLabel name={name} />
</div>
<div className="boardDescription">
<DescriptionText limit={80}>{description}</DescriptionText>
</div>
</div>
<div className="boardEditableActions">
<a onClick={this.toggleEditMode}>Edit</a>
<Separator />
<a
onClick={() => handleDelete(id)}
data-confirm="Are you sure?"
>
Delete
</a>
</div>
</React.Fragment>
:
<React.Fragment>
<BoardForm
mode='update'
id={id}
name={name}
description={description}
handleUpdate={this.handleUpdate}
/>
<a
className="boardFormCancelButton"
onClick={this.toggleEditMode}>
Cancel
</a>
</React.Fragment>
}
</li>
)}
</Draggable>
);
}
}
export default BoardsEditable;

View File

@@ -0,0 +1,112 @@
import * as React from 'react';
import Button from '../../shared/Button';
interface Props {
mode: 'create' | 'update';
id?: number;
name?: string;
description?: string;
handleSubmit?(
name: string,
description: string,
onSuccess: Function,
): void;
handleUpdate?(
id: number,
name: string,
description?: string,
): void;
}
interface State {
name: string;
description: string;
}
class BoardForm extends React.Component<Props, State> {
initialState: State = {
name: '',
description: '',
};
constructor(props: Props) {
super(props);
this.state = this.props.mode === 'create' ?
this.initialState
:
{
name: this.props.name,
description: this.props.description,
};
this.onSubmit = this.onSubmit.bind(this);
}
isFormValid() {
return this.state.name && this.state.name.length > 0;
}
onNameChange(nameText: string) {
this.setState({
name: nameText,
});
}
onDescriptionChange(descriptionText: string) {
this.setState({
description: descriptionText,
});
}
onSubmit() {
if (this.props.mode === 'create') {
this.props.handleSubmit(
this.state.name,
this.state.description,
() => this.setState({...this.initialState}),
);
} else {
this.props.handleUpdate(this.props.id, this.state.name, this.state.description);
}
}
render() {
const {mode} = this.props;
const {name, description} = this.state;
return (
<div className="boardForm">
<div className="boardMandatoryForm">
<input
type="text"
placeholder="Board name"
value={name}
onChange={e => this.onNameChange(e.target.value)}
className="form-control"
/>
<Button
onClick={this.onSubmit}
className="newBoardButton"
disabled={!this.isFormValid()}
>
{mode === 'create' ? 'Create' : 'Save'}
</Button>
</div>
<textarea
placeholder="Optional board description"
value={description}
onChange={e => this.onDescriptionChange(e.target.value)}
className="form-control"
/>
</div>
);
}
}
export default BoardForm;

View File

@@ -0,0 +1,140 @@
import * as React from 'react';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import BoardEditable from './BoardEditable';
import BoardForm from './BoardForm';
import SiteSettingsInfoBox from '../../shared/SiteSettingsInfoBox';
import Spinner from '../../shared/Spinner';
import { BoardsState } from '../../../reducers/boardsReducer';
import { CenteredMutedText } from '../../shared/CustomTexts';
import IBoard from '../../../interfaces/IBoard';
interface Props {
authenticityToken: string;
boards: BoardsState;
settingsAreUpdating: boolean;
settingsError: string;
requestBoards(): void;
submitBoard(
name: string,
description: string,
onSuccess: Function,
authenticityToken: string,
): void;
updateBoard(
id: number,
name: string,
description: string,
onSuccess: Function,
authenticityToken: string,
): void;
updateBoardOrder(
id: number,
boards: Array<IBoard>,
sourceIndex: number,
destinationIndex: number,
authenticityToken: string,
): void;
deleteBoard(id: number, authenticityToken: string): void;
}
class BoardsSiteSettingsP extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.handleDragEnd = this.handleDragEnd.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
componentDidMount() {
this.props.requestBoards();
}
handleSubmit(name: string, description: string, onSuccess: Function) {
this.props.submitBoard(name, description, onSuccess, this.props.authenticityToken);
}
handleUpdate(id: number, name: string, description: string, onSuccess: Function) {
this.props.updateBoard(id, name, description, onSuccess, this.props.authenticityToken);
}
handleDragEnd(result) {
if (result.destination == null || result.source.index === result.destination.index)
return;
this.props.updateBoardOrder(
parseInt(result.draggableId),
this.props.boards.items,
result.source.index,
result.destination.index,
this.props.authenticityToken,
);
}
handleDelete(id: number) {
this.props.deleteBoard(id, this.props.authenticityToken);
}
render() {
const {
boards,
settingsAreUpdating,
settingsError,
} = this.props;
return (
<React.Fragment>
<div className="content">
<h2>Boards</h2>
{
boards.items.length > 0 ?
<DragDropContext onDragEnd={this.handleDragEnd}>
<Droppable droppableId="boards">
{provided => (
<ul ref={provided.innerRef} {...provided.droppableProps} className="boardsList">
{boards.items.map((board, i) => (
<BoardEditable
id={board.id}
name={board.name}
description={board.description}
index={i}
settingsAreUpdating={settingsAreUpdating}
handleUpdate={this.handleUpdate}
handleDelete={this.handleDelete}
key={board.id}
/>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
:
boards.areLoading ?
<Spinner />
:
<CenteredMutedText>There are no boards. Create one below!</CenteredMutedText>
}
</div>
<div className="content">
<h2>New</h2>
<BoardForm mode='create' handleSubmit={this.handleSubmit} />
</div>
<SiteSettingsInfoBox areUpdating={settingsAreUpdating || boards.areLoading} error={settingsError} />
</React.Fragment>
);
}
}
export default BoardsSiteSettingsP;

View File

@@ -0,0 +1,33 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import BoardsSiteSettings from '../../../containers/BoardsSiteSettings';
import createStoreHelper from '../../../helpers/createStore';
import { State } from '../../../reducers/rootReducer';
interface Props {
authenticityToken: string;
}
class BoardsSiteSettingsRoot extends React.Component<Props> {
store: Store<State, any>;
constructor(props: Props) {
super(props);
this.store = createStoreHelper();
}
render() {
return (
<Provider store={this.store}>
<BoardsSiteSettings
authenticityToken={this.props.authenticityToken}
/>
</Provider>
);
}
}
export default BoardsSiteSettingsRoot;

View File

@@ -89,7 +89,7 @@ class PostStatusForm extends React.Component<Props, State> {
<div className="postStatusForm">
<input
type="text"
placeholder="Name"
placeholder="Post status name"
value={name}
onChange={e => this.onNameChange(e.target.value)}
className="form-control"
@@ -99,7 +99,7 @@ class PostStatusForm extends React.Component<Props, State> {
type="color"
value={color}
onChange={e => this.onColorChange(e.target.value)}
className="form-control"
className="form-control postStatusColorInput"
/>
<Button

View File

@@ -92,7 +92,7 @@ class PostStatusesSiteSettingsP extends React.Component<Props> {
<DragDropContext onDragEnd={this.handleDragEnd}>
<Droppable droppableId="postStatuses">
{provided => (
<ul ref={provided.innerRef} {...provided.droppableProps} className="postStatusList">
<ul ref={provided.innerRef} {...provided.droppableProps} className="postStatusesList">
{postStatuses.items.map((postStatus, i) => (
<PostStatusEditable
id={postStatus.id}

View File

@@ -1,8 +1,10 @@
import * as React from 'react';
import IBoard from '../../interfaces/IBoard';
interface Props {
name: string;
}
const PostBoardLabel = ({ name }: IBoard) => (
const PostBoardLabel = ({ name }: Props) => (
<span className="badge badgeLight">{name?.toUpperCase()}</span>
);

View File

@@ -14,7 +14,7 @@ const SiteSettingsInfoBox = ({ areUpdating, error }: Props) => (
<Spinner />
:
error ?
<span className="error">An error occurred: {error}</span>
<span className="error">An error occurred: {JSON.stringify(error)}</span>
:
<span>Everything up to date</span>
}