mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 05:09:43 +02:00
feat: add page to view all cards assigned to current user #264
This commit is contained in:
parent
e6ab4e2370
commit
8c2de439d4
15 changed files with 329 additions and 3 deletions
|
@ -78,6 +78,13 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||||
const { listName, withCreator } = useSelector((state) => {
|
const { listName, withCreator } = useSelector((state) => {
|
||||||
const board = selectors.selectCurrentBoard(state);
|
const board = selectors.selectCurrentBoard(state);
|
||||||
|
|
||||||
|
if (!board) {
|
||||||
|
return {
|
||||||
|
listName: null,
|
||||||
|
withCreator: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listName: list.name && (board.view === BoardViews.KANBAN ? null : list.name),
|
listName: list.name && (board.view === BoardViews.KANBAN ? null : list.name),
|
||||||
withCreator: board.alwaysDisplayCardCreator,
|
withCreator: board.alwaysDisplayCardCreator,
|
||||||
|
|
|
@ -45,7 +45,7 @@ const CardModal = React.memo(() => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
dispatch(push(Paths.BOARDS.replace(':id', card.boardId)));
|
window.history.back();
|
||||||
}, [card.boardId, dispatch]);
|
}, [card.boardId, dispatch]);
|
||||||
|
|
||||||
const [ClosableModal, isClosableActiveRef] = useClosableModal();
|
const [ClosableModal, isClosableActiveRef] = useClosableModal();
|
||||||
|
|
|
@ -16,6 +16,7 @@ import Paths from '../../constants/Paths';
|
||||||
import Login from './Login';
|
import Login from './Login';
|
||||||
import Core from './Core';
|
import Core from './Core';
|
||||||
import NotFound from './NotFound';
|
import NotFound from './NotFound';
|
||||||
|
import UserCardsPage from "../users/UserCardsPage";
|
||||||
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import 'photoswipe/dist/photoswipe.css';
|
import 'photoswipe/dist/photoswipe.css';
|
||||||
|
@ -37,6 +38,7 @@ function Root({ store, history }) {
|
||||||
<Route path={Paths.PROJECTS} element={<Core />} />
|
<Route path={Paths.PROJECTS} element={<Core />} />
|
||||||
<Route path={Paths.BOARDS} element={<Core />} />
|
<Route path={Paths.BOARDS} element={<Core />} />
|
||||||
<Route path={Paths.CARDS} element={<Core />} />
|
<Route path={Paths.CARDS} element={<Core />} />
|
||||||
|
<Route path={Paths.USERCARDS} element={<UserCardsPage />} />
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</ToasterProvider>
|
</ToasterProvider>
|
||||||
|
|
136
client/src/components/users/UserCardsPage/UserCardsPage.jsx
Executable file
136
client/src/components/users/UserCardsPage/UserCardsPage.jsx
Executable file
|
@ -0,0 +1,136 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Loader } from 'semantic-ui-react';
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
|
import DroppableTypes from '../../../constants/DroppableTypes';
|
||||||
|
import DraggableCard from '../../cards/DraggableCard';
|
||||||
|
import Header from '../../common/Header';
|
||||||
|
import selectors from '../../../selectors';
|
||||||
|
import entryActions from '../../../entry-actions';
|
||||||
|
|
||||||
|
import styles from './UserCardsPage.module.scss';
|
||||||
|
|
||||||
|
function UserCardsPage() {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const loading = useRef(false);
|
||||||
|
|
||||||
|
const currentUserId = useSelector(selectors.selectCurrentUserId);
|
||||||
|
const user = useSelector(selectors.selectCurrentUser);
|
||||||
|
const projectsToCard = useSelector(selectors.selectProjectsToCardsWithEditorRightsForCurrentUser);
|
||||||
|
|
||||||
|
console.log('Projects to Cards Data:', projectsToCard);
|
||||||
|
|
||||||
|
const isDataReady = currentUserId && user && projectsToCard !== null;
|
||||||
|
|
||||||
|
const handleDragEnd = (result) => {
|
||||||
|
if (!result.destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { source, destination, draggableId } = result;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentUserId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading.current || !isDataReady) return;
|
||||||
|
|
||||||
|
if (projectsToCard && projectsToCard.length > 0) {
|
||||||
|
projectsToCard.forEach((project) => {
|
||||||
|
project.boards?.forEach((board) => {
|
||||||
|
dispatch(entryActions.fetchBoard(board.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.current = true;
|
||||||
|
}, [currentUserId, projectsToCard, dispatch, isDataReady]);
|
||||||
|
|
||||||
|
if (!isDataReady) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
||||||
|
<Loader active size="massive" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<div className={styles.container}>
|
||||||
|
<h1 className={styles.title}>{t('common.myCards')}</h1>
|
||||||
|
{projectsToCard && projectsToCard.length > 0 ? (
|
||||||
|
<DragDropContext onDragEnd={handleDragEnd}>
|
||||||
|
<div className={styles.projectsContainer}>
|
||||||
|
{projectsToCard.map((project) => (
|
||||||
|
<div key={project.id} className={styles.projectSection}>
|
||||||
|
<h2 className={styles.projectTitle}>{project.name}</h2>
|
||||||
|
|
||||||
|
{project.boards && project.boards.length > 0 ? (
|
||||||
|
project.boards.map((board) => (
|
||||||
|
<div key={board.id} className={styles.boardSection}>
|
||||||
|
<h3 className={styles.boardTitle}>{board.name}</h3>
|
||||||
|
|
||||||
|
{board.cards && board.cards.length > 0 ? (
|
||||||
|
<Droppable droppableId={`board:${board.id}`} type={DroppableTypes.CARD}>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
data-rbd-droppable-id={`board:${board.id}`}
|
||||||
|
data-rbd-droppable-context-id={
|
||||||
|
provided.droppableProps['data-rbd-droppable-context-id']
|
||||||
|
}
|
||||||
|
className={styles.cardsGrid}
|
||||||
|
>
|
||||||
|
{board.cards.map((card, index) => {
|
||||||
|
const cardId = card.id || card._fields?.id;
|
||||||
|
if (!cardId) {
|
||||||
|
console.warn('Card missing ID:', card);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={cardId} className={styles.card}>
|
||||||
|
<DraggableCard id={cardId} index={index} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
) : (
|
||||||
|
<p className={styles.emptyMessage}>{t('common.noCards')}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className={styles.emptyMessage}>{t('common.noBoards')}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DragDropContext>
|
||||||
|
) : (
|
||||||
|
<div className={styles.emptyState}>
|
||||||
|
<p>{t('common.noProjects')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserCardsPage;
|
96
client/src/components/users/UserCardsPage/UserCardsPage.module.scss
Executable file
96
client/src/components/users/UserCardsPage/UserCardsPage.module.scss
Executable file
|
@ -0,0 +1,96 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectsContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectSection {
|
||||||
|
background-color: #333333;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin: 1rem;
|
||||||
|
font-family: "Nunitoga", "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectTitle {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boardSection {
|
||||||
|
width: 300px;
|
||||||
|
color: #000;
|
||||||
|
background-color: #dfe3e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boardTitle {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardsGrid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-top: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: 0 8px 16px rgba(255, 255, 255, 0.562);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardName {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyMessage {
|
||||||
|
width: 300px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #9e0000;
|
||||||
|
font-weight:bold;
|
||||||
|
font-size:large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyState {
|
||||||
|
width: 300px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #9e0000;
|
||||||
|
font-weight:bold;
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media Queries for Responsiveness */
|
||||||
|
/* tablet */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
|
.projectsContainer{
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* phone */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
|
||||||
|
}
|
8
client/src/components/users/UserCardsPage/index.js
Executable file
8
client/src/components/users/UserCardsPage/index.js
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import UserCards from './UserCardsPage';
|
||||||
|
|
||||||
|
export default UserCards;
|
|
@ -7,6 +7,7 @@ import React, { useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { Button, Menu } from 'semantic-ui-react';
|
import { Button, Menu } from 'semantic-ui-react';
|
||||||
import { Popup } from '../../../lib/custom-ui';
|
import { Popup } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
|
@ -40,6 +41,10 @@ const UserStep = React.memo(({ onClose }) => {
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose, dispatch]);
|
}, [onClose, dispatch]);
|
||||||
|
|
||||||
|
const handleCardsClick = useCallback(() => {
|
||||||
|
onClose();
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
let logoutMenuItemProps;
|
let logoutMenuItemProps;
|
||||||
if (isLogouting) {
|
if (isLogouting) {
|
||||||
logoutMenuItemProps = {
|
logoutMenuItemProps = {
|
||||||
|
@ -60,6 +65,16 @@ const UserStep = React.memo(({ onClose }) => {
|
||||||
</Popup.Header>
|
</Popup.Header>
|
||||||
<Popup.Content>
|
<Popup.Content>
|
||||||
<Menu secondary vertical className={styles.menu}>
|
<Menu secondary vertical className={styles.menu}>
|
||||||
|
<Menu.Item
|
||||||
|
as={Link}
|
||||||
|
to="/user-cards"
|
||||||
|
className={styles.menuItem}
|
||||||
|
onClick={handleCardsClick}
|
||||||
|
>
|
||||||
|
{t('common.myCards', {
|
||||||
|
context: 'title',
|
||||||
|
})}
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleSettingsClick}>
|
<Menu.Item className={styles.menuItem} onClick={handleSettingsClick}>
|
||||||
{t('common.settings', {
|
{t('common.settings', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
|
|
|
@ -9,6 +9,7 @@ const OIDC_CALLBACK = '/oidc-callback';
|
||||||
const PROJECTS = '/projects/:id';
|
const PROJECTS = '/projects/:id';
|
||||||
const BOARDS = '/boards/:id';
|
const BOARDS = '/boards/:id';
|
||||||
const CARDS = '/cards/:id';
|
const CARDS = '/cards/:id';
|
||||||
|
const USERCARDS = '/user-cards';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ROOT,
|
ROOT,
|
||||||
|
@ -17,4 +18,5 @@ export default {
|
||||||
PROJECTS,
|
PROJECTS,
|
||||||
BOARDS,
|
BOARDS,
|
||||||
CARDS,
|
CARDS,
|
||||||
|
USERCARDS,
|
||||||
};
|
};
|
||||||
|
|
|
@ -209,6 +209,7 @@ export default {
|
||||||
memberActions_title: 'Member Actions',
|
memberActions_title: 'Member Actions',
|
||||||
minutes: 'Minutes',
|
minutes: 'Minutes',
|
||||||
moveCard_title: 'Move Card',
|
moveCard_title: 'Move Card',
|
||||||
|
myCards: 'My cards',
|
||||||
myOwn_title: 'My Own',
|
myOwn_title: 'My Own',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
newestFirst: 'Newest first',
|
newestFirst: 'Newest first',
|
||||||
|
@ -218,6 +219,7 @@ export default {
|
||||||
newVersionAvailable: 'New version available',
|
newVersionAvailable: 'New version available',
|
||||||
noConnectionToServer: 'No connection to server',
|
noConnectionToServer: 'No connection to server',
|
||||||
noBoards: 'No boards',
|
noBoards: 'No boards',
|
||||||
|
noCards: 'No cards',
|
||||||
noLists: 'No lists',
|
noLists: 'No lists',
|
||||||
noProjects: 'No projects',
|
noProjects: 'No projects',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
|
|
|
@ -204,6 +204,7 @@ export default {
|
||||||
memberActions_title: 'Member Actions',
|
memberActions_title: 'Member Actions',
|
||||||
minutes: 'Minutes',
|
minutes: 'Minutes',
|
||||||
moveCard_title: 'Move Card',
|
moveCard_title: 'Move Card',
|
||||||
|
myCards: 'My cards',
|
||||||
myOwn_title: 'My Own',
|
myOwn_title: 'My Own',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
newestFirst: 'Newest first',
|
newestFirst: 'Newest first',
|
||||||
|
@ -213,6 +214,7 @@ export default {
|
||||||
newVersionAvailable: 'New version available',
|
newVersionAvailable: 'New version available',
|
||||||
noConnectionToServer: 'No connection to server',
|
noConnectionToServer: 'No connection to server',
|
||||||
noBoards: 'No boards',
|
noBoards: 'No boards',
|
||||||
|
noCards: 'No cards',
|
||||||
noLists: 'No lists',
|
noLists: 'No lists',
|
||||||
noProjects: 'No projects',
|
noProjects: 'No projects',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
|
|
|
@ -111,6 +111,7 @@ export default {
|
||||||
memberActions_title: 'Actions des membres',
|
memberActions_title: 'Actions des membres',
|
||||||
minutes: 'Minutes',
|
minutes: 'Minutes',
|
||||||
moveCard_title: 'Déplacer la carte',
|
moveCard_title: 'Déplacer la carte',
|
||||||
|
myCards: 'Mes cartes',
|
||||||
name: 'Nom',
|
name: 'Nom',
|
||||||
newestFirst: 'Le plus récent en premier',
|
newestFirst: 'Le plus récent en premier',
|
||||||
newEmail: 'Nouvel e-mail',
|
newEmail: 'Nouvel e-mail',
|
||||||
|
@ -118,6 +119,7 @@ export default {
|
||||||
newUsername: "Nouveau nom d'utilisateur",
|
newUsername: "Nouveau nom d'utilisateur",
|
||||||
noConnectionToServer: 'Pas de connexion au serveur',
|
noConnectionToServer: 'Pas de connexion au serveur',
|
||||||
noBoards: 'Pas de tableau',
|
noBoards: 'Pas de tableau',
|
||||||
|
noCards: 'Pas de cartes',
|
||||||
noLists: 'Pas de liste',
|
noLists: 'Pas de liste',
|
||||||
noProjects: 'Pas de projet',
|
noProjects: 'Pas de projet',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
|
|
|
@ -296,6 +296,10 @@ export default class extends BaseModel {
|
||||||
return this.activities.orderBy(['id.length', 'id'], ['desc', 'desc']);
|
return this.activities.orderBy(['id.length', 'id'], ['desc', 'desc']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrderedCardsQuerySet() {
|
||||||
|
return this.cards.orderBy(['position', 'id.length', 'id']);
|
||||||
|
}
|
||||||
|
|
||||||
getUnreadNotificationsQuerySet() {
|
getUnreadNotificationsQuerySet() {
|
||||||
return this.notifications.filter({
|
return this.notifications.filter({
|
||||||
isRead: false,
|
isRead: false,
|
||||||
|
@ -313,6 +317,11 @@ export default class extends BaseModel {
|
||||||
})
|
})
|
||||||
.first();
|
.first();
|
||||||
}
|
}
|
||||||
|
getOrderedCardsModelArrayForUser(userId) {
|
||||||
|
return this.getOrderedCardsQuerySet()
|
||||||
|
.toModelArray()
|
||||||
|
.filter((cardModel) => cardModel.hasMembershipForUser(userId));
|
||||||
|
}
|
||||||
|
|
||||||
getCardsModelArray() {
|
getCardsModelArray() {
|
||||||
return this.getFiniteListsQuerySet()
|
return this.getFiniteListsQuerySet()
|
||||||
|
|
|
@ -167,7 +167,9 @@ export default class extends BaseModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
payload.cardMemberships.forEach(({ cardId, userId }) => {
|
payload.cardMemberships.forEach(({ cardId, userId }) => {
|
||||||
|
try {
|
||||||
Card.withId(cardId).users.add(userId);
|
Card.withId(cardId).users.add(userId);
|
||||||
|
} catch {} // eslint-disable-line no-empty
|
||||||
});
|
});
|
||||||
|
|
||||||
payload.cardLabels.forEach(({ cardId, labelId }) => {
|
payload.cardLabels.forEach(({ cardId, labelId }) => {
|
||||||
|
@ -510,6 +512,14 @@ export default class extends BaseModel {
|
||||||
.exists();
|
.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasMembershipForUser(userId) {
|
||||||
|
return this.users
|
||||||
|
.filter({
|
||||||
|
id: userId,
|
||||||
|
})
|
||||||
|
.exists();
|
||||||
|
}
|
||||||
|
|
||||||
isAvailableForUser(userModel) {
|
isAvailableForUser(userModel) {
|
||||||
return !!this.list && this.list.isAvailableForUser(userModel);
|
return !!this.list && this.list.isAvailableForUser(userModel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,41 @@ export const selectProjectsToListsWithEditorRightsForCurrentUser = createSelecto
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectProjectsToCardsWithEditorRightsForCurrentUser = createSelector(
|
||||||
|
orm,
|
||||||
|
(state) => selectCurrentUserId(state),
|
||||||
|
({ User }, id) => {
|
||||||
|
if (!id) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userModel = User.withId(id);
|
||||||
|
|
||||||
|
if (!userModel) {
|
||||||
|
return userModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userModel.getMembershipProjectsModelArray().map((projectModel) => ({
|
||||||
|
...projectModel.ref,
|
||||||
|
boards: projectModel.getBoardsModelArrayForUserWithId(id).flatMap((boardModel) => {
|
||||||
|
const boardMembersipModel = boardModel.getMembershipModelByUserId(id);
|
||||||
|
|
||||||
|
if (boardMembersipModel.role !== BoardMembershipRoles.EDITOR) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...boardModel.ref,
|
||||||
|
cards: boardModel.getOrderedCardsModelArrayForUser(id).map((card) => ({
|
||||||
|
...card,
|
||||||
|
isPersisted: !isLocalId(card.id),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const selectBoardIdsForCurrentUser = createSelector(
|
export const selectBoardIdsForCurrentUser = createSelector(
|
||||||
orm,
|
orm,
|
||||||
(state) => selectCurrentUserId(state),
|
(state) => selectCurrentUserId(state),
|
||||||
|
@ -335,6 +370,7 @@ export default {
|
||||||
selectFilteredProjctIdsByGroupForCurrentUser,
|
selectFilteredProjctIdsByGroupForCurrentUser,
|
||||||
selectFavoriteProjectIdsForCurrentUser,
|
selectFavoriteProjectIdsForCurrentUser,
|
||||||
selectProjectsToListsWithEditorRightsForCurrentUser,
|
selectProjectsToListsWithEditorRightsForCurrentUser,
|
||||||
|
selectProjectsToCardsWithEditorRightsForCurrentUser,
|
||||||
selectBoardIdsForCurrentUser,
|
selectBoardIdsForCurrentUser,
|
||||||
selectNotificationIdsForCurrentUser,
|
selectNotificationIdsForCurrentUser,
|
||||||
selectNotificationServiceIdsForCurrentUser,
|
selectNotificationServiceIdsForCurrentUser,
|
||||||
|
|
1
server/package-lock.json
generated
1
server/package-lock.json
generated
|
@ -9455,7 +9455,6 @@
|
||||||
},
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue