mirror of
https://github.com/plankanban/planka.git
synced 2025-07-24 15:49:46 +02:00
feat: Toggle actions details, little redesign
This commit is contained in:
parent
82e4f73c1f
commit
34db8947b6
25 changed files with 301 additions and 81 deletions
|
@ -1,6 +1,5 @@
|
|||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const fetchActions = (cardId) => ({
|
||||
type: ActionTypes.ACTIONS_FETCH,
|
||||
payload: {
|
||||
|
@ -24,3 +23,28 @@ fetchActions.failure = (cardId, error) => ({
|
|||
error,
|
||||
},
|
||||
});
|
||||
|
||||
export const toggleActionsDetails = (cardId, isVisible) => ({
|
||||
type: ActionTypes.ACTIONS_DETAILS_TOGGLE,
|
||||
payload: {
|
||||
cardId,
|
||||
isVisible,
|
||||
},
|
||||
});
|
||||
|
||||
toggleActionsDetails.success = (cardId, actions, users) => ({
|
||||
type: ActionTypes.ACTIONS_DETAILS_TOGGLE__SUCCESS,
|
||||
payload: {
|
||||
cardId,
|
||||
actions,
|
||||
users,
|
||||
},
|
||||
});
|
||||
|
||||
toggleActionsDetails.failure = (cardId, error) => ({
|
||||
type: ActionTypes.ACTIONS_DETAILS_TOGGLE__FAILURE,
|
||||
payload: {
|
||||
cardId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import EntryActionTypes from '../../constants/EntryActionTypes';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const fetchActionsInCurrentCard = () => ({
|
||||
type: EntryActionTypes.ACTIONS_IN_CURRENT_CARD_FETCH,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
export const toggleActionsDetailsInCurrentCard = (isVisible) => ({
|
||||
type: EntryActionTypes.ACTIONS_DETAILS_IN_CURRENT_CARD_TOGGLE,
|
||||
payload: {
|
||||
isVisible,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Comment, Icon, Loader, Visibility } from 'semantic-ui-react';
|
||||
import { Button, Comment, Icon, Loader, Visibility } from 'semantic-ui-react';
|
||||
|
||||
import { ActionTypes } from '../../../constants/Enums';
|
||||
import CommentAdd from './CommentAdd';
|
||||
|
@ -14,15 +14,22 @@ const Actions = React.memo(
|
|||
items,
|
||||
isFetching,
|
||||
isAllFetched,
|
||||
isDetailsVisible,
|
||||
isDetailsFetching,
|
||||
canEdit,
|
||||
canEditAllComments,
|
||||
onFetch,
|
||||
onDetailsToggle,
|
||||
onCommentCreate,
|
||||
onCommentUpdate,
|
||||
onCommentDelete,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const handleToggleDetailsClick = useCallback(() => {
|
||||
onDetailsToggle(!isDetailsVisible);
|
||||
}, [isDetailsVisible, onDetailsToggle]);
|
||||
|
||||
const handleCommentUpdate = useCallback(
|
||||
(id, data) => {
|
||||
onCommentUpdate(id, data);
|
||||
|
@ -38,20 +45,18 @@ const Actions = React.memo(
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{canEdit && (
|
||||
<div className={styles.contentModule}>
|
||||
<div className={styles.moduleWrapper}>
|
||||
<Icon name="comment outline" className={styles.moduleIcon} />
|
||||
<div className={styles.moduleHeader}>{t('common.addComment')}</div>
|
||||
<CommentAdd onCreate={onCommentCreate} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.contentModule}>
|
||||
<div className={styles.moduleWrapper}>
|
||||
<Icon name="list ul" className={styles.moduleIcon} />
|
||||
<div className={styles.moduleHeader}>{t('common.actions')}</div>
|
||||
<div className={styles.moduleHeader}>
|
||||
{t('common.actions')}
|
||||
<Button
|
||||
content={isDetailsVisible ? t('action.hideDetails') : t('action.showDetails')}
|
||||
className={styles.toggleButton}
|
||||
onClick={handleToggleDetailsClick}
|
||||
/>
|
||||
</div>
|
||||
{canEdit && <CommentAdd onCreate={onCommentCreate} />}
|
||||
<div className={styles.wrapper}>
|
||||
<Comment.Group>
|
||||
{items.map((item) =>
|
||||
|
@ -78,14 +83,13 @@ const Actions = React.memo(
|
|||
)}
|
||||
</Comment.Group>
|
||||
</div>
|
||||
{isFetching ? (
|
||||
{isFetching || isDetailsFetching ? (
|
||||
<Loader active inverted inline="centered" size="small" className={styles.loader} />
|
||||
) : (
|
||||
!isAllFetched && <Visibility fireOnMount onOnScreen={onFetch} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -94,9 +98,12 @@ Actions.propTypes = {
|
|||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isAllFetched: PropTypes.bool.isRequired,
|
||||
isDetailsVisible: PropTypes.bool.isRequired,
|
||||
isDetailsFetching: PropTypes.bool.isRequired,
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
canEditAllComments: PropTypes.bool.isRequired,
|
||||
onFetch: PropTypes.func.isRequired,
|
||||
onDetailsToggle: PropTypes.func.isRequired,
|
||||
onCommentCreate: PropTypes.func.isRequired,
|
||||
onCommentUpdate: PropTypes.func.isRequired,
|
||||
onCommentDelete: PropTypes.func.isRequired,
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
}
|
||||
|
||||
.moduleHeader {
|
||||
align-items: center;
|
||||
color: #17394d;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
height: 36px;
|
||||
justify-content: space-between;
|
||||
margin: 0 0 4px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.moduleIcon {
|
||||
|
@ -33,6 +35,24 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: #6b808c;
|
||||
float: right;
|
||||
font-weight: normal;
|
||||
margin-right: 0;
|
||||
padding: 6px 11px;
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
background: rgba(9, 30, 66, 0.08);
|
||||
color: #092d42;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin-left: -40px;
|
||||
margin-top: 12px;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useCallback, useRef } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../../lib/hooks';
|
||||
|
||||
import { useForm } from '../../../hooks';
|
||||
import { useClosableForm, useForm } from '../../../hooks';
|
||||
|
||||
import styles from './CommentAdd.module.scss';
|
||||
|
||||
|
@ -14,10 +15,16 @@ const DEFAULT_DATA = {
|
|||
|
||||
const CommentAdd = React.memo(({ onCreate }) => {
|
||||
const [t] = useTranslation();
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||
const [selectTextFieldState, selectTextField] = useToggle();
|
||||
|
||||
const textField = useRef(null);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpened(false);
|
||||
}, []);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
|
@ -32,7 +39,12 @@ const CommentAdd = React.memo(({ onCreate }) => {
|
|||
onCreate(cleanData);
|
||||
|
||||
setData(DEFAULT_DATA);
|
||||
}, [onCreate, data, setData]);
|
||||
selectTextField();
|
||||
}, [onCreate, data, setData, selectTextField]);
|
||||
|
||||
const handleFieldFocus = useCallback(() => {
|
||||
setIsOpened(true);
|
||||
}, []);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
|
@ -43,10 +55,16 @@ const CommentAdd = React.memo(({ onCreate }) => {
|
|||
[submit],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(close);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
useDidUpdate(() => {
|
||||
textField.current.ref.current.focus();
|
||||
}, [selectTextFieldState]);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<TextArea
|
||||
|
@ -55,15 +73,24 @@ const CommentAdd = React.memo(({ onCreate }) => {
|
|||
name="text"
|
||||
value={data.text}
|
||||
placeholder={t('common.writeComment')}
|
||||
minRows={3}
|
||||
minRows={isOpened ? 3 : 1}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onFocus={handleFieldFocus}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
{isOpened && (
|
||||
<div className={styles.controls}>
|
||||
<Button positive content={t('action.addComment')} disabled={!data.text} />
|
||||
<Button
|
||||
positive
|
||||
content={t('action.addComment')}
|
||||
onMouseOver={handleControlMouseOver}
|
||||
onMouseOut={handleControlMouseOut}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -34,6 +34,8 @@ const CardModal = React.memo(
|
|||
isSubscribed,
|
||||
isActionsFetching,
|
||||
isAllActionsFetched,
|
||||
isActionsDetailsVisible,
|
||||
isActionsDetailsFetching,
|
||||
listId,
|
||||
boardId,
|
||||
projectId,
|
||||
|
@ -67,6 +69,7 @@ const CardModal = React.memo(
|
|||
onAttachmentUpdate,
|
||||
onAttachmentDelete,
|
||||
onActionsFetch,
|
||||
onActionsDetailsToggle,
|
||||
onCommentActionCreate,
|
||||
onCommentActionUpdate,
|
||||
onCommentActionDelete,
|
||||
|
@ -358,9 +361,12 @@ const CardModal = React.memo(
|
|||
items={actions}
|
||||
isFetching={isActionsFetching}
|
||||
isAllFetched={isAllActionsFetched}
|
||||
isDetailsVisible={isActionsDetailsVisible}
|
||||
isDetailsFetching={isActionsDetailsFetching}
|
||||
canEdit={canEdit}
|
||||
canEditAllComments={canEditAllCommentActions}
|
||||
onFetch={onActionsFetch}
|
||||
onDetailsToggle={onActionsDetailsToggle}
|
||||
onCommentCreate={onCommentActionCreate}
|
||||
onCommentUpdate={onCommentActionUpdate}
|
||||
onCommentDelete={onCommentActionDelete}
|
||||
|
@ -486,6 +492,8 @@ CardModal.propTypes = {
|
|||
isSubscribed: PropTypes.bool.isRequired,
|
||||
isActionsFetching: PropTypes.bool.isRequired,
|
||||
isAllActionsFetched: PropTypes.bool.isRequired,
|
||||
isActionsDetailsVisible: PropTypes.bool.isRequired,
|
||||
isActionsDetailsFetching: PropTypes.bool.isRequired,
|
||||
listId: PropTypes.string.isRequired,
|
||||
boardId: PropTypes.string.isRequired,
|
||||
projectId: PropTypes.string.isRequired,
|
||||
|
@ -521,6 +529,7 @@ CardModal.propTypes = {
|
|||
onAttachmentUpdate: PropTypes.func.isRequired,
|
||||
onAttachmentDelete: PropTypes.func.isRequired,
|
||||
onActionsFetch: PropTypes.func.isRequired,
|
||||
onActionsDetailsToggle: PropTypes.func.isRequired,
|
||||
onCommentActionCreate: PropTypes.func.isRequired,
|
||||
onCommentActionUpdate: PropTypes.func.isRequired,
|
||||
onCommentActionDelete: PropTypes.func.isRequired,
|
||||
|
|
|
@ -225,6 +225,9 @@ export default {
|
|||
ACTIONS_FETCH: 'ACTIONS_FETCH',
|
||||
ACTIONS_FETCH__SUCCESS: 'ACTIONS_FETCH__SUCCESS',
|
||||
ACTIONS_FETCH__FAILURE: 'ACTIONS_FETCH__FAILURE',
|
||||
ACTIONS_DETAILS_TOGGLE: 'ACTIONS_DETAILS_TOGGLE',
|
||||
ACTIONS_DETAILS_TOGGLE__SUCCESS: 'ACTIONS_DETAILS_TOGGLE__SUCCESS',
|
||||
ACTIONS_DETAILS_TOGGLE__FAILURE: 'ACTIONS_DETAILS_TOGGLE__FAILURE',
|
||||
|
||||
/* Action */
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ const ACCESS_TOKEN_KEY = 'accessToken';
|
|||
const ACCESS_TOKEN_EXPIRES = 365;
|
||||
|
||||
const POSITION_GAP = 65535;
|
||||
const ACTIONS_LIMIT = 10;
|
||||
const ACTIONS_LIMIT = 50;
|
||||
|
||||
export default {
|
||||
SERVER_BASE_URL,
|
||||
|
|
|
@ -155,6 +155,7 @@ export default {
|
|||
/* Actions */
|
||||
|
||||
ACTIONS_IN_CURRENT_CARD_FETCH: `${PREFIX}/ACTIONS_IN_CURRENT_CARD_FETCH`,
|
||||
ACTIONS_DETAILS_IN_CURRENT_CARD_TOGGLE: `${PREFIX}/ACTIONS_DETAILS_IN_CURRENT_CARD_TOGGLE`,
|
||||
|
||||
/* Action */
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
moveTask,
|
||||
removeLabelFromCurrentCard,
|
||||
removeUserFromCurrentCard,
|
||||
toggleActionsDetailsInCurrentCard,
|
||||
transferCurrentCard,
|
||||
updateAttachment,
|
||||
updateCommentAction,
|
||||
|
@ -60,6 +61,8 @@ const mapStateToProps = (state) => {
|
|||
timer,
|
||||
isSubscribed,
|
||||
isActionsFetching,
|
||||
isActionsDetailsVisible,
|
||||
isActionsDetailsFetching,
|
||||
isAllActionsFetched,
|
||||
boardId,
|
||||
listId,
|
||||
|
@ -79,6 +82,8 @@ const mapStateToProps = (state) => {
|
|||
isSubscribed,
|
||||
isActionsFetching,
|
||||
isAllActionsFetched,
|
||||
isActionsDetailsVisible,
|
||||
isActionsDetailsFetching,
|
||||
listId,
|
||||
boardId,
|
||||
projectId,
|
||||
|
@ -118,6 +123,7 @@ const mapDispatchToProps = (dispatch) =>
|
|||
onAttachmentUpdate: updateAttachment,
|
||||
onAttachmentDelete: deleteAttachment,
|
||||
onActionsFetch: fetchActionsInCurrentCard,
|
||||
onActionsDetailsToggle: toggleActionsDetailsInCurrentCard,
|
||||
onCommentActionCreate: createCommentActionInCurrentCard,
|
||||
onCommentActionUpdate: updateCommentAction,
|
||||
onCommentActionDelete: deleteCommentAction,
|
||||
|
|
|
@ -195,6 +195,7 @@ export default {
|
|||
editTimer_title: 'Timer bearbeiten',
|
||||
editTitle_title: 'Titel bearbeiten',
|
||||
editUsername_title: 'Benutzername ändern',
|
||||
hideDetails: 'Details ausblenden',
|
||||
leaveBoard: 'Board verlassen',
|
||||
leaveProject: 'Projekt verlassen',
|
||||
logOut_title: 'Ausloggen',
|
||||
|
@ -210,6 +211,7 @@ export default {
|
|||
removeMember: 'Mitglied entfernen',
|
||||
save: 'Speichern',
|
||||
showAllAttachments: 'Alle Anhänge anzeigen ({{hidden}} versteckt)',
|
||||
showDetails: 'Details anzeigen',
|
||||
showFewerAttachments: 'Weniger Anhänge anzeigen',
|
||||
start: 'Start',
|
||||
stop: 'Stopp',
|
||||
|
|
|
@ -188,6 +188,7 @@ export default {
|
|||
editTimer_title: 'Edit Timer',
|
||||
editTitle_title: 'Edit Title',
|
||||
editUsername_title: 'Edit Username',
|
||||
hideDetails: 'Hide details',
|
||||
leaveBoard: 'Leave board',
|
||||
leaveProject: 'Leave project',
|
||||
logOut_title: 'Log Out',
|
||||
|
@ -203,6 +204,7 @@ export default {
|
|||
removeMember: 'Remove member',
|
||||
save: 'Save',
|
||||
showAllAttachments: 'Show all attachments ({{hidden}} hidden)',
|
||||
showDetails: 'Show details',
|
||||
showFewerAttachments: 'Show fewer attachments',
|
||||
start: 'Start',
|
||||
stop: 'Stop',
|
||||
|
|
|
@ -178,7 +178,8 @@ export default {
|
|||
editTask: 'Изменить задачу',
|
||||
editTimer: 'Изменить таймер',
|
||||
editTitle: 'Изменить название',
|
||||
editUsername_title: 'Изменить имя пользователя',
|
||||
editUsername: 'Изменить имя пользователя',
|
||||
hideDetails: 'Скрыть подробности',
|
||||
logOut: 'Выйти',
|
||||
makeCover: 'Сделать обложкой',
|
||||
move: 'Переместить',
|
||||
|
@ -190,6 +191,7 @@ export default {
|
|||
removeMember: 'Удалить участника',
|
||||
save: 'Сохранить',
|
||||
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
|
||||
showDetails: 'Показать подробности',
|
||||
showFewerAttachments: 'Показать меньше вложений',
|
||||
start: 'Начать',
|
||||
stop: 'Остановить',
|
||||
|
|
|
@ -50,6 +50,7 @@ export default class extends Model {
|
|||
|
||||
break;
|
||||
case ActionTypes.ACTIONS_FETCH__SUCCESS:
|
||||
case ActionTypes.ACTIONS_DETAILS_TOGGLE__SUCCESS:
|
||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||
payload.actions.forEach((action) => {
|
||||
Action.upsert(action);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Model, attr, fk, many, oneToOne } from 'redux-orm';
|
|||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import Config from '../constants/Config';
|
||||
import { ActionTypes as ActionTypesEnum } from '../constants/Enums';
|
||||
|
||||
export default class extends Model {
|
||||
static modelName = 'Card';
|
||||
|
@ -22,6 +23,12 @@ export default class extends Model {
|
|||
isAllActionsFetched: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isActionsDetailsVisible: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isActionsDetailsFetching: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
|
@ -195,6 +202,36 @@ export default class extends Model {
|
|||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.ACTIONS_DETAILS_TOGGLE: {
|
||||
const cardModel = Card.withId(payload.cardId);
|
||||
cardModel.isActionsDetailsVisible = payload.isVisible;
|
||||
|
||||
if (payload.isVisible) {
|
||||
cardModel.isActionsDetailsFetching = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.ACTIONS_DETAILS_TOGGLE__SUCCESS: {
|
||||
const cardModel = Card.withId(payload.cardId);
|
||||
|
||||
cardModel.update({
|
||||
isAllActionsFetched: payload.actions.length < Config.ACTIONS_LIMIT,
|
||||
isActionsDetailsFetching: false,
|
||||
});
|
||||
|
||||
cardModel.actions.toModelArray().forEach((actionModel) => {
|
||||
if (actionModel.notification) {
|
||||
actionModel.update({
|
||||
isInCard: false,
|
||||
});
|
||||
} else {
|
||||
actionModel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||
payload.cards.forEach((card) => {
|
||||
Card.upsert(card);
|
||||
|
@ -213,8 +250,16 @@ export default class extends Model {
|
|||
return this.attachments.orderBy('id', false);
|
||||
}
|
||||
|
||||
getOrderedInCardActionsQuerySet() {
|
||||
return this.actions.orderBy('id', false);
|
||||
getFilteredOrderedInCardActionsQuerySet() {
|
||||
const filter = {
|
||||
isInCard: true,
|
||||
};
|
||||
|
||||
if (!this.isActionsDetailsVisible) {
|
||||
filter.type = ActionTypesEnum.COMMENT_CARD;
|
||||
}
|
||||
|
||||
return this.actions.filter(filter).orderBy('id', false);
|
||||
}
|
||||
|
||||
getUnreadNotificationsQuerySet() {
|
||||
|
|
|
@ -83,7 +83,7 @@ export default class extends Model {
|
|||
return this.cards.orderBy('position');
|
||||
}
|
||||
|
||||
getOrderedFilteredCardsModelArray() {
|
||||
getFilteredOrderedCardsModelArray() {
|
||||
let cardModels = this.getOrderedCardsQuerySet().toModelArray();
|
||||
|
||||
const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Model, attr, fk } from 'redux-orm';
|
||||
import { Model, attr, fk, oneToOne } from 'redux-orm';
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
|
@ -15,16 +15,15 @@ export default class extends Model {
|
|||
as: 'user',
|
||||
relatedName: 'notifications',
|
||||
}),
|
||||
actionId: fk({
|
||||
to: 'Action',
|
||||
as: 'action',
|
||||
relatedName: 'notifications',
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
relatedName: 'notifications',
|
||||
}),
|
||||
actionId: oneToOne({
|
||||
to: 'Action',
|
||||
as: 'action',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, Notification) {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { call, put, select } from 'redux-saga/effects';
|
||||
|
||||
import request from '../request';
|
||||
import { lastActionIdByCardIdSelector, pathSelector } from '../../../selectors';
|
||||
import { fetchActions } from '../../../actions';
|
||||
import { cardByIdSelector, lastActionIdByCardIdSelector, pathSelector } from '../../../selectors';
|
||||
import { fetchActions, toggleActionsDetails } from '../../../actions';
|
||||
import api from '../../../api';
|
||||
|
||||
export function* fetchActionsService(cardId) {
|
||||
const { isActionsDetailsVisible } = yield select(cardByIdSelector, cardId);
|
||||
const lastId = yield select(lastActionIdByCardIdSelector, cardId);
|
||||
|
||||
yield put(fetchActions(cardId));
|
||||
|
@ -19,6 +20,7 @@ export function* fetchActionsService(cardId) {
|
|||
included: { users },
|
||||
} = yield call(request, api.getActions, cardId, {
|
||||
beforeId: lastId,
|
||||
withDetails: isActionsDetailsVisible,
|
||||
}));
|
||||
} catch (error) {
|
||||
yield put(fetchActions.failure(cardId, error));
|
||||
|
@ -33,3 +35,32 @@ export function* fetchActionsInCurrentCardService() {
|
|||
|
||||
yield call(fetchActionsService, cardId);
|
||||
}
|
||||
|
||||
export function* toggleActionsDetailsService(cardId, isVisible) {
|
||||
yield put(toggleActionsDetails(cardId, isVisible));
|
||||
|
||||
if (isVisible) {
|
||||
let actions;
|
||||
let users;
|
||||
|
||||
try {
|
||||
({
|
||||
items: actions,
|
||||
included: { users },
|
||||
} = yield call(request, api.getActions, cardId, {
|
||||
withDetails: isVisible,
|
||||
}));
|
||||
} catch (error) {
|
||||
yield put(toggleActionsDetails.failure(cardId, error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(toggleActionsDetails.success(cardId, actions, users));
|
||||
}
|
||||
}
|
||||
|
||||
export function* toggleActionsDetailsInCurrentCardService(isVisible) {
|
||||
const { cardId } = yield select(pathSelector);
|
||||
|
||||
yield call(toggleActionsDetailsService, cardId, isVisible);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
import { takeEvery } from 'redux-saga/effects';
|
||||
import { all, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { fetchActionsInCurrentCardService } from '../services';
|
||||
import {
|
||||
fetchActionsInCurrentCardService,
|
||||
toggleActionsDetailsInCurrentCardService,
|
||||
} from '../services';
|
||||
import EntryActionTypes from '../../../constants/EntryActionTypes';
|
||||
|
||||
export default function* actionsWatchers() {
|
||||
yield takeEvery(EntryActionTypes.ACTIONS_IN_CURRENT_CARD_FETCH, () =>
|
||||
yield all([
|
||||
takeEvery(EntryActionTypes.ACTIONS_IN_CURRENT_CARD_FETCH, () =>
|
||||
fetchActionsInCurrentCardService(),
|
||||
);
|
||||
),
|
||||
takeEvery(
|
||||
EntryActionTypes.ACTIONS_DETAILS_IN_CURRENT_CARD_TOGGLE,
|
||||
({ payload: { isVisible } }) => toggleActionsDetailsInCurrentCardService(isVisible),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ export const makeLastActionIdByCardIdSelector = () =>
|
|||
return cardModel;
|
||||
}
|
||||
|
||||
const lastActionModel = cardModel.getOrderedInCardActionsQuerySet().last();
|
||||
const lastActionModel = cardModel.getFilteredOrderedInCardActionsQuerySet().last();
|
||||
|
||||
return lastActionModel && lastActionModel.id;
|
||||
},
|
||||
|
@ -249,7 +249,7 @@ export const actionsForCurrentCardSelector = createSelector(
|
|||
}
|
||||
|
||||
return cardModel
|
||||
.getOrderedInCardActionsQuerySet()
|
||||
.getFilteredOrderedInCardActionsQuerySet()
|
||||
.toModelArray()
|
||||
.map((actionModel) => ({
|
||||
...actionModel.ref,
|
||||
|
|
|
@ -73,7 +73,7 @@ export const nextCardPositionSelector = createSelector(
|
|||
return listModel;
|
||||
}
|
||||
|
||||
return nextPosition(listModel.getOrderedFilteredCardsModelArray(), index, excludedId);
|
||||
return nextPosition(listModel.getFilteredOrderedCardsModelArray(), index, excludedId);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export const makeCardIdsByListIdSelector = () =>
|
|||
return listModel;
|
||||
}
|
||||
|
||||
return listModel.getOrderedFilteredCardsModelArray().map((cardModel) => cardModel.id);
|
||||
return listModel.getFilteredOrderedCardsModelArray().map((cardModel) => cardModel.id);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ module.exports = {
|
|||
type: 'string',
|
||||
regex: /^[0-9]+$/,
|
||||
},
|
||||
withDetails: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
|
@ -43,7 +46,11 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
|
||||
const actions = await sails.helpers.cards.getActions(card.id, inputs.beforeId);
|
||||
const actions = await sails.helpers.cards.getActions(
|
||||
card.id,
|
||||
inputs.beforeId,
|
||||
inputs.withDetails,
|
||||
);
|
||||
|
||||
const userIds = sails.helpers.utils.mapRecords(actions, 'userId', true);
|
||||
const users = await sails.helpers.users.getMany(userIds, true);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const LIMIT = 10;
|
||||
const LIMIT = 50;
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
|
@ -10,6 +10,10 @@ module.exports = {
|
|||
beforeId: {
|
||||
type: 'string',
|
||||
},
|
||||
withDetails: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
|
@ -23,6 +27,10 @@ module.exports = {
|
|||
};
|
||||
}
|
||||
|
||||
if (!inputs.withDetails) {
|
||||
criteria.type = Action.Types.COMMENT_CARD;
|
||||
}
|
||||
|
||||
return sails.helpers.actions.getMany(criteria, LIMIT);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
module.exports.up = (knex) =>
|
||||
knex.schema.table('action', (table) => {
|
||||
/* Indexes */
|
||||
|
||||
table.index('type');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('action', (table) => {
|
||||
table.dropIndex('type');
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue