From 2e0483d9e999d551ac551b18fbc74629877bb668 Mon Sep 17 00:00:00 2001 From: Mashood ur Rehman <112761667+ranamashood@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:10:58 +0500 Subject: [PATCH] feat: Add ability to create new card at top (#1261) Closes #1260 --- .../components/boards/Board/FiniteContent.jsx | 2 +- client/src/components/lists/List/List.jsx | 53 ++++++++++++------- client/src/constants/EntryActionTypes.js | 2 +- client/src/entry-actions/cards.js | 14 ++--- client/src/sagas/core/services/cards.js | 20 +++---- client/src/sagas/core/watchers/cards.js | 13 ++--- 6 files changed, 62 insertions(+), 42 deletions(-) diff --git a/client/src/components/boards/Board/FiniteContent.jsx b/client/src/components/boards/Board/FiniteContent.jsx index 5bb5c568..5a5751ec 100644 --- a/client/src/components/boards/Board/FiniteContent.jsx +++ b/client/src/components/boards/Board/FiniteContent.jsx @@ -21,7 +21,7 @@ const FiniteContent = React.memo(() => { const handleCardCreate = useCallback( (data, autoOpen) => { - dispatch(entryActions.createCardInFirstFiniteList(data, autoOpen)); + dispatch(entryActions.createCardInFirstFiniteList(data, undefined, autoOpen)); }, [dispatch], ); diff --git a/client/src/components/lists/List/List.jsx b/client/src/components/lists/List/List.jsx index 89c7adc3..ce30c2d9 100755 --- a/client/src/components/lists/List/List.jsx +++ b/client/src/components/lists/List/List.jsx @@ -30,6 +30,15 @@ import PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react'; import styles from './List.module.scss'; import globalStyles from '../../../styles.module.scss'; +const AddCardPositions = { + TOP: 'top', + BOTTOM: 'bottom', +}; + +const INDEX_BY_ADD_CARD_POSITION = { + [AddCardPositions.TOP]: 0, +}; + const List = React.memo(({ id, index }) => { const selectListById = useMemo(() => selectors.makeSelectListById(), []); @@ -59,16 +68,18 @@ const List = React.memo(({ id, index }) => { const dispatch = useDispatch(); const [t] = useTranslation(); const [isEditNameOpened, setIsEditNameOpened] = useState(false); - const [isAddCardOpened, setIsAddCardOpened] = useState(false); + const [addCardPosition, setAddCardPosition] = useState(null); const wrapperRef = useRef(null); const cardsWrapperRef = useRef(null); const handleCardCreate = useCallback( (data, autoOpen) => { - dispatch(entryActions.createCard(id, data, autoOpen)); + dispatch( + entryActions.createCard(id, data, INDEX_BY_ADD_CARD_POSITION[addCardPosition], autoOpen), + ); }, - [id, dispatch], + [id, dispatch, addCardPosition], ); const handleHeaderClick = useCallback(() => { @@ -78,15 +89,15 @@ const List = React.memo(({ id, index }) => { }, [list.isPersisted, canEdit]); const handleAddCardClick = useCallback(() => { - setIsAddCardOpened(true); + setAddCardPosition(AddCardPositions.BOTTOM); }, []); const handleAddCardClose = useCallback(() => { - setIsAddCardOpened(false); + setAddCardPosition(null); }, []); const handleCardAdd = useCallback(() => { - setIsAddCardOpened(true); + setAddCardPosition(AddCardPositions.TOP); }, []); const handleNameEdit = useCallback(() => { @@ -104,14 +115,26 @@ const List = React.memo(({ id, index }) => { ); useDidUpdate(() => { - if (isAddCardOpened) { - cardsWrapperRef.current.scrollTop = cardsWrapperRef.current.scrollHeight; + if (!addCardPosition) { + return; } - }, [cardIds, isAddCardOpened]); + + cardsWrapperRef.current.scrollTop = + addCardPosition === AddCardPositions.TOP ? 0 : cardsWrapperRef.current.scrollHeight; + }, [cardIds, addCardPosition]); const ActionsPopup = usePopup(ActionsStep); const ArchiveCardsPopup = usePopup(ArchiveCardsStep); + const addCardNode = canAddCard && ( + + ); + const cardsNode = ( { // eslint-disable-next-line react/jsx-props-no-spreading + {addCardPosition === AddCardPositions.TOP && addCardNode} {cardIds.map((cardId, cardIndex) => ( ))} {placeholder} - {canAddCard && ( - - )} + {addCardPosition === AddCardPositions.BOTTOM && addCardNode} )} @@ -213,7 +230,7 @@ const List = React.memo(({ id, index }) => { {cardsNode} - {!isAddCardOpened && canAddCard && ( + {!addCardPosition && canAddCard && ( ({ }, }); -const createCard = (listId, data, autoOpen) => ({ +const createCard = (listId, data, index, autoOpen = false) => ({ type: EntryActionTypes.CARD_CREATE, payload: { listId, data, + index, autoOpen, }, }); -const createCardInCurrentList = (data, autoOpen) => ({ - type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, +const createCardInFirstFiniteList = (data, index = 0, autoOpen = false) => ({ + type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE, payload: { data, + index, autoOpen, }, }); -const createCardInFirstFiniteList = (data, autoOpen) => ({ - type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE, +const createCardInCurrentList = (data, autoOpen = false) => ({ + type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, payload: { data, autoOpen, @@ -178,8 +180,8 @@ export default { fetchCardsInCurrentList, handleCardsUpdate, createCard, - createCardInCurrentList, createCardInFirstFiniteList, + createCardInCurrentList, handleCardCreate, updateCard, updateCurrentCard, diff --git a/client/src/sagas/core/services/cards.js b/client/src/sagas/core/services/cards.js index c668f8c9..3e4c7f36 100644 --- a/client/src/sagas/core/services/cards.js +++ b/client/src/sagas/core/services/cards.js @@ -113,7 +113,7 @@ export function* handleCardsUpdate(cards, activities) { yield put(actions.handleCardsUpdate(cards, activities)); } -export function* createCard(listId, data, autoOpen) { +export function* createCard(listId, data, index, autoOpen) { const localId = yield call(createLocalId); const list = yield select(selectors.selectListById, listId); @@ -127,7 +127,7 @@ export function* createCard(listId, data, autoOpen) { }; if (isListFinite(list)) { - nextData.position = yield select(selectors.selectNextCardPosition, listId); + nextData.position = yield select(selectors.selectNextCardPosition, listId, index); } yield put( @@ -167,16 +167,16 @@ export function* createCard(listId, data, autoOpen) { } } +export function* createCardInFirstFiniteList(data, index, autoOpen) { + const firstFiniteListId = yield select(selectors.selectFirstFiniteListId); + + yield call(createCard, firstFiniteListId, data, index, autoOpen); +} + export function* createCardInCurrentList(data, autoOpen) { const currentListId = yield select(selectors.selectCurrentListId); - yield call(createCard, currentListId, data, autoOpen); -} - -export function* createCardInFirstFiniteList(data, autoOpen) { - const firstFiniteListId = yield select(selectors.selectFirstFiniteListId); - - yield call(createCard, firstFiniteListId, data, autoOpen); + yield call(createCard, currentListId, data, undefined, autoOpen); } export function* handleCardCreate(card) { @@ -596,9 +596,9 @@ export default { fetchCardsInCurrentList, handleCardsUpdate, createCard, + createCardInFirstFiniteList, createCardInCurrentList, handleCardCreate, - createCardInFirstFiniteList, updateCard, updateCurrentCard, handleCardUpdate, diff --git a/client/src/sagas/core/watchers/cards.js b/client/src/sagas/core/watchers/cards.js index 58014879..c89dfa5b 100644 --- a/client/src/sagas/core/watchers/cards.js +++ b/client/src/sagas/core/watchers/cards.js @@ -16,15 +16,16 @@ export default function* cardsWatchers() { takeEvery(EntryActionTypes.CARDS_UPDATE_HANDLE, ({ payload: { cards, activities } }) => services.handleCardsUpdate(cards, activities), ), - takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, autoOpen } }) => - services.createCard(listId, data, autoOpen), - ), - takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) => - services.createCardInCurrentList(data, autoOpen), + takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, index, autoOpen } }) => + services.createCard(listId, data, index, autoOpen), ), takeEvery( EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE, - ({ payload: { data, autoOpen } }) => services.createCardInFirstFiniteList(data, autoOpen), + ({ payload: { data, index, autoOpen } }) => + services.createCardInFirstFiniteList(data, index, autoOpen), + ), + takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) => + services.createCardInCurrentList(data, autoOpen), ), takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) => services.handleCardCreate(card),