fix: Little improvements and refactoring

pull/717/head
Maksim Eltyshev 2 years ago
parent 7b3155efe5
commit 6cec8fea44

@ -60,60 +60,62 @@ const handleListUpdate = (list) => ({
}, },
}); });
const deleteList = (id) => ({ const sortList = (id, data) => ({
type: ActionTypes.LIST_DELETE, type: ActionTypes.LIST_SORT,
payload: { payload: {
id, id,
data,
}, },
}); });
deleteList.success = (list) => ({ sortList.success = (list, cards) => ({
type: ActionTypes.LIST_DELETE__SUCCESS, type: ActionTypes.LIST_SORT__SUCCESS,
payload: { payload: {
list, list,
cards,
}, },
}); });
deleteList.failure = (id, error) => ({ sortList.failure = (id, error) => ({
type: ActionTypes.LIST_DELETE__FAILURE, type: ActionTypes.LIST_SORT__FAILURE,
payload: { payload: {
id, id,
error, error,
}, },
}); });
const handleListDelete = (list) => ({ const handleListSort = (list, cards) => ({
type: ActionTypes.LIST_DELETE_HANDLE, type: ActionTypes.LIST_SORT_HANDLE,
payload: { payload: {
list, list,
cards,
}, },
}); });
const sortList = (id, data) => ({ const deleteList = (id) => ({
type: ActionTypes.LIST_SORT, type: ActionTypes.LIST_DELETE,
payload: { payload: {
id, id,
data,
}, },
}); });
sortList.success = (list) => ({ deleteList.success = (list) => ({
type: ActionTypes.LIST_SORT__SUCCESS, type: ActionTypes.LIST_DELETE__SUCCESS,
payload: { payload: {
list, list,
}, },
}); });
sortList.failure = (id, error) => ({ deleteList.failure = (id, error) => ({
type: ActionTypes.LIST_SORT__FAILURE, type: ActionTypes.LIST_DELETE__FAILURE,
payload: { payload: {
id, id,
error, error,
}, },
}); });
const handleListSort = (list) => ({ const handleListDelete = (list) => ({
type: ActionTypes.LIST_SORT_HANDLE, type: ActionTypes.LIST_DELETE_HANDLE,
payload: { payload: {
list, list,
}, },
@ -124,8 +126,8 @@ export default {
handleListCreate, handleListCreate,
updateList, updateList,
handleListUpdate, handleListUpdate,
deleteList,
handleListDelete,
sortList, sortList,
handleListSort, handleListSort,
deleteList,
handleListDelete,
}; };

@ -1,4 +1,5 @@
import socket from './socket'; import socket from './socket';
import { transformCard } from './cards';
/* Actions */ /* Actions */
@ -7,13 +8,33 @@ const createList = (boardId, data, headers) =>
const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers); const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers);
const sortList = (id, data, headers) => socket.post(`/lists/${id}/sort`, data, headers); const sortList = (id, data, headers) =>
socket.post(`/lists/${id}/sort`, data, headers).then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
},
}));
const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers); const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers);
/* Event handlers */
const makeHandleListSort = (next) => (body) => {
next({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
},
});
};
export default { export default {
createList, createList,
updateList, updateList,
sortList, sortList,
deleteList, deleteList,
makeHandleListSort,
}; };

@ -5,17 +5,17 @@ import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui'; import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks'; import { useSteps } from '../../hooks';
import ListSortStep from '../ListSortStep';
import DeleteStep from '../DeleteStep'; import DeleteStep from '../DeleteStep';
import styles from './ActionsStep.module.scss'; import styles from './ActionsStep.module.scss';
import SortStep from '../SortStep';
const StepTypes = { const StepTypes = {
DELETE: 'DELETE', DELETE: 'DELETE',
SORT: 'SORT', SORT: 'SORT',
}; };
const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClose }) => { const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onSort, onDelete, onClose }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps(); const [step, openStep, handleBack] = useSteps();
@ -29,16 +29,29 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo
onClose(); onClose();
}, [onCardAdd, onClose]); }, [onCardAdd, onClose]);
const handleSortClick = useCallback(() => {
openStep(StepTypes.SORT);
}, [openStep]);
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE); openStep(StepTypes.DELETE);
}, [openStep]); }, [openStep]);
const handleSortClick = useCallback(() => { const handleSortTypeSelect = useCallback(
openStep(StepTypes.SORT); (type) => {
}, [openStep]); onSort({
type,
});
onClose();
},
[onSort, onClose],
);
if (step && step.type) { if (step && step.type) {
switch (step.type) { switch (step.type) {
case StepTypes.SORT:
return <ListSortStep onTypeSelect={handleSortTypeSelect} onBack={handleBack} />;
case StepTypes.DELETE: case StepTypes.DELETE:
return ( return (
<DeleteStep <DeleteStep
@ -49,8 +62,6 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo
onBack={handleBack} onBack={handleBack}
/> />
); );
case StepTypes.SORT:
return <SortStep title="common.sortList" onSort={onSort} onBack={handleBack} />;
default: default:
} }
} }
@ -64,11 +75,6 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo
</Popup.Header> </Popup.Header>
<Popup.Content> <Popup.Content>
<Menu secondary vertical className={styles.menu}> <Menu secondary vertical className={styles.menu}>
<Menu.Item className={styles.menuItem} onClick={handleSortClick}>
{t('action.sort', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}> <Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
{t('action.editTitle', { {t('action.editTitle', {
context: 'title', context: 'title',
@ -79,6 +85,11 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo
context: 'title', context: 'title',
})} })}
</Menu.Item> </Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleSortClick}>
{t('action.sortList', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}> <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteList', { {t('action.deleteList', {
context: 'title', context: 'title',

@ -171,8 +171,8 @@ List.propTypes = {
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired, onSort: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onCardCreate: PropTypes.func.isRequired, onCardCreate: PropTypes.func.isRequired,
}; };

@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { ListSortTypes } from '../../constants/Enums';
import styles from './ListSortStep.module.scss';
const ListSortStep = React.memo(({ onTypeSelect, onBack }) => {
const [t] = useTranslation();
return (
<>
<Popup.Header onBack={onBack}>
{t('common.sortList', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Menu secondary vertical className={styles.menu}>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.NAME_ASC)}
>
{t('common.name')}
</Menu.Item>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.DUE_DATE_ASC)}
>
{t('common.dueDate')}
</Menu.Item>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.CREATED_AT_ASC)}
>
{t('common.oldestFirst')}
</Menu.Item>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.CREATED_AT_DESC)}
>
{t('common.newestFirst')}
</Menu.Item>
</Menu>
</Popup.Content>
</>
);
});
ListSortStep.propTypes = {
onTypeSelect: PropTypes.func.isRequired,
onBack: PropTypes.func,
};
ListSortStep.defaultProps = {
onBack: undefined,
};
export default ListSortStep;

@ -0,0 +1,11 @@
:global(#app) {
.menu {
margin: -7px -12px -5px;
width: calc(100% + 24px);
}
.menuItem {
margin: 0;
padding-left: 14px;
}
}

@ -0,0 +1,3 @@
import ListSortStep from './ListSortStep';
export default ListSortStep;

@ -1,59 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import styles from './SortStep.module.scss';
const SortStep = React.memo(({ title, onSort, onBack }) => {
const [t] = useTranslation();
const handeClick = (sortType) => onSort({ sortType });
return (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Menu secondary vertical className={styles.menu}>
<Menu.Item className={styles.menuItem} onClick={() => handeClick('createdat_asc')}>
{t('action.sort.createdFirst', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={() => handeClick('createdat_desc')}>
{t('action.sort.createdLast', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={() => handeClick('name_asc')}>
{t('action.sort.name', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={() => handeClick('duedate_asc')}>
{t('action.sort.due', {
context: 'title',
})}
</Menu.Item>
</Menu>
</Popup.Content>
</>
);
});
SortStep.propTypes = {
title: PropTypes.string.isRequired,
onSort: PropTypes.func.isRequired,
onBack: PropTypes.func,
};
SortStep.defaultProps = {
onBack: undefined,
};
export default SortStep;

@ -1,3 +0,0 @@
import SortStep from './SortStep';
export default SortStep;

@ -8,6 +8,13 @@ export const BoardMembershipRoles = {
VIEWER: 'viewer', VIEWER: 'viewer',
}; };
export const ListSortTypes = {
NAME_ASC: 'name_asc',
DUE_DATE_ASC: 'dueDate_asc',
CREATED_AT_ASC: 'createdAt_asc',
CREATED_AT_DESC: 'createdAt_desc',
};
export const ActivityTypes = { export const ActivityTypes = {
CREATE_CARD: 'createCard', CREATE_CARD: 'createCard',
MOVE_CARD: 'moveCard', MOVE_CARD: 'moveCard',

@ -33,8 +33,8 @@ const mapDispatchToProps = (dispatch, { id }) =>
bindActionCreators( bindActionCreators(
{ {
onUpdate: (data) => entryActions.updateList(id, data), onUpdate: (data) => entryActions.updateList(id, data),
onDelete: () => entryActions.deleteList(id),
onSort: (data) => entryActions.sortList(id, data), onSort: (data) => entryActions.sortList(id, data),
onDelete: () => entryActions.deleteList(id),
onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen), onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen),
}, },
dispatch, dispatch,

@ -37,20 +37,6 @@ const moveList = (id, index) => ({
}, },
}); });
const deleteList = (id) => ({
type: EntryActionTypes.LIST_DELETE,
payload: {
id,
},
});
const handleListDelete = (list) => ({
type: EntryActionTypes.LIST_DELETE_HANDLE,
payload: {
list,
},
});
const sortList = (id, data) => { const sortList = (id, data) => {
return { return {
type: EntryActionTypes.LIST_SORT, type: EntryActionTypes.LIST_SORT,
@ -61,8 +47,23 @@ const sortList = (id, data) => {
}; };
}; };
const handleListSort = (list) => ({ const handleListSort = (list, cards) => ({
type: EntryActionTypes.LIST_SORT_HANDLE, type: EntryActionTypes.LIST_SORT_HANDLE,
payload: {
list,
cards,
},
});
const deleteList = (id) => ({
type: EntryActionTypes.LIST_DELETE,
payload: {
id,
},
});
const handleListDelete = (list) => ({
type: EntryActionTypes.LIST_DELETE_HANDLE,
payload: { payload: {
list, list,
}, },
@ -74,8 +75,8 @@ export default {
updateList, updateList,
handleListUpdate, handleListUpdate,
moveList, moveList,
deleteList,
handleListDelete,
sortList, sortList,
handleListSort, handleListSort,
deleteList,
handleListDelete,
}; };

@ -60,6 +60,7 @@ export default {
currentPassword: 'Current password', currentPassword: 'Current password',
dangerZone_title: 'Danger Zone', dangerZone_title: 'Danger Zone',
date: 'Date', date: 'Date',
dueDate: 'Due date',
dueDate_title: 'Due Date', dueDate_title: 'Due Date',
deleteAttachment_title: 'Delete Attachment', deleteAttachment_title: 'Delete Attachment',
deleteBoard_title: 'Delete Board', deleteBoard_title: 'Delete Board',
@ -112,6 +113,7 @@ export default {
minutes: 'Minutes', minutes: 'Minutes',
moveCard_title: 'Move Card', moveCard_title: 'Move Card',
name: 'Name', name: 'Name',
newestFirst: 'Newest first',
newEmail: 'New e-mail', newEmail: 'New e-mail',
newPassword: 'New password', newPassword: 'New password',
newUsername: 'New username', newUsername: 'New username',
@ -121,6 +123,7 @@ export default {
noProjects: 'No projects', noProjects: 'No projects',
notifications: 'Notifications', notifications: 'Notifications',
noUnreadNotifications: 'No unread notifications.', noUnreadNotifications: 'No unread notifications.',
oldestFirst: 'Oldest first',
openBoard_title: 'Open Board', openBoard_title: 'Open Board',
optional_inline: 'optional', optional_inline: 'optional',
organization: 'Organization', organization: 'Organization',
@ -141,6 +144,7 @@ export default {
selectPermissions_title: 'Select Permissions', selectPermissions_title: 'Select Permissions',
selectProject: 'Select project', selectProject: 'Select project',
settings: 'Settings', settings: 'Settings',
sortList_title: 'Sort List',
stopwatch: 'Stopwatch', stopwatch: 'Stopwatch',
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default', subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
taskActions_title: 'Task Actions', taskActions_title: 'Task Actions',
@ -228,6 +232,7 @@ export default {
showAllAttachments: 'Show all attachments ({{hidden}} hidden)', showAllAttachments: 'Show all attachments ({{hidden}} hidden)',
showDetails: 'Show details', showDetails: 'Show details',
showFewerAttachments: 'Show fewer attachments', showFewerAttachments: 'Show fewer attachments',
sortList_title: 'Sort List',
start: 'Start', start: 'Start',
stop: 'Stop', stop: 'Stop',
subscribe: 'Subscribe', subscribe: 'Subscribe',

@ -164,6 +164,14 @@ export default class extends BaseModel {
Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId); Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId);
} catch {} // eslint-disable-line no-empty } catch {} // eslint-disable-line no-empty
break;
case ActionTypes.LIST_SORT__SUCCESS:
case ActionTypes.LIST_SORT_HANDLE:
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
payload.cards.forEach((card) => {
Card.upsert(card);
});
break; break;
case ActionTypes.CARD_CREATE: case ActionTypes.CARD_CREATE:
case ActionTypes.CARD_UPDATE__SUCCESS: case ActionTypes.CARD_UPDATE__SUCCESS:
@ -282,12 +290,6 @@ export default class extends BaseModel {
break; break;
} }
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
payload.cards.forEach((card) => {
Card.upsert(card);
});
break;
default: default:
} }
} }

@ -50,6 +50,8 @@ export default class extends BaseModel {
case ActionTypes.LIST_CREATE_HANDLE: case ActionTypes.LIST_CREATE_HANDLE:
case ActionTypes.LIST_UPDATE__SUCCESS: case ActionTypes.LIST_UPDATE__SUCCESS:
case ActionTypes.LIST_UPDATE_HANDLE: case ActionTypes.LIST_UPDATE_HANDLE:
case ActionTypes.LIST_SORT__SUCCESS:
case ActionTypes.LIST_SORT_HANDLE:
List.upsert(payload.list); List.upsert(payload.list);
break; break;
@ -66,9 +68,6 @@ export default class extends BaseModel {
List.withId(payload.id).deleteWithRelated(); List.withId(payload.id).deleteWithRelated();
break; break;
// Possible improved flow for updating
// case ActionTypes.LIST_SORT__SUCCESS:
// case ActionTypes.LIST_SORT_HANDLE:
case ActionTypes.LIST_DELETE__SUCCESS: case ActionTypes.LIST_DELETE__SUCCESS:
case ActionTypes.LIST_DELETE_HANDLE: { case ActionTypes.LIST_DELETE_HANDLE: {
const listModel = List.withId(payload.list.id); const listModel = List.withId(payload.list.id);

@ -61,31 +61,37 @@ export function* handleListUpdate(list) {
yield put(actions.handleListUpdate(list)); yield put(actions.handleListUpdate(list));
} }
export function* moveList(id, index) {
const { boardId } = yield select(selectors.selectListById, id);
const position = yield select(selectors.selectNextListPosition, boardId, index, id);
yield call(updateList, id, {
position,
});
}
// TODO: sort locally
export function* sortList(id, data) { export function* sortList(id, data) {
yield put(actions.sortList(id, data)); yield put(actions.sortList(id, data));
let list; let list;
let cards;
try { try {
({ item: list } = yield call(request, api.sortList, id, data)); ({
item: list,
included: { cards },
} = yield call(request, api.sortList, id, data));
} catch (error) { } catch (error) {
yield put(actions.sortList.failure(id, error)); yield put(actions.sortList.failure(id, error));
return; return;
} }
yield put(actions.sortList.success(list)); yield put(actions.sortList.success(list, cards));
} }
export function* handleListSort(list) { export function* handleListSort(list, cards) {
yield put(actions.handleListSort(list)); yield put(actions.handleListSort(list, cards));
}
export function* moveList(id, index) {
const { boardId } = yield select(selectors.selectListById, id);
const position = yield select(selectors.selectNextListPosition, boardId, index, id);
yield call(updateList, id, {
position,
});
} }
export function* deleteList(id) { export function* deleteList(id) {
@ -112,9 +118,9 @@ export default {
handleListCreate, handleListCreate,
updateList, updateList,
handleListUpdate, handleListUpdate,
moveList,
sortList, sortList,
handleListSort, handleListSort,
moveList,
deleteList, deleteList,
handleListDelete, handleListDelete,
}; };

@ -17,14 +17,14 @@ export default function* listsWatchers() {
takeEvery(EntryActionTypes.LIST_UPDATE_HANDLE, ({ payload: { list } }) => takeEvery(EntryActionTypes.LIST_UPDATE_HANDLE, ({ payload: { list } }) =>
services.handleListUpdate(list), services.handleListUpdate(list),
), ),
takeEvery(EntryActionTypes.LIST_MOVE, ({ payload: { id, index } }) =>
services.moveList(id, index),
),
takeEvery(EntryActionTypes.LIST_SORT, ({ payload: { id, data } }) => takeEvery(EntryActionTypes.LIST_SORT, ({ payload: { id, data } }) =>
services.sortList(id, data), services.sortList(id, data),
), ),
takeEvery(EntryActionTypes.LIST_SORT_HANDLE, ({ payload: { list } }) => takeEvery(EntryActionTypes.LIST_SORT_HANDLE, ({ payload: { list, cards } }) =>
services.handleListSort(list), services.handleListSort(list, cards),
),
takeEvery(EntryActionTypes.LIST_MOVE, ({ payload: { id, index } }) =>
services.moveList(id, index),
), ),
takeEvery(EntryActionTypes.LIST_DELETE, ({ payload: { id } }) => services.deleteList(id)), takeEvery(EntryActionTypes.LIST_DELETE, ({ payload: { id } }) => services.deleteList(id)),
takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list } }) => takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list } }) =>

@ -84,6 +84,10 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleListUpdate(item)); emit(entryActions.handleListUpdate(item));
}; };
const handleListSort = api.makeHandleListSort(({ item, included: { cards } }) => {
emit(entryActions.handleListSort(item, cards));
});
const handleListDelete = ({ item }) => { const handleListDelete = ({ item }) => {
emit(entryActions.handleListDelete(item)); emit(entryActions.handleListDelete(item));
}; };
@ -198,6 +202,7 @@ const createSocketEventsChannel = () =>
socket.on('listCreate', handleListCreate); socket.on('listCreate', handleListCreate);
socket.on('listUpdate', handleListUpdate); socket.on('listUpdate', handleListUpdate);
socket.on('listSort', handleListSort);
socket.on('listDelete', handleListDelete); socket.on('listDelete', handleListDelete);
socket.on('labelCreate', handleLabelCreate); socket.on('labelCreate', handleLabelCreate);
@ -256,6 +261,7 @@ const createSocketEventsChannel = () =>
socket.off('listCreate', handleListCreate); socket.off('listCreate', handleListCreate);
socket.off('listUpdate', handleListUpdate); socket.off('listUpdate', handleListUpdate);
socket.off('listSort', handleListSort);
socket.off('listDelete', handleListDelete); socket.off('listDelete', handleListDelete);
socket.off('labelCreate', handleLabelCreate); socket.off('labelCreate', handleLabelCreate);

@ -14,10 +14,9 @@ module.exports = {
regex: /^[0-9]+$/, regex: /^[0-9]+$/,
required: true, required: true,
}, },
sortType: { type: {
type: 'string', type: 'string',
isIn: ['name_asc', 'duedate_asc', 'createdat_asc', 'createdat_desc'], isIn: Object.values(List.SortTypes),
defaultsTo: 'name_asc',
}, },
}, },
@ -33,7 +32,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { list } = await sails.helpers.lists const { list } = await sails.helpers.lists
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND); .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
@ -43,25 +42,24 @@ module.exports = {
}); });
if (!boardMembership) { if (!boardMembership) {
throw Errors.LIST_NOT_FOUND; // This should rather be NOT_ENOUGH_RIGHTS if it's a permissions issue throw Errors.LIST_NOT_FOUND;
} }
if (boardMembership.role !== BoardMembership.Roles.EDITOR) { if (boardMembership.role !== BoardMembership.Roles.EDITOR) {
throw Errors.NOT_ENOUGH_RIGHTS; throw Errors.NOT_ENOUGH_RIGHTS;
} }
list = await sails.helpers.lists.sortOne.with({ const cards = await sails.helpers.lists.sortOne.with({
record: list, record: list,
type: inputs.type,
request: this.req, request: this.req,
sortType: inputs.sortType,
}); });
if (!list) {
throw Errors.LIST_NOT_FOUND;
}
return { return {
item: list, item: list,
included: {
cards,
},
}; };
}, },
}; };

