mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 19:27:52 +01:00
Add roadmap management to Site settings (#123)
This commit is contained in:
committed by
GitHub
parent
db674eaf6a
commit
e2065b2c5e
@@ -75,6 +75,6 @@ class PostStatusesController < ApplicationController
|
|||||||
def post_status_params
|
def post_status_params
|
||||||
params
|
params
|
||||||
.require(:post_status)
|
.require(:post_status)
|
||||||
.permit(:name, :color)
|
.permit(:name, :color, :show_in_roadmap)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,4 +11,7 @@ class SiteSettingsController < ApplicationController
|
|||||||
|
|
||||||
def post_statuses
|
def post_statuses
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def roadmap
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
import { ThunkAction } from 'redux-thunk';
|
import { ThunkAction } from 'redux-thunk';
|
||||||
|
|
||||||
import IPostStatus from '../../interfaces/IPostStatus';
|
import IPostStatusJSON from '../../interfaces/json/IPostStatus';
|
||||||
|
|
||||||
import { State } from '../../reducers/rootReducer';
|
import { State } from '../../reducers/rootReducer';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ interface PostStatusesRequestStartAction {
|
|||||||
export const POST_STATUSES_REQUEST_SUCCESS = 'POST_STATUSES_REQUEST_SUCCESS';
|
export const POST_STATUSES_REQUEST_SUCCESS = 'POST_STATUSES_REQUEST_SUCCESS';
|
||||||
interface PostStatusesRequestSuccessAction {
|
interface PostStatusesRequestSuccessAction {
|
||||||
type: typeof POST_STATUSES_REQUEST_SUCCESS;
|
type: typeof POST_STATUSES_REQUEST_SUCCESS;
|
||||||
postStatuses: Array<IPostStatus>;
|
postStatuses: Array<IPostStatusJSON>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const POST_STATUSES_REQUEST_FAILURE = 'POST_STATUSES_REQUEST_FAILURE';
|
export const POST_STATUSES_REQUEST_FAILURE = 'POST_STATUSES_REQUEST_FAILURE';
|
||||||
@@ -33,7 +33,7 @@ const postStatusesRequestStart = (): PostStatusesRequestActionTypes => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const postStatusesRequestSuccess = (
|
const postStatusesRequestSuccess = (
|
||||||
postStatuses: Array<IPostStatus>
|
postStatuses: Array<IPostStatusJSON>
|
||||||
): PostStatusesRequestActionTypes => ({
|
): PostStatusesRequestActionTypes => ({
|
||||||
type: POST_STATUSES_REQUEST_SUCCESS,
|
type: POST_STATUSES_REQUEST_SUCCESS,
|
||||||
postStatuses,
|
postStatuses,
|
||||||
|
|||||||
@@ -43,24 +43,34 @@ const postStatusUpdateFailure = (error: string): PostStatusUpdateFailureAction =
|
|||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updatePostStatus = (
|
interface UpdatePostStatusParams {
|
||||||
id: number,
|
id: number;
|
||||||
name: string,
|
name?: string;
|
||||||
color: string,
|
color?: string;
|
||||||
authenticityToken: string,
|
showInRoadmap?: boolean;
|
||||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
authenticityToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePostStatus = ({
|
||||||
|
id,
|
||||||
|
name = null,
|
||||||
|
color = null,
|
||||||
|
showInRoadmap = null,
|
||||||
|
authenticityToken,
|
||||||
|
}: UpdatePostStatusParams): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||||
dispatch(postStatusUpdateStart());
|
dispatch(postStatusUpdateStart());
|
||||||
|
|
||||||
|
const post_status = Object.assign({},
|
||||||
|
name !== null ? {name} : null,
|
||||||
|
color !== null ? {color} : null,
|
||||||
|
showInRoadmap !== null ? {show_in_roadmap: showInRoadmap} : null
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/post_statuses/${id}`, {
|
const res = await fetch(`/post_statuses/${id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: buildRequestHeaders(authenticityToken),
|
headers: buildRequestHeaders(authenticityToken),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({post_status}),
|
||||||
post_status: {
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const Comment = ({
|
|||||||
</a>
|
</a>
|
||||||
{
|
{
|
||||||
isPowerUser ?
|
isPowerUser ?
|
||||||
<React.Fragment>
|
<>
|
||||||
<Separator />
|
<Separator />
|
||||||
<a
|
<a
|
||||||
onClick={() => handleToggleIsCommentUpdate(id, isPostUpdate)}
|
onClick={() => handleToggleIsCommentUpdate(id, isPostUpdate)}
|
||||||
@@ -103,7 +103,7 @@ const Comment = ({
|
|||||||
{I18n.t('common.buttons.delete')}
|
{I18n.t('common.buttons.delete')}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</React.Fragment>
|
</>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const CommentList = ({
|
|||||||
isPowerUser,
|
isPowerUser,
|
||||||
userEmail,
|
userEmail,
|
||||||
}: Props) => (
|
}: Props) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
{comments.map((comment, i) => {
|
{comments.map((comment, i) => {
|
||||||
if (comment.parentId === parentId) {
|
if (comment.parentId === parentId) {
|
||||||
return (
|
return (
|
||||||
@@ -77,7 +77,7 @@ const CommentList = ({
|
|||||||
);
|
);
|
||||||
} else return null;
|
} else return null;
|
||||||
})}
|
})}
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default CommentList;
|
export default CommentList;
|
||||||
@@ -41,11 +41,11 @@ const NewComment = ({
|
|||||||
isPowerUser,
|
isPowerUser,
|
||||||
userEmail,
|
userEmail,
|
||||||
}: Props) => (
|
}: Props) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
<div className="newCommentForm">
|
<div className="newCommentForm">
|
||||||
{
|
{
|
||||||
isLoggedIn ?
|
isLoggedIn ?
|
||||||
<React.Fragment>
|
<>
|
||||||
<div className="commentBodyForm">
|
<div className="commentBodyForm">
|
||||||
<Gravatar email={userEmail} size={48} className="currentUserAvatar" />
|
<Gravatar email={userEmail} size={48} className="currentUserAvatar" />
|
||||||
<textarea
|
<textarea
|
||||||
@@ -69,7 +69,7 @@ const NewComment = ({
|
|||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</>
|
||||||
:
|
:
|
||||||
<a href="/users/sign_in" className="loginInfo">
|
<a href="/users/sign_in" className="loginInfo">
|
||||||
{I18n.t('post.new_comment.not_logged_in')}
|
{I18n.t('post.new_comment.not_logged_in')}
|
||||||
@@ -78,7 +78,7 @@ const NewComment = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ error ? <DangerText>{error}</DangerText> : null }
|
{ error ? <DangerText>{error}</DangerText> : null }
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default NewComment;
|
export default NewComment;
|
||||||
@@ -56,12 +56,12 @@ const PostUpdateList = ({
|
|||||||
{postUpdate.body}
|
{postUpdate.body}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
:
|
:
|
||||||
<React.Fragment>
|
<>
|
||||||
<i>{I18n.t('post.updates_box.status_change')}</i>
|
<i>{I18n.t('post.updates_box.status_change')}</i>
|
||||||
<PostStatusLabel
|
<PostStatusLabel
|
||||||
{...postStatuses.find(postStatus => postStatus.id === postUpdate.postStatusId)}
|
{...postStatuses.find(postStatus => postStatus.id === postUpdate.postStatusId)}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class BoardsEditable extends React.Component<Props, State> {
|
|||||||
<DragZone dndProvided={provided} isDragDisabled={settingsAreUpdating} />
|
<DragZone dndProvided={provided} isDragDisabled={settingsAreUpdating} />
|
||||||
|
|
||||||
{ editMode === false ?
|
{ editMode === false ?
|
||||||
<React.Fragment>
|
<>
|
||||||
<div className="boardInfo">
|
<div className="boardInfo">
|
||||||
<div className="boardName">
|
<div className="boardName">
|
||||||
<PostBoardLabel name={name} />
|
<PostBoardLabel name={name} />
|
||||||
@@ -94,9 +94,9 @@ class BoardsEditable extends React.Component<Props, State> {
|
|||||||
{I18n.t('common.buttons.delete')}
|
{I18n.t('common.buttons.delete')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</>
|
||||||
:
|
:
|
||||||
<React.Fragment>
|
<>
|
||||||
<BoardForm
|
<BoardForm
|
||||||
mode='update'
|
mode='update'
|
||||||
id={id}
|
id={id}
|
||||||
@@ -110,7 +110,7 @@ class BoardsEditable extends React.Component<Props, State> {
|
|||||||
onClick={this.toggleEditMode}>
|
onClick={this.toggleEditMode}>
|
||||||
{I18n.t('common.buttons.cancel')}
|
{I18n.t('common.buttons.cancel')}
|
||||||
</a>
|
</a>
|
||||||
</React.Fragment>
|
</>
|
||||||
}
|
}
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import I18n from 'i18n-js';
|
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import BoardEditable from './BoardEditable';
|
import BoardEditable from './BoardEditable';
|
||||||
import BoardForm from './BoardForm';
|
import BoardForm from './BoardForm';
|
||||||
@@ -97,8 +97,8 @@ class BoardsSiteSettingsP extends React.Component<Props> {
|
|||||||
{
|
{
|
||||||
boards.items.length > 0 ?
|
boards.items.length > 0 ?
|
||||||
<DragDropContext onDragEnd={this.handleDragEnd}>
|
<DragDropContext onDragEnd={this.handleDragEnd}>
|
||||||
<Droppable droppableId="boards">
|
<Droppable droppableId="boards">
|
||||||
{provided => (
|
{provided => (
|
||||||
<ul ref={provided.innerRef} {...provided.droppableProps} className="boardsList">
|
<ul ref={provided.innerRef} {...provided.droppableProps} className="boardsList">
|
||||||
{boards.items.map((board, i) => (
|
{boards.items.map((board, i) => (
|
||||||
<BoardEditable
|
<BoardEditable
|
||||||
@@ -117,7 +117,7 @@ class BoardsSiteSettingsP extends React.Component<Props> {
|
|||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
:
|
:
|
||||||
boards.areLoading ?
|
boards.areLoading ?
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import I18n from 'i18n-js';
|
|
||||||
|
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import PostStatusLabel from "../../common/PostStatusLabel";
|
import PostStatusLabel from "../../common/PostStatusLabel";
|
||||||
import DragZone from '../../common/DragZone';
|
import DragZone from '../../common/DragZone';
|
||||||
@@ -71,7 +70,7 @@ class PostStatusEditable extends React.Component<Props, State> {
|
|||||||
<DragZone dndProvided={provided} isDragDisabled={settingsAreUpdating} />
|
<DragZone dndProvided={provided} isDragDisabled={settingsAreUpdating} />
|
||||||
|
|
||||||
{ editMode === false ?
|
{ editMode === false ?
|
||||||
<React.Fragment>
|
<>
|
||||||
<PostStatusLabel name={name} color={color} />
|
<PostStatusLabel name={name} color={color} />
|
||||||
|
|
||||||
<div className="postStatusEditableActions">
|
<div className="postStatusEditableActions">
|
||||||
@@ -86,9 +85,9 @@ class PostStatusEditable extends React.Component<Props, State> {
|
|||||||
{I18n.t('common.buttons.delete')}
|
{I18n.t('common.buttons.delete')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</>
|
||||||
:
|
:
|
||||||
<React.Fragment>
|
<>
|
||||||
<PostStatusForm
|
<PostStatusForm
|
||||||
mode='update'
|
mode='update'
|
||||||
id={id}
|
id={id}
|
||||||
@@ -102,7 +101,7 @@ class PostStatusEditable extends React.Component<Props, State> {
|
|||||||
onClick={this.toggleEditMode}>
|
onClick={this.toggleEditMode}>
|
||||||
{I18n.t('common.buttons.cancel')}
|
{I18n.t('common.buttons.cancel')}
|
||||||
</a>
|
</a>
|
||||||
</React.Fragment>
|
</>
|
||||||
}
|
}
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
import I18n from 'i18n-js';
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
|
||||||
import IPostStatus from '../../../interfaces/IPostStatus';
|
|
||||||
|
|
||||||
import { PostStatusesState } from "../../../reducers/postStatusesReducer";
|
|
||||||
import { CenteredMutedText } from '../../common/CustomTexts';
|
import { CenteredMutedText } from '../../common/CustomTexts';
|
||||||
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||||
import PostStatusForm from './PostStatusForm';
|
import PostStatusForm from './PostStatusForm';
|
||||||
@@ -12,6 +9,9 @@ import PostStatusEditable from './PostStatusEditable';
|
|||||||
import Spinner from '../../common/Spinner';
|
import Spinner from '../../common/Spinner';
|
||||||
import Box from '../../common/Box';
|
import Box from '../../common/Box';
|
||||||
|
|
||||||
|
import { PostStatusesState } from "../../../reducers/postStatusesReducer";
|
||||||
|
import IPostStatus from '../../../interfaces/IPostStatus';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
postStatuses: PostStatusesState;
|
postStatuses: PostStatusesState;
|
||||||
@@ -92,8 +92,8 @@ class PostStatusesSiteSettingsP extends React.Component<Props> {
|
|||||||
{
|
{
|
||||||
postStatuses.items.length > 0 ?
|
postStatuses.items.length > 0 ?
|
||||||
<DragDropContext onDragEnd={this.handleDragEnd}>
|
<DragDropContext onDragEnd={this.handleDragEnd}>
|
||||||
<Droppable droppableId="postStatuses">
|
<Droppable droppableId="postStatuses">
|
||||||
{provided => (
|
{provided => (
|
||||||
<ul ref={provided.innerRef} {...provided.droppableProps} className="postStatusesList">
|
<ul ref={provided.innerRef} {...provided.droppableProps} className="postStatusesList">
|
||||||
{postStatuses.items.map((postStatus, i) => (
|
{postStatuses.items.map((postStatus, i) => (
|
||||||
<PostStatusEditable
|
<PostStatusEditable
|
||||||
@@ -112,13 +112,13 @@ class PostStatusesSiteSettingsP extends React.Component<Props> {
|
|||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
:
|
|
||||||
postStatuses.areLoading ?
|
|
||||||
<Spinner />
|
|
||||||
:
|
:
|
||||||
<CenteredMutedText>{I18n.t('site_settings.post_statuses.empty')}</CenteredMutedText>
|
postStatuses.areLoading ?
|
||||||
|
<Spinner />
|
||||||
|
:
|
||||||
|
<CenteredMutedText>{I18n.t('site_settings.post_statuses.empty')}</CenteredMutedText>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
|
import DragZone from '../../common/DragZone';
|
||||||
|
import { TitleText } from '../../common/CustomTexts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
index: number;
|
||||||
|
settingsAreUpdating: boolean;
|
||||||
|
headerOnly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoadmapPostStatus = ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
index,
|
||||||
|
settingsAreUpdating,
|
||||||
|
headerOnly,
|
||||||
|
}: Props) => (
|
||||||
|
<Draggable key={id} draggableId={id.toString()} index={index} isDragDisabled={settingsAreUpdating}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
className={`roadmapPostStatus${snapshot.isDragging ? '' : ' notDragging'}${headerOnly ? ' headerOnly' : ''}`}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
>
|
||||||
|
<div className="roadmapPostStatusHeader" style={{backgroundColor: color}}>
|
||||||
|
<DragZone color='white' dndProvided={provided} isDragDisabled={settingsAreUpdating} />
|
||||||
|
<TitleText>{name}</TitleText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default RoadmapPostStatus;
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
|
import { PostStatusesState } from "../../../reducers/postStatusesReducer";
|
||||||
|
import Box from '../../common/Box';
|
||||||
|
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||||
|
import RoadmapPostStatus from './RoadmapPostStatus';
|
||||||
|
import IPostStatus from '../../../interfaces/IPostStatus';
|
||||||
|
import { MutedText } from '../../common/CustomTexts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
authenticityToken: string,
|
||||||
|
postStatuses: PostStatusesState,
|
||||||
|
settingsAreUpdating: boolean,
|
||||||
|
settingsError: string,
|
||||||
|
|
||||||
|
requestPostStatuses(): void;
|
||||||
|
updatePostStatus(
|
||||||
|
id: number,
|
||||||
|
showInRoadmap: boolean,
|
||||||
|
onComplete: Function,
|
||||||
|
authenticityToken: string,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isDragging: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoadmapSiteSettingsP extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isDragging: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleDragStart = this.handleDragStart.bind(this);
|
||||||
|
this.handleDragEnd = this.handleDragEnd.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.requestPostStatuses();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragStart(result) {
|
||||||
|
this.setState({ isDragging: parseInt(result.draggableId) });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragEnd(result) {
|
||||||
|
if (result.destination == null || result.source.droppableId === result.destination.droppableId) {
|
||||||
|
this.setState({ isDragging: null });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.updatePostStatus(
|
||||||
|
result.draggableId,
|
||||||
|
result.destination.droppableId === 'statusesInRoadmap',
|
||||||
|
() => this.setState({ isDragging: null }),
|
||||||
|
this.props.authenticityToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround needed because after dropping a post status, the state is not yet updated
|
||||||
|
// with the new showInRoadmap value (we need to wait POSTSTATUS_UPDATE_SUCCESS dispatch)
|
||||||
|
// and the UI would flicker, moving the poststatus back in its original spot
|
||||||
|
placeDraggingStatusDuringUpdate(statusesInRoadmap: IPostStatus[], statusesNotInRoadmap: IPostStatus[]) {
|
||||||
|
const { postStatuses } = this.props;
|
||||||
|
const movedPostStatus = postStatuses.items.find(postStatus => postStatus.id === this.state.isDragging);
|
||||||
|
|
||||||
|
if (movedPostStatus.showInRoadmap) {
|
||||||
|
statusesInRoadmap = statusesInRoadmap.filter(postStatus => postStatus.id !== this.state.isDragging);
|
||||||
|
statusesNotInRoadmap.push(movedPostStatus);
|
||||||
|
statusesNotInRoadmap.sort((ps1, ps2) => ps1.order - ps2.order);
|
||||||
|
} else {
|
||||||
|
statusesNotInRoadmap = statusesNotInRoadmap.filter(postStatus => postStatus.id !== this.state.isDragging);
|
||||||
|
statusesInRoadmap.push(movedPostStatus);
|
||||||
|
statusesInRoadmap.sort((ps1, ps2) => ps1.order - ps2.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [statusesInRoadmap, statusesNotInRoadmap];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { postStatuses, settingsAreUpdating, settingsError } = this.props;
|
||||||
|
const { isDragging } = this.state;
|
||||||
|
|
||||||
|
let statusesInRoadmap = postStatuses.items.filter(postStatus => postStatus.showInRoadmap);
|
||||||
|
let statusesNotInRoadmap = postStatuses.items.filter(postStatus => !postStatus.showInRoadmap);
|
||||||
|
|
||||||
|
if (settingsAreUpdating && this.state.isDragging) {
|
||||||
|
[statusesInRoadmap, statusesNotInRoadmap] = this.placeDraggingStatusDuringUpdate(statusesInRoadmap, statusesNotInRoadmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragDropContext onDragStart={this.handleDragStart} onDragEnd={this.handleDragEnd}>
|
||||||
|
<Box>
|
||||||
|
<h2>{I18n.t('site_settings.roadmap.title')}</h2>
|
||||||
|
|
||||||
|
<Droppable droppableId="statusesInRoadmap" direction="horizontal">
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
className={`roadmapPostStatuses${isDragging ? ' isDraggingSomething' : ''}${snapshot.isDraggingOver ? ' isDraggingOver' : ''}`}
|
||||||
|
{...provided.droppableProps}
|
||||||
|
>
|
||||||
|
{statusesInRoadmap.map((postStatus, i) => (
|
||||||
|
<RoadmapPostStatus
|
||||||
|
id={postStatus.id}
|
||||||
|
name={postStatus.name}
|
||||||
|
color={postStatus.color}
|
||||||
|
index={i}
|
||||||
|
settingsAreUpdating={settingsAreUpdating}
|
||||||
|
|
||||||
|
key={postStatus.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
|
||||||
|
{
|
||||||
|
statusesInRoadmap.length > 0 ?
|
||||||
|
null
|
||||||
|
:
|
||||||
|
<MutedText>{I18n.t('site_settings.roadmap.empty')}</MutedText>
|
||||||
|
}
|
||||||
|
<MutedText>{I18n.t('site_settings.roadmap.help')}</MutedText>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<h2>{I18n.t('site_settings.roadmap.title2')}</h2>
|
||||||
|
|
||||||
|
<Droppable droppableId="statusesNotInRoadmap" direction="horizontal">
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
className={`roadmapPostStatuses${isDragging ? ' isDraggingSomething' : ''}${snapshot.isDraggingOver ? ' isDraggingOver' : ''}`}
|
||||||
|
{...provided.droppableProps}
|
||||||
|
>
|
||||||
|
{statusesNotInRoadmap.map((postStatus, i) => (
|
||||||
|
<RoadmapPostStatus
|
||||||
|
id={postStatus.id}
|
||||||
|
name={postStatus.name}
|
||||||
|
color={postStatus.color}
|
||||||
|
index={i}
|
||||||
|
settingsAreUpdating={settingsAreUpdating}
|
||||||
|
headerOnly
|
||||||
|
|
||||||
|
key={postStatus.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<SiteSettingsInfoBox areUpdating={settingsAreUpdating || postStatuses.areLoading} error={settingsError} />
|
||||||
|
</DragDropContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoadmapSiteSettingsP;
|
||||||
33
app/javascript/components/SiteSettings/Roadmap/index.tsx
Normal file
33
app/javascript/components/SiteSettings/Roadmap/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Store } from 'redux';
|
||||||
|
import RoadmapSiteSettings from '../../../containers/RoadmapSiteSettings';
|
||||||
|
|
||||||
|
import createStoreHelper from '../../../helpers/createStore';
|
||||||
|
import { State } from '../../../reducers/rootReducer';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
authenticityToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoadmapSiteSettingsRoot extends React.Component<Props> {
|
||||||
|
store: Store<State, any>;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.store = createStoreHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Provider store={this.store}>
|
||||||
|
<RoadmapSiteSettings
|
||||||
|
authenticityToken={this.props.authenticityToken}
|
||||||
|
/>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoadmapSiteSettingsRoot;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
const DragZone = ({dndProvided, isDragDisabled}) => (
|
const DragZone = ({dndProvided, isDragDisabled, color = 'black'}) => (
|
||||||
<span
|
<span
|
||||||
className={`drag-zone${isDragDisabled ? ' drag-zone-disabled' : ''}`}
|
className={`drag-zone${isDragDisabled ? ' drag-zone-disabled' : ''}`}
|
||||||
{...dndProvided.dragHandleProps}
|
{...dndProvided.dragHandleProps}
|
||||||
>
|
>
|
||||||
<span className="drag-icon"></span>
|
<span className={`drag-icon${color === 'white' ? ' drag-icon-white' : ''}`}></span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
onSuccess: Function,
|
onSuccess: Function,
|
||||||
authenticityToken: string,
|
authenticityToken: string,
|
||||||
) {
|
) {
|
||||||
dispatch(updatePostStatus(id, name, color, authenticityToken)).then(res => {
|
dispatch(updatePostStatus({id, name, color, authenticityToken})).then(res => {
|
||||||
if (res && res.status === HttpStatus.OK) onSuccess();
|
if (res && res.status === HttpStatus.OK) onSuccess();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
30
app/javascript/containers/RoadmapSiteSettings.tsx
Normal file
30
app/javascript/containers/RoadmapSiteSettings.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import RoadmapSiteSettingsP from "../components/SiteSettings/Roadmap/RoadmapSiteSettingsP";
|
||||||
|
|
||||||
|
import { requestPostStatuses } from "../actions/PostStatus/requestPostStatuses";
|
||||||
|
import { State } from "../reducers/rootReducer";
|
||||||
|
import { updatePostStatus } from "../actions/PostStatus/updatePostStatus";
|
||||||
|
|
||||||
|
const mapStateToProps = (state: State) => ({
|
||||||
|
postStatuses: state.postStatuses,
|
||||||
|
settingsAreUpdating: state.siteSettings.roadmap.areUpdating,
|
||||||
|
settingsError: state.siteSettings.roadmap.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: any) => ({
|
||||||
|
requestPostStatuses() {
|
||||||
|
dispatch(requestPostStatuses());
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePostStatus(id: number, showInRoadmap: boolean, onComplete: Function, authenticityToken: string) {
|
||||||
|
dispatch(updatePostStatus({id, showInRoadmap, authenticityToken})).then(() => {
|
||||||
|
onComplete();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(RoadmapSiteSettingsP);
|
||||||
@@ -2,6 +2,8 @@ interface IPostStatus {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
order: number;
|
||||||
|
showInRoadmap: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IPostStatus;
|
export default IPostStatus;
|
||||||
@@ -2,6 +2,8 @@ interface IPostStatusJSON {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
order: number;
|
||||||
|
show_in_roadmap: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IPostStatusJSON;
|
export default IPostStatusJSON;
|
||||||
48
app/javascript/reducers/SiteSettings/roadmapReducer.ts
Normal file
48
app/javascript/reducers/SiteSettings/roadmapReducer.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
PostStatusUpdateActionTypes,
|
||||||
|
POSTSTATUS_UPDATE_START,
|
||||||
|
POSTSTATUS_UPDATE_SUCCESS,
|
||||||
|
POSTSTATUS_UPDATE_FAILURE,
|
||||||
|
} from '../../actions/PostStatus/updatePostStatus';
|
||||||
|
|
||||||
|
export interface SiteSettingsRoadmapState {
|
||||||
|
areUpdating: boolean;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
areUpdating: false,
|
||||||
|
error: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const siteSettingsRoadmapReducer = (
|
||||||
|
state = initialState,
|
||||||
|
action: PostStatusUpdateActionTypes,
|
||||||
|
): SiteSettingsRoadmapState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case POSTSTATUS_UPDATE_START:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
areUpdating: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
case POSTSTATUS_UPDATE_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
areUpdating: false,
|
||||||
|
error: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
case POSTSTATUS_UPDATE_FAILURE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
areUpdating: false,
|
||||||
|
error: action.error,
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default siteSettingsRoadmapReducer;
|
||||||
@@ -62,6 +62,8 @@ const postStatusesReducer = (
|
|||||||
id: postStatus.id,
|
id: postStatus.id,
|
||||||
name: postStatus.name,
|
name: postStatus.name,
|
||||||
color: postStatus.color,
|
color: postStatus.color,
|
||||||
|
order: postStatus.order,
|
||||||
|
showInRoadmap: postStatus.show_in_roadmap,
|
||||||
})),
|
})),
|
||||||
areLoading: false,
|
areLoading: false,
|
||||||
error: '',
|
error: '',
|
||||||
@@ -85,7 +87,12 @@ const postStatusesReducer = (
|
|||||||
...state,
|
...state,
|
||||||
items: state.items.map(postStatus => {
|
items: state.items.map(postStatus => {
|
||||||
if (postStatus.id !== action.postStatus.id) return postStatus;
|
if (postStatus.id !== action.postStatus.id) return postStatus;
|
||||||
return {...postStatus, name: action.postStatus.name, color: action.postStatus.color};
|
return {
|
||||||
|
...postStatus,
|
||||||
|
name: action.postStatus.name,
|
||||||
|
color: action.postStatus.color,
|
||||||
|
showInRoadmap: action.postStatus.show_in_roadmap,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -63,15 +63,18 @@ import {
|
|||||||
|
|
||||||
import siteSettingsBoardsReducer, { SiteSettingsBoardsState } from './SiteSettings/boardsReducer';
|
import siteSettingsBoardsReducer, { SiteSettingsBoardsState } from './SiteSettings/boardsReducer';
|
||||||
import siteSettingsPostStatusesReducer, { SiteSettingsPostStatusesState } from './SiteSettings/postStatusesReducer';
|
import siteSettingsPostStatusesReducer, { SiteSettingsPostStatusesState } from './SiteSettings/postStatusesReducer';
|
||||||
|
import siteSettingsRoadmapReducer, { SiteSettingsRoadmapState } from './SiteSettings/roadmapReducer';
|
||||||
|
|
||||||
interface SiteSettingsState {
|
interface SiteSettingsState {
|
||||||
boards: SiteSettingsBoardsState;
|
boards: SiteSettingsBoardsState;
|
||||||
postStatuses: SiteSettingsPostStatusesState;
|
postStatuses: SiteSettingsPostStatusesState;
|
||||||
|
roadmap: SiteSettingsRoadmapState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SiteSettingsState = {
|
const initialState: SiteSettingsState = {
|
||||||
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
||||||
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
||||||
|
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
|
||||||
};
|
};
|
||||||
|
|
||||||
const siteSettingsReducer = (
|
const siteSettingsReducer = (
|
||||||
@@ -107,22 +110,28 @@ const siteSettingsReducer = (
|
|||||||
...state,
|
...state,
|
||||||
boards: siteSettingsBoardsReducer(state.boards, action),
|
boards: siteSettingsBoardsReducer(state.boards, action),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case POSTSTATUS_SUBMIT_START:
|
||||||
|
case POSTSTATUS_SUBMIT_SUCCESS:
|
||||||
|
case POSTSTATUS_SUBMIT_FAILURE:
|
||||||
case POSTSTATUS_ORDER_UPDATE_START:
|
case POSTSTATUS_ORDER_UPDATE_START:
|
||||||
case POSTSTATUS_ORDER_UPDATE_SUCCESS:
|
case POSTSTATUS_ORDER_UPDATE_SUCCESS:
|
||||||
case POSTSTATUS_ORDER_UPDATE_FAILURE:
|
case POSTSTATUS_ORDER_UPDATE_FAILURE:
|
||||||
case POST_STATUS_DELETE_START:
|
case POST_STATUS_DELETE_START:
|
||||||
case POST_STATUS_DELETE_SUCCESS:
|
case POST_STATUS_DELETE_SUCCESS:
|
||||||
case POST_STATUS_DELETE_FAILURE:
|
case POST_STATUS_DELETE_FAILURE:
|
||||||
case POSTSTATUS_SUBMIT_START:
|
return {
|
||||||
case POSTSTATUS_SUBMIT_SUCCESS:
|
...state,
|
||||||
case POSTSTATUS_SUBMIT_FAILURE:
|
postStatuses: siteSettingsPostStatusesReducer(state.postStatuses, action),
|
||||||
|
};
|
||||||
|
|
||||||
case POSTSTATUS_UPDATE_START:
|
case POSTSTATUS_UPDATE_START:
|
||||||
case POSTSTATUS_UPDATE_SUCCESS:
|
case POSTSTATUS_UPDATE_SUCCESS:
|
||||||
case POSTSTATUS_UPDATE_FAILURE:
|
case POSTSTATUS_UPDATE_FAILURE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
postStatuses: siteSettingsPostStatusesReducer(state.postStatuses, action),
|
postStatuses: siteSettingsPostStatusesReducer(state.postStatuses, action),
|
||||||
|
roadmap: siteSettingsRoadmapReducer(state.roadmap, action),
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
.roadmapPostStatuses {
|
||||||
|
@extend
|
||||||
|
.d-flex,
|
||||||
|
.flex-row,
|
||||||
|
.flex-wrap;
|
||||||
|
|
||||||
|
min-height: 150px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px dotted transparent;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
&.isDraggingSomething {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.isDraggingOver {
|
||||||
|
background-color: rgba(255, 255, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roadmapPostStatus {
|
||||||
|
@extend
|
||||||
|
.card,
|
||||||
|
.m-2,
|
||||||
|
.p-0;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 0 28%;
|
||||||
|
background-color: $astuto-light-grey;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 150px;
|
||||||
|
|
||||||
|
&.headerOnly { height: fit-content; }
|
||||||
|
|
||||||
|
&.notDragging { transform: none !important; }
|
||||||
|
|
||||||
|
.roadmapPostStatusHeader {
|
||||||
|
@extend
|
||||||
|
.d-flex,
|
||||||
|
.flex-row,
|
||||||
|
.card-header;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
padding: 8px 4px;
|
||||||
|
|
||||||
|
.titleText { @extend .align-self-center; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,11 @@ span.drag-icon::before {
|
|||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.drag-icon.drag-icon-white,
|
||||||
|
span.drag-icon.drag-icon-white::before {
|
||||||
|
background-image: radial-gradient(white 40%, transparent 40%);
|
||||||
|
}
|
||||||
|
|
||||||
span.drag-icon::before {
|
span.drag-icon::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
@import 'components/SiteSettings';
|
@import 'components/SiteSettings';
|
||||||
@import 'components/SiteSettings/Boards';
|
@import 'components/SiteSettings/Boards';
|
||||||
@import 'components/SiteSettings/PostStatuses';
|
@import 'components/SiteSettings/PostStatuses';
|
||||||
|
@import 'components/SiteSettings/Roadmap';
|
||||||
|
|
||||||
/* Icons */
|
/* Icons */
|
||||||
@import 'icons/drag_icon';
|
@import 'icons/drag_icon';
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<div class="verticalNavigation" role="tablist" aria-orientation="vertical">
|
<div class="verticalNavigation" role="tablist" aria-orientation="vertical">
|
||||||
<%= render 'menu_link', label: t('site_settings.menu.boards'), path: site_settings_boards_path %>
|
<%= render 'menu_link', label: t('site_settings.menu.boards'), path: site_settings_boards_path %>
|
||||||
<%= render 'menu_link', label: t('site_settings.menu.post_statuses'), path: site_settings_post_statuses_path %>
|
<%= render 'menu_link', label: t('site_settings.menu.post_statuses'), path: site_settings_post_statuses_path %>
|
||||||
|
<%= render 'menu_link', label: t('site_settings.menu.roadmap'), path: site_settings_roadmap_path %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
13
app/views/site_settings/roadmap.html.erb
Normal file
13
app/views/site_settings/roadmap.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<div class="twoColumnsContainer">
|
||||||
|
<%= render 'menu' %>
|
||||||
|
<div>
|
||||||
|
<%=
|
||||||
|
react_component(
|
||||||
|
'SiteSettings/Roadmap',
|
||||||
|
{
|
||||||
|
authenticityToken: form_authenticity_token
|
||||||
|
}
|
||||||
|
)
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -103,6 +103,7 @@ en:
|
|||||||
title: 'Site settings'
|
title: 'Site settings'
|
||||||
boards: 'Boards'
|
boards: 'Boards'
|
||||||
post_statuses: 'Statuses'
|
post_statuses: 'Statuses'
|
||||||
|
roadmap: 'Roadmap'
|
||||||
info_box:
|
info_box:
|
||||||
up_to_date: 'All changes saved'
|
up_to_date: 'All changes saved'
|
||||||
error: 'An error occurred: %{message}'
|
error: 'An error occurred: %{message}'
|
||||||
@@ -119,6 +120,11 @@ en:
|
|||||||
new: 'New'
|
new: 'New'
|
||||||
form:
|
form:
|
||||||
name: 'Status name'
|
name: 'Status name'
|
||||||
|
roadmap:
|
||||||
|
title: 'Roadmap'
|
||||||
|
title2: 'Not in roadmap'
|
||||||
|
empty: 'The roadmap is empty.'
|
||||||
|
help: 'You can add new statuses to the roadmap by dragging them from the section below. If you want to add a new status or change their order, go to Site settings -> Statuses.'
|
||||||
user_mailer:
|
user_mailer:
|
||||||
opening_greeting: 'Hello!'
|
opening_greeting: 'Hello!'
|
||||||
closing_greeting: 'Have a great day!'
|
closing_greeting: 'Have a great day!'
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ it:
|
|||||||
title: 'Impostazioni sito'
|
title: 'Impostazioni sito'
|
||||||
boards: 'Bacheche'
|
boards: 'Bacheche'
|
||||||
post_statuses: 'Stati'
|
post_statuses: 'Stati'
|
||||||
|
roadmap: 'Roadmap'
|
||||||
info_box:
|
info_box:
|
||||||
up_to_date: 'Tutte le modifiche sono state salvate'
|
up_to_date: 'Tutte le modifiche sono state salvate'
|
||||||
error: 'Si è verificato un errore: %{message}'
|
error: 'Si è verificato un errore: %{message}'
|
||||||
@@ -119,6 +120,11 @@ it:
|
|||||||
new: 'Nuovo'
|
new: 'Nuovo'
|
||||||
form:
|
form:
|
||||||
name: 'Nome stato'
|
name: 'Nome stato'
|
||||||
|
roadmap:
|
||||||
|
title: 'Roadmap'
|
||||||
|
title2: 'Non mostrati in roadmap'
|
||||||
|
empty: 'La roadmap è vuota.'
|
||||||
|
help: "Puoi aggiungere nuovi stati alla roadmap trascinandoli dalla sezione sottostante. Se vuoi aggiungere un nuovo stato o cambiarne l'ordine, vai in Impostazioni sito -> Stati."
|
||||||
user_mailer:
|
user_mailer:
|
||||||
opening_greeting: 'Ciao!'
|
opening_greeting: 'Ciao!'
|
||||||
closing_greeting: 'Buona giornata!'
|
closing_greeting: 'Buona giornata!'
|
||||||
|
|||||||
@@ -35,5 +35,6 @@ Rails.application.routes.draw do
|
|||||||
get 'general'
|
get 'general'
|
||||||
get 'boards'
|
get 'boards'
|
||||||
get 'post_statuses'
|
get 'post_statuses'
|
||||||
|
get 'roadmap'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user