mirror of
https://github.com/plankanban/planka.git
synced 2025-08-10 16:05:35 +02:00
feat: client implementation of list sorting
This commit is contained in:
parent
03825c3bed
commit
1f62f20350
14 changed files with 190 additions and 12 deletions
|
@ -89,6 +89,36 @@ const handleListDelete = (list) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const sortList = (id, data) => ({
|
||||
type: ActionTypes.LIST_SORT,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
sortList.success = (list) => ({
|
||||
type: ActionTypes.LIST_SORT__SUCCESS,
|
||||
payload: {
|
||||
list,
|
||||
},
|
||||
});
|
||||
|
||||
sortList.failure = (id, error) => ({
|
||||
type: ActionTypes.LIST_SORT__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleListSort = (list) => ({
|
||||
type: ActionTypes.LIST_SORT_HANDLE,
|
||||
payload: {
|
||||
list,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createList,
|
||||
handleListCreate,
|
||||
|
@ -96,4 +126,6 @@ export default {
|
|||
handleListUpdate,
|
||||
deleteList,
|
||||
handleListDelete,
|
||||
sortList,
|
||||
handleListSort
|
||||
};
|
||||
|
|
|
@ -7,10 +7,13 @@ 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 deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createList,
|
||||
updateList,
|
||||
sortList,
|
||||
deleteList,
|
||||
};
|
||||
|
|
|
@ -8,12 +8,14 @@ import { useSteps } from '../../hooks';
|
|||
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, onClose }) => {
|
||||
const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onSort, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
const [step, openStep, handleBack] = useSteps();
|
||||
|
||||
|
@ -31,16 +33,32 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
|
|||
openStep(StepTypes.DELETE);
|
||||
}, [openStep]);
|
||||
|
||||
if (step && step.type === StepTypes.DELETE) {
|
||||
return (
|
||||
<DeleteStep
|
||||
title="common.deleteList"
|
||||
content="common.areYouSureYouWantToDeleteThisList"
|
||||
buttonContent="action.deleteList"
|
||||
onConfirm={onDelete}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
const handleSortClick = useCallback(() => {
|
||||
openStep(StepTypes.SORT);
|
||||
}, [openStep]);
|
||||
|
||||
if (step && step.type) {
|
||||
switch (step.type){
|
||||
case StepTypes.DELETE:
|
||||
return (
|
||||
<DeleteStep
|
||||
title="common.deleteList"
|
||||
content="common.areYouSureYouWantToDeleteThisList"
|
||||
buttonContent="action.deleteList"
|
||||
onConfirm={onDelete}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
case StepTypes.SORT:
|
||||
return (
|
||||
<SortStep
|
||||
title="common.sortList"
|
||||
onSort={onSort}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -52,6 +70,11 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
|
|||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<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}>
|
||||
{t('action.editTitle', {
|
||||
context: 'title',
|
||||
|
@ -77,6 +100,7 @@ ActionsStep.propTypes = {
|
|||
onNameEdit: PropTypes.func.isRequired,
|
||||
onCardAdd: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onSort: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-ic
|
|||
import styles from './List.module.scss';
|
||||
|
||||
const List = React.memo(
|
||||
({ id, index, name, isPersisted, cardIds, canEdit, onUpdate, onDelete, onCardCreate }) => {
|
||||
({ id, index, name, isPersisted, cardIds, canEdit, onUpdate, onDelete, onSort, onCardCreate }) => {
|
||||
const [t] = useTranslation();
|
||||
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
|
||||
|
||||
|
@ -114,6 +114,7 @@ const List = React.memo(
|
|||
onNameEdit={handleNameEdit}
|
||||
onCardAdd={handleCardAdd}
|
||||
onDelete={onDelete}
|
||||
onSort={onSort}
|
||||
>
|
||||
<Button className={classNames(styles.headerButton, styles.target)}>
|
||||
<Icon fitted name="pencil" size="small" />
|
||||
|
@ -160,6 +161,7 @@ List.propTypes = {
|
|||
canEdit: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onSort: PropTypes.func.isRequired,
|
||||
onCardCreate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
|
59
client/src/components/SortStep/SortStep.jsx
Normal file
59
client/src/components/SortStep/SortStep.jsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {Button, 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;
|
0
client/src/components/SortStep/SortStep.module.scss
Normal file
0
client/src/components/SortStep/SortStep.module.scss
Normal file
3
client/src/components/SortStep/index.js
Normal file
3
client/src/components/SortStep/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import SortStep from './SortStep';
|
||||
|
||||
export default SortStep;
|
|
@ -173,6 +173,10 @@ export default {
|
|||
LIST_UPDATE__SUCCESS: 'LIST_UPDATE__SUCCESS',
|
||||
LIST_UPDATE__FAILURE: 'LIST_UPDATE__FAILURE',
|
||||
LIST_UPDATE_HANDLE: 'LIST_UPDATE_HANDLE',
|
||||
LIST_SORT: 'LIST_SORT',
|
||||
LIST_SORT__SUCCESS: 'LIST_SORT__SUCCESS',
|
||||
LIST_SORT__FAILURE: 'LIST_SORT__FAILURE',
|
||||
LIST_SORT_HANDLE: 'LIST_SORT_HANDLE',
|
||||
LIST_DELETE: 'LIST_DELETE',
|
||||
LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS',
|
||||
LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE',
|
||||
|
|
|
@ -120,6 +120,8 @@ export default {
|
|||
LIST_MOVE: `${PREFIX}/LIST_MOVE`,
|
||||
LIST_DELETE: `${PREFIX}/LIST_DELETE`,
|
||||
LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`,
|
||||
LIST_SORT: `${PREFIX}/LIST_SORT`,
|
||||
LIST_SORT_HANDLE: `${PREFIX}/LIST_SORT_HANDLE`,
|
||||
|
||||
/* Cards */
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ const mapDispatchToProps = (dispatch, { id }) =>
|
|||
{
|
||||
onUpdate: (data) => entryActions.updateList(id, data),
|
||||
onDelete: () => entryActions.deleteList(id),
|
||||
onSort: (data) => entryActions.sortList(id, data),
|
||||
onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen),
|
||||
},
|
||||
dispatch,
|
||||
|
|
|
@ -51,6 +51,23 @@ const handleListDelete = (list) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const sortList = (id, data) => {
|
||||
return ({
|
||||
type: EntryActionTypes.LIST_SORT,
|
||||
payload: {
|
||||
id,
|
||||
data
|
||||
},
|
||||
})};
|
||||
|
||||
const handleListSort = (list) => ({
|
||||
type: EntryActionTypes.LIST_SORT_HANDLE,
|
||||
payload: {
|
||||
list,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default {
|
||||
createListInCurrentBoard,
|
||||
handleListCreate,
|
||||
|
@ -59,4 +76,6 @@ export default {
|
|||
moveList,
|
||||
deleteList,
|
||||
handleListDelete,
|
||||
sortList,
|
||||
handleListSort,
|
||||
};
|
||||
|
|
|
@ -66,6 +66,9 @@ 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);
|
||||
|
|
|
@ -61,6 +61,24 @@ export function* handleListUpdate(list) {
|
|||
yield put(actions.handleListUpdate(list));
|
||||
}
|
||||
|
||||
export function* sortList(id, data) {
|
||||
yield put(actions.sortList(id, data));
|
||||
|
||||
let list;
|
||||
try {
|
||||
({ item: list } = yield call(request, api.sortList, id, data));
|
||||
} catch (error) {
|
||||
yield put(actions.sortList.failure(id, error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.sortList.success(list));
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -94,6 +112,8 @@ export default {
|
|||
handleListCreate,
|
||||
updateList,
|
||||
handleListUpdate,
|
||||
sortList,
|
||||
handleListSort,
|
||||
moveList,
|
||||
deleteList,
|
||||
handleListDelete,
|
||||
|
|
|
@ -17,6 +17,12 @@ export default function* listsWatchers() {
|
|||
takeEvery(EntryActionTypes.LIST_UPDATE_HANDLE, ({ payload: { list } }) =>
|
||||
services.handleListUpdate(list),
|
||||
),
|
||||
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),
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue