diff --git a/client/src/actions/lists.js b/client/src/actions/lists.js index fcab0fc..4f85864 100644 --- a/client/src/actions/lists.js +++ b/client/src/actions/lists.js @@ -60,60 +60,62 @@ const handleListUpdate = (list) => ({ }, }); -const deleteList = (id) => ({ - type: ActionTypes.LIST_DELETE, +const sortList = (id, data) => ({ + type: ActionTypes.LIST_SORT, payload: { id, + data, }, }); -deleteList.success = (list) => ({ - type: ActionTypes.LIST_DELETE__SUCCESS, +sortList.success = (list, cards) => ({ + type: ActionTypes.LIST_SORT__SUCCESS, payload: { list, + cards, }, }); -deleteList.failure = (id, error) => ({ - type: ActionTypes.LIST_DELETE__FAILURE, +sortList.failure = (id, error) => ({ + type: ActionTypes.LIST_SORT__FAILURE, payload: { id, error, }, }); -const handleListDelete = (list) => ({ - type: ActionTypes.LIST_DELETE_HANDLE, +const handleListSort = (list, cards) => ({ + type: ActionTypes.LIST_SORT_HANDLE, payload: { list, + cards, }, }); -const sortList = (id, data) => ({ - type: ActionTypes.LIST_SORT, +const deleteList = (id) => ({ + type: ActionTypes.LIST_DELETE, payload: { id, - data, }, }); -sortList.success = (list) => ({ - type: ActionTypes.LIST_SORT__SUCCESS, +deleteList.success = (list) => ({ + type: ActionTypes.LIST_DELETE__SUCCESS, payload: { list, }, }); -sortList.failure = (id, error) => ({ - type: ActionTypes.LIST_SORT__FAILURE, +deleteList.failure = (id, error) => ({ + type: ActionTypes.LIST_DELETE__FAILURE, payload: { id, error, }, }); -const handleListSort = (list) => ({ - type: ActionTypes.LIST_SORT_HANDLE, +const handleListDelete = (list) => ({ + type: ActionTypes.LIST_DELETE_HANDLE, payload: { list, }, @@ -124,8 +126,8 @@ export default { handleListCreate, updateList, handleListUpdate, - deleteList, - handleListDelete, sortList, handleListSort, + deleteList, + handleListDelete, }; diff --git a/client/src/api/lists.js b/client/src/api/lists.js index aa36f0a..2ed11c5 100755 --- a/client/src/api/lists.js +++ b/client/src/api/lists.js @@ -1,4 +1,5 @@ import socket from './socket'; +import { transformCard } from './cards'; /* Actions */ @@ -7,13 +8,33 @@ const createList = (boardId, 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); +/* Event handlers */ + +const makeHandleListSort = (next) => (body) => { + next({ + ...body, + included: { + ...body.included, + cards: body.included.cards.map(transformCard), + }, + }); +}; + export default { createList, updateList, sortList, deleteList, + makeHandleListSort, }; diff --git a/client/src/components/List/ActionsStep.jsx b/client/src/components/List/ActionsStep.jsx index d4e8b39..bc04826 100755 --- a/client/src/components/List/ActionsStep.jsx +++ b/client/src/components/List/ActionsStep.jsx @@ -5,17 +5,17 @@ import { Menu } from 'semantic-ui-react'; import { Popup } from '../../lib/custom-ui'; import { useSteps } from '../../hooks'; +import ListSortStep from '../ListSortStep'; import DeleteStep from '../DeleteStep'; import styles from './ActionsStep.module.scss'; -import SortStep from '../SortStep'; const StepTypes = { DELETE: 'DELETE', SORT: 'SORT', }; -const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClose }) => { +const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onSort, onDelete, onClose }) => { const [t] = useTranslation(); const [step, openStep, handleBack] = useSteps(); @@ -29,16 +29,29 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo onClose(); }, [onCardAdd, onClose]); + const handleSortClick = useCallback(() => { + openStep(StepTypes.SORT); + }, [openStep]); + const handleDeleteClick = useCallback(() => { openStep(StepTypes.DELETE); }, [openStep]); - const handleSortClick = useCallback(() => { - openStep(StepTypes.SORT); - }, [openStep]); + const handleSortTypeSelect = useCallback( + (type) => { + onSort({ + type, + }); + + onClose(); + }, + [onSort, onClose], + ); if (step && step.type) { switch (step.type) { + case StepTypes.SORT: + return ; case StepTypes.DELETE: return ( ); - case StepTypes.SORT: - return ; default: } } @@ -64,11 +75,6 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo - - {t('action.sort', { - context: 'title', - })} - {t('action.editTitle', { context: 'title', @@ -79,6 +85,11 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClo context: 'title', })} + + {t('action.sortList', { + context: 'title', + })} + {t('action.deleteList', { context: 'title', diff --git a/client/src/components/List/List.jsx b/client/src/components/List/List.jsx index f6a600f..25c85e5 100755 --- a/client/src/components/List/List.jsx +++ b/client/src/components/List/List.jsx @@ -171,8 +171,8 @@ List.propTypes = { cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types canEdit: PropTypes.bool.isRequired, onUpdate: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, onSort: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, onCardCreate: PropTypes.func.isRequired, }; diff --git a/client/src/components/ListSortStep/ListSortStep.jsx b/client/src/components/ListSortStep/ListSortStep.jsx new file mode 100644 index 0000000..daf9e3b --- /dev/null +++ b/client/src/components/ListSortStep/ListSortStep.jsx @@ -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 ( + <> + + {t('common.sortList', { + context: 'title', + })} + + + + onTypeSelect(ListSortTypes.NAME_ASC)} + > + {t('common.name')} + + onTypeSelect(ListSortTypes.DUE_DATE_ASC)} + > + {t('common.dueDate')} + + onTypeSelect(ListSortTypes.CREATED_AT_ASC)} + > + {t('common.oldestFirst')} + + onTypeSelect(ListSortTypes.CREATED_AT_DESC)} + > + {t('common.newestFirst')} + + + + + ); +}); + +ListSortStep.propTypes = { + onTypeSelect: PropTypes.func.isRequired, + onBack: PropTypes.func, +}; + +ListSortStep.defaultProps = { + onBack: undefined, +}; + +export default ListSortStep; diff --git a/client/src/components/ListSortStep/ListSortStep.module.scss b/client/src/components/ListSortStep/ListSortStep.module.scss new file mode 100644 index 0000000..135ce58 --- /dev/null +++ b/client/src/components/ListSortStep/ListSortStep.module.scss @@ -0,0 +1,11 @@ +:global(#app) { + .menu { + margin: -7px -12px -5px; + width: calc(100% + 24px); + } + + .menuItem { + margin: 0; + padding-left: 14px; + } +} diff --git a/client/src/components/ListSortStep/index.js b/client/src/components/ListSortStep/index.js new file mode 100644 index 0000000..2ffd741 --- /dev/null +++ b/client/src/components/ListSortStep/index.js @@ -0,0 +1,3 @@ +import ListSortStep from './ListSortStep'; + +export default ListSortStep; diff --git a/client/src/components/SortStep/SortStep.jsx b/client/src/components/SortStep/SortStep.jsx deleted file mode 100644 index bcf3d54..0000000 --- a/client/src/components/SortStep/SortStep.jsx +++ /dev/null @@ -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 ( - <> - - {t(title, { - context: 'title', - })} - - - - handeClick('createdat_asc')}> - {t('action.sort.createdFirst', { - context: 'title', - })} - - handeClick('createdat_desc')}> - {t('action.sort.createdLast', { - context: 'title', - })} - - handeClick('name_asc')}> - {t('action.sort.name', { - context: 'title', - })} - - handeClick('duedate_asc')}> - {t('action.sort.due', { - context: 'title', - })} - - - - - ); -}); - -SortStep.propTypes = { - title: PropTypes.string.isRequired, - onSort: PropTypes.func.isRequired, - onBack: PropTypes.func, -}; - -SortStep.defaultProps = { - onBack: undefined, -}; - -export default SortStep; diff --git a/client/src/components/SortStep/SortStep.module.scss b/client/src/components/SortStep/SortStep.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/components/SortStep/index.js b/client/src/components/SortStep/index.js deleted file mode 100644 index 4388a89..0000000 --- a/client/src/components/SortStep/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import SortStep from './SortStep'; - -export default SortStep; diff --git a/client/src/constants/Enums.js b/client/src/constants/Enums.js index 1fb5cdc..b85a559 100755 --- a/client/src/constants/Enums.js +++ b/client/src/constants/Enums.js @@ -8,6 +8,13 @@ export const BoardMembershipRoles = { 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 = { CREATE_CARD: 'createCard', MOVE_CARD: 'moveCard', diff --git a/client/src/containers/ListContainer.js b/client/src/containers/ListContainer.js index d3e6491..6651d2c 100755 --- a/client/src/containers/ListContainer.js +++ b/client/src/containers/ListContainer.js @@ -33,8 +33,8 @@ const mapDispatchToProps = (dispatch, { id }) => bindActionCreators( { onUpdate: (data) => entryActions.updateList(id, data), - onDelete: () => entryActions.deleteList(id), onSort: (data) => entryActions.sortList(id, data), + onDelete: () => entryActions.deleteList(id), onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen), }, dispatch, diff --git a/client/src/entry-actions/lists.js b/client/src/entry-actions/lists.js index 835d272..83b857e 100755 --- a/client/src/entry-actions/lists.js +++ b/client/src/entry-actions/lists.js @@ -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) => { return { type: EntryActionTypes.LIST_SORT, @@ -61,8 +47,23 @@ const sortList = (id, data) => { }; }; -const handleListSort = (list) => ({ +const handleListSort = (list, cards) => ({ 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: { list, }, @@ -74,8 +75,8 @@ export default { updateList, handleListUpdate, moveList, - deleteList, - handleListDelete, sortList, handleListSort, + deleteList, + handleListDelete, }; diff --git a/client/src/locales/en/core.js b/client/src/locales/en/core.js index 41e4494..d346354 100644 --- a/client/src/locales/en/core.js +++ b/client/src/locales/en/core.js @@ -60,6 +60,7 @@ export default { currentPassword: 'Current password', dangerZone_title: 'Danger Zone', date: 'Date', + dueDate: 'Due date', dueDate_title: 'Due Date', deleteAttachment_title: 'Delete Attachment', deleteBoard_title: 'Delete Board', @@ -112,6 +113,7 @@ export default { minutes: 'Minutes', moveCard_title: 'Move Card', name: 'Name', + newestFirst: 'Newest first', newEmail: 'New e-mail', newPassword: 'New password', newUsername: 'New username', @@ -121,6 +123,7 @@ export default { noProjects: 'No projects', notifications: 'Notifications', noUnreadNotifications: 'No unread notifications.', + oldestFirst: 'Oldest first', openBoard_title: 'Open Board', optional_inline: 'optional', organization: 'Organization', @@ -141,6 +144,7 @@ export default { selectPermissions_title: 'Select Permissions', selectProject: 'Select project', settings: 'Settings', + sortList_title: 'Sort List', stopwatch: 'Stopwatch', subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default', taskActions_title: 'Task Actions', @@ -228,6 +232,7 @@ export default { showAllAttachments: 'Show all attachments ({{hidden}} hidden)', showDetails: 'Show details', showFewerAttachments: 'Show fewer attachments', + sortList_title: 'Sort List', start: 'Start', stop: 'Stop', subscribe: 'Subscribe', diff --git a/client/src/models/Card.js b/client/src/models/Card.js index 3c45256..af11fd5 100755 --- a/client/src/models/Card.js +++ b/client/src/models/Card.js @@ -164,6 +164,14 @@ export default class extends BaseModel { Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId); } 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; case ActionTypes.CARD_CREATE: case ActionTypes.CARD_UPDATE__SUCCESS: @@ -282,12 +290,6 @@ export default class extends BaseModel { break; } - case ActionTypes.NOTIFICATION_CREATE_HANDLE: - payload.cards.forEach((card) => { - Card.upsert(card); - }); - - break; default: } } diff --git a/client/src/models/List.js b/client/src/models/List.js index 045622e..b664129 100755 --- a/client/src/models/List.js +++ b/client/src/models/List.js @@ -50,6 +50,8 @@ export default class extends BaseModel { case ActionTypes.LIST_CREATE_HANDLE: case ActionTypes.LIST_UPDATE__SUCCESS: case ActionTypes.LIST_UPDATE_HANDLE: + case ActionTypes.LIST_SORT__SUCCESS: + case ActionTypes.LIST_SORT_HANDLE: List.upsert(payload.list); break; @@ -66,9 +68,6 @@ export default class extends BaseModel { List.withId(payload.id).deleteWithRelated(); 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_HANDLE: { const listModel = List.withId(payload.list.id); diff --git a/client/src/sagas/core/services/lists.js b/client/src/sagas/core/services/lists.js index 6dd1e0d..5ed56d7 100644 --- a/client/src/sagas/core/services/lists.js +++ b/client/src/sagas/core/services/lists.js @@ -61,31 +61,37 @@ export function* 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) { yield put(actions.sortList(id, data)); let list; + let cards; + try { - ({ item: list } = yield call(request, api.sortList, id, data)); + ({ + item: list, + included: { cards }, + } = yield call(request, api.sortList, id, data)); } catch (error) { yield put(actions.sortList.failure(id, error)); return; } - yield put(actions.sortList.success(list)); + yield put(actions.sortList.success(list, cards)); } -export function* handleListSort(list) { - yield put(actions.handleListSort(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, - }); +export function* handleListSort(list, cards) { + yield put(actions.handleListSort(list, cards)); } export function* deleteList(id) { @@ -112,9 +118,9 @@ export default { handleListCreate, updateList, handleListUpdate, + moveList, sortList, handleListSort, - moveList, deleteList, handleListDelete, }; diff --git a/client/src/sagas/core/watchers/lists.js b/client/src/sagas/core/watchers/lists.js index d4576aa..721a2d6 100644 --- a/client/src/sagas/core/watchers/lists.js +++ b/client/src/sagas/core/watchers/lists.js @@ -17,14 +17,14 @@ export default function* listsWatchers() { takeEvery(EntryActionTypes.LIST_UPDATE_HANDLE, ({ payload: { list } }) => services.handleListUpdate(list), ), + takeEvery(EntryActionTypes.LIST_MOVE, ({ payload: { id, index } }) => + services.moveList(id, index), + ), takeEvery(EntryActionTypes.LIST_SORT, ({ payload: { id, data } }) => services.sortList(id, data), ), - takeEvery(EntryActionTypes.LIST_SORT_HANDLE, ({ payload: { list } }) => - services.handleListSort(list), - ), - takeEvery(EntryActionTypes.LIST_MOVE, ({ payload: { id, index } }) => - services.moveList(id, index), + takeEvery(EntryActionTypes.LIST_SORT_HANDLE, ({ payload: { list, cards } }) => + services.handleListSort(list, cards), ), takeEvery(EntryActionTypes.LIST_DELETE, ({ payload: { id } }) => services.deleteList(id)), takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list } }) => diff --git a/client/src/sagas/core/watchers/socket.js b/client/src/sagas/core/watchers/socket.js index ddb65cf..4cb59d1 100644 --- a/client/src/sagas/core/watchers/socket.js +++ b/client/src/sagas/core/watchers/socket.js @@ -84,6 +84,10 @@ const createSocketEventsChannel = () => emit(entryActions.handleListUpdate(item)); }; + const handleListSort = api.makeHandleListSort(({ item, included: { cards } }) => { + emit(entryActions.handleListSort(item, cards)); + }); + const handleListDelete = ({ item }) => { emit(entryActions.handleListDelete(item)); }; @@ -198,6 +202,7 @@ const createSocketEventsChannel = () => socket.on('listCreate', handleListCreate); socket.on('listUpdate', handleListUpdate); + socket.on('listSort', handleListSort); socket.on('listDelete', handleListDelete); socket.on('labelCreate', handleLabelCreate); @@ -256,6 +261,7 @@ const createSocketEventsChannel = () => socket.off('listCreate', handleListCreate); socket.off('listUpdate', handleListUpdate); + socket.off('listSort', handleListSort); socket.off('listDelete', handleListDelete); socket.off('labelCreate', handleLabelCreate); diff --git a/server/api/controllers/lists/sort.js b/server/api/controllers/lists/sort.js index 10e0606..ec5863f 100644 --- a/server/api/controllers/lists/sort.js +++ b/server/api/controllers/lists/sort.js @@ -14,10 +14,9 @@ module.exports = { regex: /^[0-9]+$/, required: true, }, - sortType: { + type: { type: 'string', - isIn: ['name_asc', 'duedate_asc', 'createdat_asc', 'createdat_desc'], - defaultsTo: 'name_asc', + isIn: Object.values(List.SortTypes), }, }, @@ -33,7 +32,7 @@ module.exports = { async fn(inputs) { const { currentUser } = this.req; - let { list } = await sails.helpers.lists + const { list } = await sails.helpers.lists .getProjectPath(inputs.id) .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND); @@ -43,25 +42,24 @@ module.exports = { }); 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) { throw Errors.NOT_ENOUGH_RIGHTS; } - list = await sails.helpers.lists.sortOne.with({ + const cards = await sails.helpers.lists.sortOne.with({ record: list, + type: inputs.type, request: this.req, - sortType: inputs.sortType, }); - if (!list) { - throw Errors.LIST_NOT_FOUND; - } - return { item: list, + included: { + cards, + }, }; }, }; diff --git a/server/api/helpers/lists/sort-one.js b/server/api/helpers/lists/sort-one.js index 76e5fcc..c5e4fbd 100644 --- a/server/api/helpers/lists/sort-one.js +++ b/server/api/helpers/lists/sort-one.js @@ -1,68 +1,70 @@ +const List = require('../../models/List'); + module.exports = { inputs: { record: { type: 'ref', required: true, }, + type: { + type: 'string', + isIn: Object.values(List.SortTypes), + defaultsTo: List.SortTypes.NAME_ASC, + }, request: { type: 'ref', }, - sortType: { - type: 'string', - isIn: ['name_asc', 'duedate_asc', 'createdat_asc', 'createdat_desc'], - defaultsTo: 'name_asc', - }, }, async fn(inputs) { - const list = await List.findOne({ id: 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"); - } + let cards = await sails.helpers.lists.getCards(inputs.record.id); - const positions = cards.map((c) => c.position).sort((a, b) => a - b); - - for (let i = 0; i < cards.length; i++) { - let card = cards[i]; - let nextPosition = positions[i]; - - await Card.updateOne({ id: card.id }).set({ - position: nextPosition, + switch (inputs.type) { + case List.SortTypes.NAME_ASC: + cards.sort((a, b) => a.name.localeCompare(b.name)); + break; + case List.SortTypes.DUE_DATE_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 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', { - item: { - id: card.id, - position: nextPosition, - }, - }); - } + const positions = cards.map((c) => c.position).sort((a, b) => a - b); - sails.sockets.broadcast(`board:${list.boardId}`, 'listSorted', { - item: list, - }, inputs.request); - } + cards = await Promise.all( + cards.map(({ id }, index) => + 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; }, }; diff --git a/server/api/models/List.js b/server/api/models/List.js index ce785b0..84f02c2 100755 --- a/server/api/models/List.js +++ b/server/api/models/List.js @@ -5,7 +5,16 @@ * @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 = { + SortTypes, + attributes: { // ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗ // ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