diff --git a/src/App.js b/src/App.js index 032c4e0..f2f1f1c 100644 --- a/src/App.js +++ b/src/App.js @@ -2,12 +2,12 @@ import './App.css' import { Route, Routes } from 'react-router-dom' import ErrorPage from './utils/ErrorPage' import PrivateRouter from './PrivateRouter' -import SharedBoard from './pages/SharedBoard' +import SharedView from './pages/SharedView' function App() { return ( - } /> + } /> } /> } /> diff --git a/src/modules/services/boardSlice.ts b/src/modules/services/boardSlice.ts index 781f183..d4fcfee 100644 --- a/src/modules/services/boardSlice.ts +++ b/src/modules/services/boardSlice.ts @@ -4,9 +4,10 @@ import { toast } from 'react-hot-toast' import pusher from '../../pusher/pusher.ts' import handleQueryError from '../../utils/handleQueryError' import { flatten, removeChildrenOf } from '../../utils/taskUtils.ts' +import { current } from 'immer' -const setGroupBy = ({ board, groupBy }) => { - board.groupBy = groupBy +const setGroupBy = ({ board, groupBy, viewId }) => { + board.views.find(x => x._id === viewId).groupBy = groupBy } const addTask = ({ board, newTaskName, taskGroupId, _id, author, level }) => { const task = { @@ -203,14 +204,17 @@ const removeWatcher = ({ board, task, user }) => { const localTask = board.tasks.find(x => x._id === task._id) localTask.watcher = localTask.watcher.filter(x => x._id !== user._id) } -const setSharing = ({ board, share }) => { - board.sharing = share +const setSharing = ({ board, share, viewShortId }) => { + const view = board.views.find(x => x.id === viewShortId) + view.sharing = share } export const boardApi = api.injectEndpoints({ endpoints: builder => ({ fetchBoard: builder.query({ - query: boardId => `board/${boardId}`, + query: ({ boardId, viewShortId = '' }) => + `board/${boardId}/${viewShortId}`, + keepUnusedDataFor: 0, transformResponse: (response: any, meta, arg) => { return { ...response, @@ -218,7 +222,7 @@ export const boardApi = api.injectEndpoints({ } }, async onCacheEntryAdded( - boardId, + { boardId }, { updateCachedData, cacheDataLoaded, cacheEntryRemoved } ) { let channel @@ -341,7 +345,7 @@ export const boardApi = api.injectEndpoints({ channel.unbind() }, providesTags: board => (board ? [board._id, 'Board'] : []), - async onQueryStarted(args, { dispatch, queryFulfilled }) { + async onQueryStarted({ boardId }, { queryFulfilled }) { try { await queryFulfilled localStorage.setItem( @@ -349,25 +353,28 @@ export const boardApi = api.injectEndpoints({ window.location.pathname ) } catch { - toast('Could not find that Board') + if (boardId === undefined) + toast('Could not find that Board') } } }), setGroupBy: builder.mutation({ - query: ({ boardId, groupBy }) => ({ - url: `board/${boardId}`, - method: 'PATCH', + query: ({ viewId, groupBy }) => ({ + url: `view/${viewId}/groupBy`, + method: 'PUT', body: { groupBy } }), async onQueryStarted( - { boardId, groupBy }, + { boardId, groupBy, viewId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, - board => setGroupBy({ board, groupBy }) + { boardId, viewShortId }, + board => { + setGroupBy({ board, groupBy, viewId }) + } ) ) try { @@ -388,13 +395,22 @@ export const boardApi = api.injectEndpoints({ } }), async onQueryStarted( - { boardId, newTaskName, taskGroupId, _id, author, level }, + { + boardId, + newTaskName, + taskGroupId, + _id, + author, + level, + viewId, + viewShortId + }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => addTask({ board, @@ -413,46 +429,19 @@ export const boardApi = api.injectEndpoints({ } } }), - editTaskField: builder.mutation({ - query: ({ boardId, taskId, type, value }) => ({ - url: `task/${boardId}/${taskId}`, - method: 'PATCH', - body: { - type, - value - } - }), - async onQueryStarted( - { taskId, type, value, boardId }, - { dispatch, queryFulfilled } - ) { - const patchResult = dispatch( - boardApi.util.updateQueryData( - 'fetchBoard', - boardId, - board => editTaskField({ board, taskId, type, value }) - ) - ) - try { - await queryFulfilled - } catch (err) { - if (handleQueryError({ err })) patchResult.undo() - } - } - }), deleteTask: builder.mutation({ query: ({ boardId, taskId }) => ({ url: `task/${boardId}/${taskId}`, method: 'DELETE' }), async onQueryStarted( - { taskId, boardId }, + { taskId, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => { deleteTask({ board, taskId }) } @@ -477,13 +466,20 @@ export const boardApi = api.injectEndpoints({ } }), async onQueryStarted( - { result, destinationIndex, newItems, sourceIndex, boardId }, + { + result, + destinationIndex, + newItems, + sourceIndex, + boardId, + viewShortId + }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => { // sortTask({ // board, @@ -516,13 +512,13 @@ export const boardApi = api.injectEndpoints({ } }), async onQueryStarted( - { column, value, type, taskId, boardId }, + { column, value, type, taskId, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => { editTaskField({ board, @@ -548,13 +544,13 @@ export const boardApi = api.injectEndpoints({ body: { content, _id } }), async onQueryStarted( - { content, author, taskId, boardId, _id }, + { content, author, taskId, boardId, _id, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => addTaskComment({ board, @@ -579,13 +575,13 @@ export const boardApi = api.injectEndpoints({ body: { content } }), async onQueryStarted( - { content, taskId, boardId, commentId }, + { content, taskId, boardId, commentId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => editTaskComment({ board, @@ -608,13 +604,13 @@ export const boardApi = api.injectEndpoints({ method: 'DELETE' }), async onQueryStarted( - { taskId, boardId, commentId }, + { taskId, boardId, commentId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => deleteTaskComment({ board, @@ -637,13 +633,21 @@ export const boardApi = api.injectEndpoints({ body: { content, _id } }), async onQueryStarted( - { content, author, taskId, boardId, commentId, _id }, + { + content, + author, + taskId, + boardId, + commentId, + _id, + viewShortId + }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => addReply({ board, @@ -699,13 +703,13 @@ export const boardApi = api.injectEndpoints({ method: 'DELETE' }), async onQueryStarted( - { taskId, boardId, replyId, commentId }, + { taskId, boardId, replyId, commentId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => deleteReply({ board, @@ -728,13 +732,13 @@ export const boardApi = api.injectEndpoints({ method: 'DELETE' }), async onQueryStarted( - { taskId, optionId, boardId }, + { taskId, optionId, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => clearStatusTask({ board, taskId, optionId }) ) ) @@ -752,13 +756,13 @@ export const boardApi = api.injectEndpoints({ body: { type, _id } }), async onQueryStarted( - { type, _id, boardId }, + { type, _id, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => addAttribute({ board, type, _id }) ) ) @@ -776,13 +780,13 @@ export const boardApi = api.injectEndpoints({ body: { name } }), async onQueryStarted( - { name, attributeId, boardId }, + { name, attributeId, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => editAttributeName({ board, name, attributeId }) ) ) @@ -799,13 +803,13 @@ export const boardApi = api.injectEndpoints({ method: 'DELETE' }), async onQueryStarted( - { attributeId, boardId }, + { attributeId, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => deleteAttribute({ board, attributeId }) ) ) @@ -823,13 +827,13 @@ export const boardApi = api.injectEndpoints({ body: { result } }), async onQueryStarted( - { result, boardId }, + { result, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => sortAttribute({ board, result }) ) ) @@ -847,13 +851,13 @@ export const boardApi = api.injectEndpoints({ body: { name, labels } }), async onQueryStarted( - { name, labels, boardId }, + { name, labels, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => editAttributeLabels({ board, name, labels }) ) ) @@ -871,13 +875,13 @@ export const boardApi = api.injectEndpoints({ body: { userId: user._id } }), async onQueryStarted( - { task, user, boardId }, + { task, user, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => addWatcher({ board, task, user }) ) ) @@ -895,13 +899,13 @@ export const boardApi = api.injectEndpoints({ body: { userId: user._id } }), async onQueryStarted( - { task, user, boardId }, + { task, user, boardId, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, board => removeWatcher({ board, task, user }) ) ) @@ -913,20 +917,20 @@ export const boardApi = api.injectEndpoints({ } }), setSharing: builder.mutation({ - query: ({ boardId, share }) => ({ - url: `board/share/${boardId}`, - method: 'PATCH', + query: ({ viewId, share }) => ({ + url: `view/${viewId}/setSharing`, + method: 'PUT', body: { share } }), async onQueryStarted( - { boardId, share }, + { boardId, share, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, - board => setSharing({ board, share }) + { boardId, viewShortId }, + board => setSharing({ board, share, viewShortId }) ) ) try { @@ -937,20 +941,95 @@ export const boardApi = api.injectEndpoints({ } }), addSubtask: builder.mutation({ - query: ({ boardId, newTaskName, taskId }) => ({ + query: ({ boardId, newTaskName, taskId, viewShortId }) => ({ url: `task/${boardId}/${taskId}`, method: 'POST', body: { name: newTaskName } }), invalidatesTags: (result, error, arg) => [arg.boardId], async onQueryStarted( - { boardId, newTaskName, taskGroupId, _id, author }, + { boardId, newTaskName, taskGroupId, _id, author, viewShortId }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( boardApi.util.updateQueryData( 'fetchBoard', - boardId, + { boardId, viewShortId }, + board => {} + ) + ) + try { + await queryFulfilled + } catch (err) { + if (handleQueryError({ err })) patchResult.undo() + } + } + }), + addView: builder.mutation({ + query: ({ boardId, type }) => ({ + url: `view/${boardId}/addView`, + method: 'POST', + body: { type } + }), + invalidatesTags: (result, error, arg) => [arg.boardId], + async onQueryStarted( + { boardId, viewShortId }, + { dispatch, queryFulfilled } + ) { + const patchResult = dispatch( + boardApi.util.updateQueryData( + 'fetchBoard', + { boardId, viewShortId }, + board => {} + ) + ) + try { + await queryFulfilled + } catch (err) { + if (handleQueryError({ err })) patchResult.undo() + } + } + }), + deleteView: builder.mutation({ + query: ({ boardId, viewId }) => ({ + url: `view/${boardId}/deleteView`, + method: 'DELETE', + body: { viewId } + }), + invalidatesTags: (result, error, arg) => [arg.boardId], + async onQueryStarted( + { boardId, viewShortId }, + { dispatch, queryFulfilled } + ) { + const patchResult = dispatch( + boardApi.util.updateQueryData( + 'fetchBoard', + { boardId, viewShortId }, + board => {} + ) + ) + try { + await queryFulfilled + } catch (err) { + if (handleQueryError({ err })) patchResult.undo() + } + } + }), + renameView: builder.mutation({ + query: ({ viewId, name }) => ({ + url: `view/${viewId}/renameView`, + method: 'PUT', + body: { name } + }), + invalidatesTags: (result, error, arg) => ['Board'], + async onQueryStarted( + { boardId, viewShortId }, + { dispatch, queryFulfilled } + ) { + const patchResult = dispatch( + boardApi.util.updateQueryData( + 'fetchBoard', + { boardId, viewShortId }, board => {} ) ) @@ -962,12 +1041,12 @@ export const boardApi = api.injectEndpoints({ } }), setFilters: builder.mutation({ - query: ({ boardId, filters }) => ({ - url: `board/${boardId}`, + query: ({ viewId, filters }) => ({ + url: `view/${viewId}/setFilters`, method: 'PUT', body: { filters } }), - invalidatesTags: (result, error, arg) => [arg.boardId] + invalidatesTags: (result, error, arg) => ['Board'] }) }) }) @@ -995,5 +1074,8 @@ export const { useRemoveWatcherMutation, useSetSharingMutation, useAddSubtaskMutation, - useSetFiltersMutation + useSetFiltersMutation, + useAddViewMutation, + useDeleteViewMutation, + useRenameViewMutation } = boardApi diff --git a/src/modules/services/sharedContentSlice.js b/src/modules/services/sharedContentSlice.js index 2d67d00..d7b2048 100644 --- a/src/modules/services/sharedContentSlice.js +++ b/src/modules/services/sharedContentSlice.js @@ -4,8 +4,8 @@ import { flatten } from '../../utils/taskUtils.ts' export const boardApi = api.injectEndpoints({ endpoints: builder => ({ - fetchSharedBoard: builder.query({ - query: boardId => `board/share/${boardId}`, + fetchSharedView: builder.query({ + query: viewId => `view/share/${viewId}`, transformResponse: (response, meta, arg) => { return { ...response, @@ -23,4 +23,4 @@ export const boardApi = api.injectEndpoints({ }) }) -export const { useFetchSharedBoardQuery } = boardApi +export const { useFetchSharedViewQuery } = boardApi diff --git a/src/modules/services/workspaceSlice.js b/src/modules/services/workspaceSlice.js index d0d585f..a432fef 100644 --- a/src/modules/services/workspaceSlice.js +++ b/src/modules/services/workspaceSlice.js @@ -4,14 +4,17 @@ import handleQueryError from '../../utils/handleQueryError' import pusher from '../../pusher/pusher.ts' const addBoard = data => { - const { workspace, workspaceId, spaceId, name, _id } = data + const { workspace, viewShortId, viewId, spaceId, name, _id } = data const board = { _id, name, - workspace: workspaceId, - attributes: [], - taskGroups: [], - sharing: false + views: [ + { + name: 'List', + _id: viewId, + id: viewShortId + } + ] } workspace.spaces.find(space => space._id === spaceId).boards.push(board) return workspace @@ -147,6 +150,7 @@ export const workspaceApi = api.injectEndpoints({ { workspaceId, workspaceIdFriendly, spaceId, name, _id }, { dispatch, queryFulfilled } ) { + const { data } = await queryFulfilled const patchResult = dispatch( workspaceApi.util.updateQueryData( 'fetchWorkspace', @@ -157,7 +161,9 @@ export const workspaceApi = api.injectEndpoints({ workspaceId, spaceId, name, - _id + _id, + viewId: data.message.viewId, + viewShortId: data.message.viewShortId }) ) ) diff --git a/src/pages/SharedBoard.js b/src/pages/SharedView.js similarity index 85% rename from src/pages/SharedBoard.js rename to src/pages/SharedView.js index 3b32aac..8a48fd3 100644 --- a/src/pages/SharedBoard.js +++ b/src/pages/SharedView.js @@ -2,16 +2,12 @@ import { Link, useParams } from 'react-router-dom' import styles from '../styles/SharedBoard.module.scss' import { CircularProgress } from '@mui/material' import BoardView from './workspace/view/board/BoardView' -import { useFetchSharedBoardQuery } from '../modules/services/sharedContentSlice' +import { useFetchSharedViewQuery } from '../modules/services/sharedContentSlice' import { useCallback, useRef, useState } from 'react' -function SharedBoard() { - const { boardId } = useParams() - const { - data: board, - isError, - isLoading - } = useFetchSharedBoardQuery(boardId) +function SharedView() { + const { viewId } = useParams() + const { data: board, isError, isLoading } = useFetchSharedViewQuery(viewId) const viewContainerRef = useRef(null) const [bla, setBla] = useState(false) const setViewContainerRef = useCallback(node => { @@ -50,6 +46,7 @@ function SharedBoard() { ) } + const view = board?.views.find(x => x._id === viewId) return (
{}} /> @@ -69,4 +67,4 @@ function SharedBoard() { ) } -export default SharedBoard +export default SharedView diff --git a/src/pages/Workspace.js b/src/pages/Workspace.js index 0605c7a..0a0f401 100644 --- a/src/pages/Workspace.js +++ b/src/pages/Workspace.js @@ -54,7 +54,18 @@ function Workspace(props) { } /> + } + /> + {provided => ( { + if ( + window.innerWidth <= + 576 + ) + props.toggleSideBar() + }} {...provided.draggableProps} ref={ provided.innerRef @@ -347,7 +354,10 @@ function SideBar(props) { )}/${board.name.replaceAll( ' ', '-' - )}`} + )}/${ + board.views[0] + .id + }`} >
{board._id === diff --git a/src/pages/workspace/View.js b/src/pages/workspace/View.js index 8f12c2e..90f2f6b 100644 --- a/src/pages/workspace/View.js +++ b/src/pages/workspace/View.js @@ -1,7 +1,7 @@ import Board from './view/Board' import ViewTopMenu from './view/ViewTopMenu' import { memo, useCallback, useRef, useState } from 'react' -import { useParams } from 'react-router-dom' +import { Navigate, useParams } from 'react-router-dom' import { useFetchWorkspaceQuery } from '../../modules/services/workspaceSlice' import { LinearProgress } from '@mui/material' import { useFetchBoardQuery } from '../../modules/services/boardSlice.ts' @@ -17,7 +17,10 @@ function View({ level, view, sidebarOpen, toggleSideBar }) { ?.boards.find(x => x.name === board)?._id }, [params.space, params.board, workspace?.spaces, workspace]) const boardId = findBoardId() - const { isFetching } = useFetchBoardQuery(boardId) + const { isFetching, error } = useFetchBoardQuery({ + boardId, + viewShortId: params.view + }) const viewContainerRef = useRef(null) const [bla, setBla] = useState(false) const setViewContainerRef = useCallback(node => { @@ -27,6 +30,10 @@ function View({ level, view, sidebarOpen, toggleSideBar }) { } }, []) + if (error && !isFetching && boardId === undefined) { + return + } + if (level === 'list') { if (view === 'list') return ( @@ -37,20 +44,25 @@ function View({ level, view, sidebarOpen, toggleSideBar }) { sideBarClosed={!sidebarOpen} />
- {isFetching && } + {(!params.view || isFetching || error) && ( + + )}
- {viewContainerRef?.current && ( - - )} + {params.view && + !isFetching && + !error && + viewContainerRef?.current && ( + + )}
) diff --git a/src/pages/workspace/topMenu/GroupByPopover.js b/src/pages/workspace/topMenu/GroupByPopover.js index 2ac3ee6..1421daf 100644 --- a/src/pages/workspace/topMenu/GroupByPopover.js +++ b/src/pages/workspace/topMenu/GroupByPopover.js @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { solid } from '@fortawesome/fontawesome-svg-core/import.macro' import React from 'react' -function GroupByPopover({ board, groupByOpen, setGroupByOpen }) { +function GroupByPopover({ view, board, groupByOpen, setGroupByOpen }) { const [setGroupBy] = useSetGroupByMutation() const handleClose = () => { @@ -13,7 +13,12 @@ function GroupByPopover({ board, groupByOpen, setGroupByOpen }) { } const handleGroupByChange = _id => { - setGroupBy({ boardId: board._id, groupBy: _id }) + setGroupBy({ + boardId: board._id, + viewId: view._id, + viewShortId: view.id, + groupBy: _id + }) handleClose() } @@ -48,7 +53,7 @@ function GroupByPopover({ board, groupByOpen, setGroupByOpen }) { } > {attribute.name} - {board?.groupBy === attribute._id && ( + {view?.groupBy === attribute._id && ( )}
@@ -59,7 +64,7 @@ function GroupByPopover({ board, groupByOpen, setGroupByOpen }) { onClick={() => handleGroupByChange('none')} > None - {(!board?.groupBy || board?.groupBy === 'none') && ( + {(!view?.groupBy || view?.groupBy === 'none') && ( )} diff --git a/src/pages/workspace/topMenu/ShareDialog.js b/src/pages/workspace/topMenu/ShareDialog.js index 29b01b1..dac4821 100644 --- a/src/pages/workspace/topMenu/ShareDialog.js +++ b/src/pages/workspace/topMenu/ShareDialog.js @@ -5,13 +5,17 @@ import styles from '../../../styles/ShareDialog.module.scss' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { solid } from '@fortawesome/fontawesome-svg-core/import.macro' import { useSetSharingMutation } from '../../../modules/services/boardSlice.ts' +import { useParams } from 'react-router-dom' -function ShareDialog({ handleClose, open, board }) { +function ShareDialog({ handleClose, open, boardId, view }) { + const params = useParams() const [setSharing] = useSetSharingMutation() const toggleShare = (e, share) => { setSharing({ - boardId: board._id, + viewShortId: params.view, + viewId: view._id, + boardId, share }) } @@ -30,14 +34,14 @@ function ShareDialog({ handleClose, open, board }) {

Link sharing

- {board.sharing && ( + {view.sharing && (
@@ -46,7 +50,7 @@ function ShareDialog({ handleClose, open, board }) { diff --git a/src/pages/workspace/view/Board.js b/src/pages/workspace/view/Board.js index e905d66..c2bc378 100644 --- a/src/pages/workspace/view/Board.js +++ b/src/pages/workspace/view/Board.js @@ -1,28 +1,29 @@ import '../../../styles/Board.css' -import { Navigate, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useFetchWorkspaceQuery } from '../../../modules/services/workspaceSlice' import { useFetchBoardQuery } from '../../../modules/services/boardSlice.ts' import BoardView from './board/BoardView' import { forwardRef, useCallback } from 'react' -function Board(props, viewContainerRef) { +function Board({ boardId }, viewContainerRef) { const params = useParams() const { data: workspace } = useFetchWorkspaceQuery(params.workspace) - const { data: board, error, isFetching } = useFetchBoardQuery(props.boardId) + const { data: board } = useFetchBoardQuery({ + boardId, + viewShortId: params.view + }) + const view = board?.views.find(x => x.id === params.view) const hasPerms = useCallback(permission => { return true }, []) - if (error && !isFetching) { - return - } - return ( ) diff --git a/src/pages/workspace/view/ViewTopMenu.js b/src/pages/workspace/view/ViewTopMenu.js index 8288c04..4bd38af 100644 --- a/src/pages/workspace/view/ViewTopMenu.js +++ b/src/pages/workspace/view/ViewTopMenu.js @@ -8,29 +8,48 @@ import { solid } from '@fortawesome/fontawesome-svg-core/import.macro' import { useFetchBoardQuery } from '../../../modules/services/boardSlice.ts' import { useAtom } from 'jotai' import { searchAtom } from '../../../utils/atoms.ts' -import { useParams } from 'react-router-dom' +import { useNavigate, useParams } from 'react-router-dom' import FilterMenu from './viewTopMenu/FilterMenu.tsx' -import ListIcon from '../../../styles/assets/ListIcon.tsx' +import NewViewMenu from './viewTopMenu/NewViewMenu.tsx' +import ViewNavItem from './viewTopMenu/ViewNavItem.tsx' function ViewTopMenu({ boardId, sideBarClosed, toggleSideBar }) { - const { data: board } = useFetchBoardQuery(boardId) + const params = useParams() + const { + data: board, + isFetching, + error + } = useFetchBoardQuery({ boardId, viewShortId: params.view }) const [shareDialogOpen, toggleShareDialogOpen] = useToggleState(false) const [groupByOpen, setGroupByOpen] = useState(null) + const navigate = useNavigate() const [search, setSearch] = useAtom(searchAtom) - const params = useParams() + const view = board?.views.find(x => x.id === params?.view) useEffect(() => { setSearch('') }, [params.board]) + useEffect(() => { + if (!params.view && board) { + const { workspace, space, board: boardId } = params + navigate(`/${workspace}/${space}/${boardId}/${board.views[0].id}`) + } + }, [params.board, board]) + const handleChange = e => { setSearch(e.target.value) } + const handleViewClick = id => { + const { workspace, space, board } = params + navigate(`/${workspace}/${space}/${board}/${id}`) + } + const groupBy = () => { - if (!board?.groupBy || board.groupBy === 'none') return false + if (!view?.groupBy || view.groupBy === 'none') return false return board.attributes.find( - attribute => attribute._id === board.groupBy + attribute => attribute._id === view.groupBy ).name } @@ -41,63 +60,46 @@ function ViewTopMenu({ boardId, sideBarClosed, toggleSideBar }) { return (
-
+
{sideBarClosed && (
)} -
+
-
-

{board?.name || '...'}

+
+

+ {!isFetching && !error && board?.name + ? board?.name + : '...'} +

+
+
+
+ {!isFetching && + !error && + board?.views.map(({ name, id, _id }) => ( + + ))} + + +
- {/*
*/} - {/* {[*/} - {/* {*/} - {/* name: 'List',*/} - {/* icon: ,*/} - {/* active: true*/} - {/* },*/} - {/* {*/} - {/* name: 'Bugs',*/} - {/* icon: ,*/} - {/* active: false*/} - {/* }*/} - {/* ].map(({ name, icon, active }) => (*/} - {/* */} - {/* */} - {/* {icon}*/} - {/* */} - {/* {name}*/} - {/* */} - {/*
*/} - {/*
*/} - {/* ))}*/} - {/*
*/}
- {board && ( + {view && ( @@ -138,7 +141,9 @@ function ViewTopMenu({ boardId, sideBarClosed, toggleSideBar }) { {/*
*/}
- {board && } + {board && view && ( + + )}
{ - if (!board) return - if (!board.groupBy || board.groupBy === 'none') { + if (!view) return + if (!view.groupBy || view.groupBy === 'none') { const fuse = new Fuse(board.tasks, { keys: ['name'], shouldSort: false @@ -44,10 +46,11 @@ function BoardView({ board, members, hasPerms }, viewContainerRef) { return ( attribute._id === board.groupBy) + board.attributes.find(attribute => attribute._id === view.groupBy) .labels ) @@ -72,7 +75,7 @@ function BoardView({ board, members, hasPerms }, viewContainerRef) { buildTree(board.tasks).map(task => { const value = task.options.find( - option => option.column === board.groupBy + option => option.column === view.groupBy )?.value const label = labels.find(label => label._id === value) if (value && label) label.tasks.push(task) @@ -84,11 +87,12 @@ function BoardView({ board, members, hasPerms }, viewContainerRef) { return labels.map(label => ( x._id.toString() === result.draggableId ) - if (!(!board.groupBy || board.groupBy === 'none')) { + + const view = board?.views.find(x => x.id === params?.view) + if (!(!view.groupBy || view.groupBy === 'none')) { const destinationTask = groupedTasks.find( group => group._id === result.destination.droppableId ).tasks[result.destination.index] @@ -145,7 +151,11 @@ function BoardView({ board, members, hasPerms }, viewContainerRef) { boardId: board._id }) } else if (/^attribute /gm.test(result.type)) { - sortAttribute({ result, boardId: board._id }) + sortAttribute({ + viewShortId: params.view, + result, + boardId: board._id + }) } } diff --git a/src/pages/workspace/view/board/TaskGroup.tsx b/src/pages/workspace/view/board/TaskGroup.tsx index 807f68c..1d1448e 100644 --- a/src/pages/workspace/view/board/TaskGroup.tsx +++ b/src/pages/workspace/view/board/TaskGroup.tsx @@ -40,10 +40,11 @@ interface Props { taskGroupId: string members: Member[] groupedTasks: Label[] + viewShortId: string } function TaskGroup(props: Props, viewContainerRef) { - const { hasPerms, tasks, boardId, groupBy, attributes } = props + const { hasPerms, tasks, boardId, groupBy, attributes, viewShortId } = props const [collapsed, setCollapsed] = useState(false) const [activeItem, setActiveItem] = useState(null) const [collapsedItems, setCollapsedItems] = useState([]) @@ -179,6 +180,7 @@ function TaskGroup(props: Props, viewContainerRef) { const sortedTasks = arrayMove(clonedTasks, activeIndex, overIndex) sortTask({ + viewShortId, newItems: flatten(buildTree(sortedTasks)), boardId, taskId: clonedTasks[activeIndex]._id, @@ -357,6 +359,7 @@ function TaskGroup(props: Props, viewContainerRef) { totalCount={flattenedTasks.length} itemContent={(i, task) => ( {Boolean(activeItem) && ( { props.close() @@ -20,6 +22,7 @@ function AddAttributePopover(props) { return } addAttribute({ + viewShortId: params.view, boardId: props.boardId, type, _id: ObjectId() diff --git a/src/pages/workspace/view/board/taskGroup/AttributePopover.js b/src/pages/workspace/view/board/taskGroup/AttributePopover.js index a9f41f3..4c52bc4 100644 --- a/src/pages/workspace/view/board/taskGroup/AttributePopover.js +++ b/src/pages/workspace/view/board/taskGroup/AttributePopover.js @@ -15,12 +15,14 @@ import { } from '../../../../../modules/services/boardSlice.ts' import { regular } from '@fortawesome/fontawesome-svg-core/import.macro' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useParams } from 'react-router-dom' function AttributePopover(props) { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [name, setName] = useState(props.attr.name) const [deleteAttribute] = useDeleteAttributeMutation() const [editAttributeName] = useEditAttributeNameMutation() + const params = useParams() useEffect(() => { setName(props.attr.name || '') @@ -33,12 +35,17 @@ function AttributePopover(props) { const handleAttributeDelete = async () => { setDeleteDialogOpen(!deleteDialogOpen) - deleteAttribute({ attributeId: props.attr._id, boardId: props.boardId }) + deleteAttribute({ + viewShortId: params.view, + attributeId: props.attr._id, + boardId: props.boardId + }) } const updateName = async () => { if (props.attr.name === name || name === '') return editAttributeName({ + viewShortId: params.view, attributeId: props.attr._id, name, boardId: props.boardId diff --git a/src/pages/workspace/view/board/taskGroup/NewTaskForm.tsx b/src/pages/workspace/view/board/taskGroup/NewTaskForm.tsx index 9e26435..f6d51cc 100644 --- a/src/pages/workspace/view/board/taskGroup/NewTaskForm.tsx +++ b/src/pages/workspace/view/board/taskGroup/NewTaskForm.tsx @@ -7,6 +7,7 @@ import { } from '../../../../../modules/services/boardSlice.ts' import { useRef, useState } from 'react' import { useClickAway } from 'react-use' +import { useParams } from 'react-router-dom' function NewTaskForm({ boardId, @@ -15,6 +16,7 @@ function NewTaskForm({ setNewTaskOpen, taskId }) { + const params = useParams() const { data: user } = useFetchUserQuery() const [addTask] = useAddTaskMutation() const [addSubtask] = useAddSubtaskMutation() @@ -31,6 +33,7 @@ function NewTaskForm({ resetNewTaskName() if (level >= 0) return handleSubTask() addTask({ + viewShortId: params.view, author: user.username, level: level + 1, boardId, @@ -55,6 +58,7 @@ function NewTaskForm({ const handleSubTask = () => { addSubtask({ + viewShortId: params.view, boardId, newTaskName, taskId @@ -65,12 +69,14 @@ function NewTaskForm({ multipleTasks.map(taskName => { if (level >= 0) { addSubtask({ + viewShortId: params.view, boardId, newTaskName: taskName, taskId }) } else { addTask({ + viewShortId: params.view, author: user.username, level: level + 1, boardId, diff --git a/src/pages/workspace/view/board/taskGroup/Task.tsx b/src/pages/workspace/view/board/taskGroup/Task.tsx index 9357cda..4bd00a4 100644 --- a/src/pages/workspace/view/board/taskGroup/Task.tsx +++ b/src/pages/workspace/view/board/taskGroup/Task.tsx @@ -26,6 +26,7 @@ interface Props { groupedTasks: Label[] taskGroupId: string index: number + viewShortId: string } function Task({ @@ -38,7 +39,8 @@ function Task({ handleCollapse, groupedTasks, taskGroupId, - index + index, + viewShortId }: Props) { const [anchor, setAnchor] = useState(null) const navigate = useNavigate() @@ -104,6 +106,7 @@ function Task({ setTaskEditing(false) if (task.name === taskName) return editTaskField({ + viewShortId: viewShortId, type: 'name', value: taskName, boardId, diff --git a/src/pages/workspace/view/board/taskGroup/TaskModal.tsx b/src/pages/workspace/view/board/taskGroup/TaskModal.tsx index 9881148..28dcc37 100644 --- a/src/pages/workspace/view/board/taskGroup/TaskModal.tsx +++ b/src/pages/workspace/view/board/taskGroup/TaskModal.tsx @@ -31,7 +31,10 @@ interface Props { function TaskModal({ task, boardId, hasPerms, members }: Props) { const navigate = useNavigate() const params = useParams() - const { data: board } = useFetchBoardQuery(boardId) + const { data: board } = useFetchBoardQuery({ + boardId, + viewShortId: params.view + }) const { data: user } = useFetchUserQuery() const [name, changeName] = useInputState(task.name) const [anchor, setAnchor] = useState(null) @@ -44,7 +47,9 @@ function TaskModal({ task, boardId, hasPerms, members }: Props) { if (document.activeElement instanceof HTMLElement) { document.activeElement.blur() } - navigate(`/${params.workspace}/${params.space}/${params.board}`) + navigate( + `/${params.workspace}/${params.space}/${params.board}/${params.view}` + ) } useEffect(() => { @@ -62,7 +67,8 @@ function TaskModal({ task, boardId, hasPerms, members }: Props) { type: 'name', value: name, boardId, - taskId: task._id + taskId: task._id, + viewShortId: params.view }) } @@ -70,6 +76,7 @@ function TaskModal({ task, boardId, hasPerms, members }: Props) { if (editor.getJSON() === '') return await addTaskComment({ + viewShortId: params.view, _id: ObjectId(), boardId, content: editor.getJSON(), diff --git a/src/pages/workspace/view/board/taskGroup/task/PersonColumnPopover.js b/src/pages/workspace/view/board/taskGroup/task/PersonColumnPopover.js index 368763d..e50b51d 100644 --- a/src/pages/workspace/view/board/taskGroup/task/PersonColumnPopover.js +++ b/src/pages/workspace/view/board/taskGroup/task/PersonColumnPopover.js @@ -30,7 +30,8 @@ function PersonColumnPopover({ value: taskOption.value.filter(x => x !== user._id), type: 'person', boardId, - taskId: task._id + taskId: task._id, + viewShortId: params.view }) } else { let value = [user._id] @@ -40,7 +41,8 @@ function PersonColumnPopover({ value, type: 'person', boardId, - taskId: task._id + taskId: task._id, + viewShortId: params.view }) } } diff --git a/src/pages/workspace/view/board/taskGroup/task/TaskColumnPopover.tsx b/src/pages/workspace/view/board/taskGroup/task/TaskColumnPopover.tsx index 2af067b..f9daa99 100644 --- a/src/pages/workspace/view/board/taskGroup/task/TaskColumnPopover.tsx +++ b/src/pages/workspace/view/board/taskGroup/task/TaskColumnPopover.tsx @@ -12,6 +12,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { solid } from '@fortawesome/fontawesome-svg-core/import.macro' import { Menu, Transition } from '@headlessui/react' import { Attribute, Label, FlatTask } from '../../../../../../types/Board' +import { useParams } from 'react-router-dom' interface Props { hasPerms: (string) => boolean @@ -32,6 +33,7 @@ function TaskColumnPopover({ const [editAttributeLabels] = useEditAttributeLabelsMutation() const [clearStatusTask] = useClearStatusTaskMutation() const [editTaskField] = useEditTaskFieldMutation() + const params = useParams() const [labelsEditing, setLabelsEditing] = useState(false) @@ -43,6 +45,7 @@ function TaskColumnPopover({ const toggleEdit = async () => { if (labelsEditing) { editAttributeLabels({ + viewShortId: params.view, boardId, name: attribute.name, labels: editingLabels @@ -111,6 +114,7 @@ function TaskColumnPopover({ editTaskField({ boardId, + viewShortId: params.view, taskId: task._id, column: attribute._id, value: id._id, @@ -124,6 +128,7 @@ function TaskColumnPopover({ if (labelsEditing) return clearStatusTask({ + viewShortId: params.view, boardId, taskId: task._id, optionId: attribute._id diff --git a/src/pages/workspace/view/board/taskGroup/task/TaskPopover.js b/src/pages/workspace/view/board/taskGroup/task/TaskPopover.js index 814a480..8c3618f 100644 --- a/src/pages/workspace/view/board/taskGroup/task/TaskPopover.js +++ b/src/pages/workspace/view/board/taskGroup/task/TaskPopover.js @@ -13,8 +13,10 @@ import styles from '../../../../../../styles/TaskPopover.module.scss' import { useDeleteTaskMutation } from '../../../../../../modules/services/boardSlice.ts' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { regular, solid } from '@fortawesome/fontawesome-svg-core/import.macro' +import { useParams } from 'react-router-dom' function TaskPopover(props) { + const params = useParams() const [deleteTask] = useDeleteTaskMutation() const [deleteDialog, setDeleteDialog] = useState(false) const { boardId, taskId } = props @@ -28,6 +30,7 @@ function TaskPopover(props) { const handleDelete = () => { setDeleteDialog(false) deleteTask({ + viewShortId: params.view, boardId, taskId }) diff --git a/src/pages/workspace/view/board/taskGroup/task/taskColumns/TextColumn.tsx b/src/pages/workspace/view/board/taskGroup/task/taskColumns/TextColumn.tsx index 32348ca..01cacd7 100644 --- a/src/pages/workspace/view/board/taskGroup/task/taskColumns/TextColumn.tsx +++ b/src/pages/workspace/view/board/taskGroup/task/taskColumns/TextColumn.tsx @@ -2,6 +2,7 @@ import { useEditTaskFieldMutation } from '../../../../../../../modules/services/ import { Attribute, Task } from '../../../../../../../types/Board' import useInputState from '../../../../../../../modules/hooks/useInputState.tsx' import { useEffect } from 'react' +import { useParams } from 'react-router-dom' type Props = { attribute: Attribute @@ -16,6 +17,7 @@ export default function TextColumn({ boardId, hasPerms }: Props) { + const params = useParams() const [editTaskField] = useEditTaskFieldMutation() const handleTextEdit = async e => { @@ -27,6 +29,7 @@ export default function TextColumn({ editTaskField({ column: e.target.name, + viewShortId: params.view, value: e.target.value, type: 'text', boardId, diff --git a/src/pages/workspace/view/board/taskGroup/taskModal/Activity.js b/src/pages/workspace/view/board/taskGroup/taskModal/Activity.js index bcd2a33..3f92195 100644 --- a/src/pages/workspace/view/board/taskGroup/taskModal/Activity.js +++ b/src/pages/workspace/view/board/taskGroup/taskModal/Activity.js @@ -2,9 +2,14 @@ import styles from '../../../../../../styles/TaskModal.module.scss' import RelativeDate from '../../../../../../utils/RelativeDate' import { diffWords } from 'diff' import { useFetchBoardQuery } from '../../../../../../modules/services/boardSlice.ts' +import { useParams } from 'react-router-dom' function Activity({ timestamp, activity, boardId }) { - const { data: board } = useFetchBoardQuery(boardId) + const params = useParams() + const { data: board } = useFetchBoardQuery({ + boardId, + viewShortId: params.view + }) const getDiff = () => { const groups = diffWords(activity.change.from, activity.change.to) diff --git a/src/pages/workspace/view/board/taskGroup/taskModal/Comment.tsx b/src/pages/workspace/view/board/taskGroup/taskModal/Comment.tsx index 1825c51..4361af9 100644 --- a/src/pages/workspace/view/board/taskGroup/taskModal/Comment.tsx +++ b/src/pages/workspace/view/board/taskGroup/taskModal/Comment.tsx @@ -8,6 +8,7 @@ import Editor from '../../../../../../utils/Editor.tsx' import CommentReplySection from './comment/CommentReplySection.tsx' import RelativeDate from '../../../../../../utils/RelativeDate' import { Comment as CommentType } from '../../../../../../types/Board' +import { useParams } from 'react-router-dom' interface Props { comment: CommentType @@ -15,12 +16,14 @@ interface Props { boardId: string } function Comment({ comment, taskId, boardId }: Props) { + const params = useParams() const [editing, setEditing] = useState(false) const [replySectionOpen, setReplySectionOpen] = useState(false) const [editTaskComment] = useEditTaskCommentMutation() const handleSave = editor => { editTaskComment({ + viewShortId: params.view, boardId, taskId, commentId: comment._id, diff --git a/src/pages/workspace/view/board/taskGroup/taskModal/WatcherPopover.js b/src/pages/workspace/view/board/taskGroup/taskModal/WatcherPopover.js index d1420a6..c005dc9 100644 --- a/src/pages/workspace/view/board/taskGroup/taskModal/WatcherPopover.js +++ b/src/pages/workspace/view/board/taskGroup/taskModal/WatcherPopover.js @@ -25,9 +25,19 @@ function WatcherPopover({ task, anchor, setAnchor, boardId }) { const handleClick = (user, isWatcher) => { if (isWatcher) { - removeWatcher({ task: task, user: user, boardId }) + removeWatcher({ + viewShortId: params.view, + task: task, + user: user, + boardId + }) } else { - addWatcher({ task: task, user: user, boardId }) + addWatcher({ + viewShortId: params.view, + task: task, + user: user, + boardId + }) } } diff --git a/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentPopover.js b/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentPopover.js index cd6804d..bd94e0d 100644 --- a/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentPopover.js +++ b/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentPopover.js @@ -7,12 +7,14 @@ import { useDeleteReplyMutation, useDeleteTaskCommentMutation } from '../../../../../../../modules/services/boardSlice.ts' +import { useParams } from 'react-router-dom' function CommentPopover({ taskId, commentId, type, replyId, boardId }) { const [open, setOpen] = useState(null) const [deleteComment] = useDeleteTaskCommentMutation() const [deleteReply] = useDeleteReplyMutation() const lastEl = useRef() + const params = useParams() const items = [ { @@ -32,9 +34,21 @@ function CommentPopover({ taskId, commentId, type, replyId, boardId }) { ] const handleDelete = () => { - if (type === 'comment') deleteComment({ boardId, taskId, commentId }) + if (type === 'comment') + deleteComment({ + viewShortId: params.view, + boardId, + taskId, + commentId + }) else if (type === 'reply') - deleteReply({ boardId, taskId, commentId, replyId }) + deleteReply({ + viewShortId: params.view, + boardId, + taskId, + commentId, + replyId + }) setOpen(false) } diff --git a/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentReplySection.tsx b/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentReplySection.tsx index 9c73f07..3162c58 100644 --- a/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentReplySection.tsx +++ b/src/pages/workspace/view/board/taskGroup/taskModal/comment/CommentReplySection.tsx @@ -4,6 +4,7 @@ import Reply from './commentReplySection/Reply.tsx' import { useFetchUserQuery } from '../../../../../../../modules/services/userSlice' import { ObjectId } from '../../../../../../../utils' import { ReplyType } from '../../../../../../../types/Board' +import { useParams } from 'react-router-dom' interface Props { taskId: string @@ -15,9 +16,11 @@ interface Props { function CommentReplySection({ taskId, commentId, replies, boardId }: Props) { const { data: user } = useFetchUserQuery() const [addReply] = useAddReplyMutation() + const params = useParams() const handleAddReply = editor => { addReply({ + viewShortId: params.view, _id: ObjectId(), boardId, taskId, diff --git a/src/pages/workspace/view/board/taskGroup/taskModal/comment/commentReplySection/Reply.tsx b/src/pages/workspace/view/board/taskGroup/taskModal/comment/commentReplySection/Reply.tsx index a8f793e..bb4b4af 100644 --- a/src/pages/workspace/view/board/taskGroup/taskModal/comment/commentReplySection/Reply.tsx +++ b/src/pages/workspace/view/board/taskGroup/taskModal/comment/commentReplySection/Reply.tsx @@ -7,6 +7,7 @@ import { useState } from 'react' import { useEditReplyMutation } from '../../../../../../../../modules/services/boardSlice.ts' import RelativeDate from '../../../../../../../../utils/RelativeDate' import { Reply as ReplyType } from '../../../../../../../../types/Board' +import { useParams } from 'react-router-dom' interface Props { commentId: string @@ -18,9 +19,11 @@ interface Props { function Reply({ commentId, reply, taskId, boardId }: Props) { const [editing, setEditing] = useState(false) const [editReply] = useEditReplyMutation() + const params = useParams() const handleSave = editor => { editReply({ + viewShortId: params.view, boardId, taskId, commentId, diff --git a/src/pages/workspace/view/viewTopMenu/FilterMenu.tsx b/src/pages/workspace/view/viewTopMenu/FilterMenu.tsx index 8c7b270..e02f2e5 100644 --- a/src/pages/workspace/view/viewTopMenu/FilterMenu.tsx +++ b/src/pages/workspace/view/viewTopMenu/FilterMenu.tsx @@ -8,6 +8,7 @@ import { useFetchBoardQuery, useSetFiltersMutation } from '../../../../modules/services/boardSlice.ts' +import { useParams } from 'react-router-dom' const operations = { status: ['Is', 'Is not', 'Is set', 'Is not set'], @@ -15,8 +16,12 @@ const operations = { text: ['Contains', 'Does not contain', 'Is set', 'Is not set'] } -export default function FilterMenu({ boardId }) { - const { data: board } = useFetchBoardQuery(boardId) +export default function FilterMenu({ boardId, view }) { + const params = useParams() + const { data: board } = useFetchBoardQuery({ + boardId, + viewShortId: params?.view + }) const [setFilters] = useSetFiltersMutation() const [filters, setFiltersState] = useState([]) @@ -81,21 +86,23 @@ export default function FilterMenu({ boardId }) { } useEffect(() => { - if (board?.filters) { - setFiltersState(board.filters) + if (view?.filters) { + setFiltersState(view.filters) } - }, [board.filters]) + }, [view.filters]) const handleClose = open => { + if (filters === view.filters) return if (!open) { setFilters({ - boardId, + viewId: view._id, filters }) } } const used = filters.length >= 1 + if (!board) return return ( diff --git a/src/pages/workspace/view/viewTopMenu/NewViewMenu.tsx b/src/pages/workspace/view/viewTopMenu/NewViewMenu.tsx new file mode 100644 index 0000000..bae345e --- /dev/null +++ b/src/pages/workspace/view/viewTopMenu/NewViewMenu.tsx @@ -0,0 +1,64 @@ +import { Menu, Transition } from '@headlessui/react' +import { Fragment } from 'react' +import ListIcon from '../../../../styles/assets/ListIcon.tsx' +import { useAddViewMutation } from '../../../../modules/services/boardSlice.ts' +import * as React from 'react' + +interface NewViewMenuProps { + boardId: string +} + +function NewViewMenu({ boardId }: NewViewMenuProps) { + const [addView] = useAddViewMutation() + + const handleAddView = (type: string): void => { + addView({ + boardId, + type + }) + } + + return ( + +
+ +
+
+ + + Add view + +
+
+
+
+ + +
+ handleAddView('list')}> + {({ active }) => ( + + )} + +
+
+
+
+ ) +} + +export default NewViewMenu diff --git a/src/pages/workspace/view/viewTopMenu/ViewNavItem.tsx b/src/pages/workspace/view/viewTopMenu/ViewNavItem.tsx new file mode 100644 index 0000000..8b02cfa --- /dev/null +++ b/src/pages/workspace/view/viewTopMenu/ViewNavItem.tsx @@ -0,0 +1,190 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import ListIcon from '../../../../styles/assets/ListIcon.tsx' +import { regular, solid } from '@fortawesome/fontawesome-svg-core/import.macro' +import { Menu, Transition } from '@headlessui/react' +import { Fragment, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { + useDeleteViewMutation, + useRenameViewMutation +} from '../../../../modules/services/boardSlice.ts' +import useInputState from '../../../../modules/hooks/useInputState.tsx' +import { useParams } from 'react-router-dom' +import { usePopper } from '../../../../utils/usePopper.ts' + +interface Props { + boardId: string + _id: string + id: string + name: string + handleViewClick: (id: string) => void + active: boolean +} + +export default function ({ + boardId, + _id, + active, + name, + id, + handleViewClick +}: Props) { + const [deleteView] = useDeleteViewMutation() + const [renameView] = useRenameViewMutation() + const [renaming, setRenaming] = useState(false) + const [newName, changeNewName, , setNewName] = useInputState(name) + const [width, setWidth] = useState(100) + const ref = useRef() + const params = useParams() + + const [trigger, container] = usePopper({ + placement: 'bottom-end', + strategy: 'fixed', + modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] + }) + + const handleRenameClick = () => { + setWidth(ref.current.offsetWidth) + setRenaming(true) + } + + const handleRename = () => { + renameView({ + viewShortId: params.view, + viewId: _id, + name: newName + }) + setRenaming(false) + } + + const handleDelete = () => { + deleteView({ + viewShortId: params.view, + boardId, + viewId: _id + }) + } + + useEffect(() => { + setNewName(name) + }, [name]) + + return ( +
handleViewClick(id)} + className="min-w-[0] shrink-0 subpixel-antialiased flex justify-center items-center h-[60px] box-border cursor-pointer border-y-[3px] border-y-[white] border-solid" + > +
+ + {renaming ? ( + { + if (['Enter', 'Escape'].includes(e.key)) + e.currentTarget.blur() + }} + style={{ width: `${width}px` }} + onBlur={handleRename} + className="outline-none text-sm leading-[14px] font-medium text-[#4169e1]" + value={newName} + onChange={changeNewName} + autoFocus + /> + ) : ( + + {name} + + )} + {active && ( + + +
+ +
+
+
+ + +
+ + {({ active }) => ( +
+
+
+ +
+

+ Rename +

+
+
+ )} +
+ + {({ active }) => ( +
+
+
+ +
+

+ Delete +

+
+
+ )} +
+
+
+
+
+
+ )} +
+
+ ) +} diff --git a/src/utils/usePopper.ts b/src/utils/usePopper.ts new file mode 100644 index 0000000..79da025 --- /dev/null +++ b/src/utils/usePopper.ts @@ -0,0 +1,41 @@ +import { RefCallback, useRef, useCallback, useMemo } from 'react' +import { createPopper, Options } from '@popperjs/core' + +/** + * Example implementation to use Popper: https://popper.js.org/ + */ +export function usePopper( + options?: Partial +): [RefCallback, RefCallback] { + let reference = useRef(null) + let popper = useRef(null) + + let cleanupCallback = useRef(() => {}) + + let instantiatePopper = useCallback(() => { + if (!reference.current) return + if (!popper.current) return + + if (cleanupCallback.current) cleanupCallback.current() + + cleanupCallback.current = createPopper( + reference.current, + popper.current, + options + ).destroy + }, [reference, popper, cleanupCallback, options]) + + return useMemo( + () => [ + referenceDomNode => { + reference.current = referenceDomNode + instantiatePopper() + }, + popperDomNode => { + popper.current = popperDomNode + instantiatePopper() + } + ], + [reference, popper, instantiatePopper] + ) +}