@ -1,68 +1,70 @@
const List = require('../../models/List');
module.exports = { module.exports = {
inputs: { inputs: {
record: { record: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
type: {
type: 'string',
isIn: Object.values(List.SortTypes),
defaultsTo: List.SortTypes.NAME_ASC,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
sortType: {
type: 'string',
isIn: ['name_asc', 'duedate_asc', 'createdat_asc', 'createdat_desc'],
defaultsTo: 'name_asc',
},
}, },
async fn(inputs) { async fn(inputs) {
const list = await List.findOne({ id: inputs.record.id }); let cards = await sails.helpers.lists.getCards(inputs.record.id);
if (list) {
let cards = await Card.find({ listId: inputs.record.id });
switch (inputs.sortType) {
case 'name_asc':
cards.sort((a, b) => a.name.localeCompare(b.name));
break;
case 'duedate_asc':
cards.sort((a, b) => {
if (a.dueDate === null) return 1;
if (b.dueDate === null) return -1;
return new Date(a.dueDate) - new Date(b.dueDate);
});
break;
case 'createdat_asc':
cards.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
break;
case 'createdat_desc':
cards.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
break;
default:
throw new Error("Invalid sort type specified");
}
const positions = cards.map((c) => c.position).sort((a, b) => a - b); switch (inputs.type) {
case List.SortTypes.NAME_ASC:
for (let i = 0; i < cards.length; i++) { cards.sort((a, b) => a.name.localeCompare(b.name));
let card = cards[i]; break;
let nextPosition = positions[i]; case List.SortTypes.DUE_DATE_ASC:
cards.sort((a, b) => {
await Card.updateOne({ id: card.id }).set({ if (a.dueDate === null) return 1;
position: nextPosition, if (b.dueDate === null) return -1;
return new Date(a.dueDate) - new Date(b.dueDate);
}); });
break;
case List.SortTypes.CREATED_AT_ASC:
cards.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
break;
case List.SortTypes.CREATED_AT_DESC:
cards.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
break;
default:
throw new Error('Invalid sort type specified');
}
sails.sockets.broadcast(`board:${card.boardId}`, 'cardUpdate', { const positions = cards.map((c) => c.position).sort((a, b) => a - b);
item: {
id: card.id,
position: nextPosition,
},
});
}
sails.sockets.broadcast(`board:${list.boardId}`, 'listSorted', { cards = await Promise.all(
item: list, cards.map(({ id }, index) =>
}, inputs.request); Card.updateOne({
} id,
listId: inputs.record.id,
}).set({
position: positions[index],
}),
),
);
sails.sockets.broadcast(
`board:${inputs.record.boardId}`,
'listSort',
{
item: inputs.record,
included: {
cards,
},
},
inputs.request,
);
return list; return cards;
}, },
}; };

@ -5,7 +5,16 @@
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models * @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/ */
const SortTypes = {
NAME_ASC: 'name_asc',
DUE_DATE_ASC: 'dueDate_asc',
CREATED_AT_ASC: 'createdAt_asc',
CREATED_AT_DESC: 'createdAt_desc',
};
module.exports = { module.exports = {
SortTypes,
attributes: { attributes: {
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗ // ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗ // ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗

Loading…
Cancel
Save