From 15432ba4a99e6ab63409530b8f4441a3096ce0bb Mon Sep 17 00:00:00 2001 From: hudy9x <95471659+hudy9x@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:30:51 +0700 Subject: [PATCH] enhance: add status filter to board view (#152) --- .../project/[projectId]/ProjectTabContent.tsx | 30 ++- .../board/BoardActionCreateTask.tsx | 5 +- .../[projectId]/board/BoardContainer.tsx | 24 +- .../project/[projectId]/board/BoardHeader.tsx | 14 +- .../[projectId]/board/BoardItemDraggable.tsx | 17 ++ .../project/[projectId]/board/BoardList.tsx | 1 - .../[projectId]/board/useBoardAction.ts | 3 +- .../[projectId]/board/useBoardDndAction.ts | 3 +- .../[projectId]/calendar/CalMonthTask.tsx | 58 +++-- .../project/[projectId]/views/ListMode.tsx | 30 +-- .../[projectId]/views/TaskCheckAll.tsx | 3 +- .../Project/Vision/VisionMonthNavigator.tsx | 3 +- .../app/_features/ProjectContainer/index.tsx | 2 + .../_features/ProjectContainer/useGetTask.ts | 16 +- .../ProjectView/ProjectViewModalForm.tsx | 2 + .../app/_features/ProjectView/index.tsx | 23 -- .../_features/ProjectView/useSetViewFilter.ts | 12 +- .../ProjectViewFilter/BoardFilter.tsx | 44 ++-- .../ProjectViewFilter/FilterForm.tsx | 194 +++++++------- .../app/_features/Report/ReportContent.tsx | 4 +- .../TaskFilter/CalendarModeFilter.tsx | 21 +- .../app/_features/TaskFilter/context.tsx | 8 +- .../ui-app/app/_features/TaskFilter/index.tsx | 22 +- .../TaskFilter/useTaskFilterContext.ts | 120 +++++++++ .../app/_features/TaskFilter/useTodoFilter.ts | 4 +- .../TaskFilter/useUpdateGroupbyItem.ts | 245 ++++++++++++++++++ packages/ui-app/services/task.ts | 6 +- 27 files changed, 651 insertions(+), 263 deletions(-) create mode 100644 packages/ui-app/app/_features/TaskFilter/useTaskFilterContext.ts create mode 100644 packages/ui-app/app/_features/TaskFilter/useUpdateGroupbyItem.ts diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/ProjectTabContent.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/ProjectTabContent.tsx index a75f04e5..b66bb038 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/ProjectTabContent.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/ProjectTabContent.tsx @@ -49,20 +49,22 @@ function AnimateView({ visible: boolean children: React.ReactNode }) { - const id = useId() - return ( - - {visible ? ( - - {children} - - ) : null} - - ) + if (!visible) return null + return <>{children} + // const id = useId() + // return ( + // + // {visible ? ( + // + // {children} + // + // ) : null} + // + // ) } export default function ProjectTabContent() { diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardActionCreateTask.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardActionCreateTask.tsx index a5ad15ca..d3439fea 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardActionCreateTask.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardActionCreateTask.tsx @@ -7,6 +7,7 @@ import { taskAdd } from '@/services/task' import { Task, TaskPriority } from '@prisma/client' import { useTaskFilter } from '@/features/TaskFilter/context' import { useParams } from 'next/navigation' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' export const BoardActionCreateTaskWithIcon = ({ groupId @@ -14,7 +15,7 @@ export const BoardActionCreateTaskWithIcon = ({ groupId: string }) => { const { isGroupbyStatus, isGroupbyAssignee, isGroupbyPriority } = - useTaskFilter() + useTaskFilterContext() const [visible, setVisible] = useState(false) const { handleSubmit } = useHandleSubmit(() => { setVisible(false) @@ -90,7 +91,7 @@ const useHandleSubmit = (cb: () => void) => { export const BoardActionCreateTask = ({ groupId }: { groupId: string }) => { const { isGroupbyPriority, isGroupbyAssignee, isGroupbyStatus } = - useTaskFilter() + useTaskFilterContext() const [visible, setVisible] = useState(false) const { projectId } = useParams() const { handleSubmit } = useHandleSubmit(() => { diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardContainer.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardContainer.tsx index 0655113b..e5f055d9 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardContainer.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardContainer.tsx @@ -1,4 +1,4 @@ -import { useTaskFilter } from '@/features/TaskFilter/context' +import { ETaskFilterGroupByType } from '@/features/TaskFilter/context' import './style.css' import { DragDropContext, DropResult, Droppable } from 'react-beautiful-dnd' @@ -8,16 +8,19 @@ import { triggerEventTaskReorder } from '@/events/useEventTaskReorder' import { useUrl } from '@/hooks/useUrl' import { useBoardRealtimeUpdate } from './useBoardRealtimeUpdate' import { triggerEventMoveTaskToOtherBoard } from '@/events/useEventMoveTaskToOtherBoard' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' export default function BoardContainer() { const { projectId } = useUrl() - const { groupByItems } = useTaskFilter() + const { groupByItems, filter, groupBy } = useTaskFilterContext() const { dragColumnToAnotherPosition, dragItemToAnotherPosition, dragItemToAnotherColumn } = useBoardDndAction() + const { statusIds } = filter + useBoardRealtimeUpdate() const onDragEnd = (result: DropResult) => { @@ -85,6 +88,18 @@ export default function BoardContainer() { {...provided.droppableProps} ref={provided.innerRef}> {groupByItems.map((group, groupIndex) => { + if ( + groupBy === ETaskFilterGroupByType.STATUS && + statusIds.length + ) { + if ( + !statusIds.includes('ALL') && + !statusIds.includes(group.id) + ) { + return null + } + } + return ( ) })} - {/*
*/} - {/*

*/} - {/* Create new status */} - {/*

*/} - {/*
*/} {provided.placeholder} ) diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardHeader.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardHeader.tsx index e3a7fb19..92f3e2ad 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardHeader.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardHeader.tsx @@ -4,6 +4,7 @@ import { MdDragIndicator } from 'react-icons/md' import { useTaskFilter } from '@/features/TaskFilter/context' import { Avatar } from '@shared/ui' import Badge from '@/components/Badge' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' // import TaskCheckAll from '../views/TaskCheckAll' interface IBoardHeaderProps { @@ -22,17 +23,20 @@ export default function BoardHeader({ id, provided }: IBoardHeaderProps) { - const { isGroupbyAssignee, isGroupbyStatus, groupByLoading } = useTaskFilter() + const { isGroupbyAssignee, isGroupbyStatus, groupByLoading } = + useTaskFilterContext() return (
+ className={`board-header-loading ${ + groupByLoading ? 'visible' : 'invisible ' + }`}>
+ className={`board-header-section ${ + groupByLoading ? 'opacity-0' : 'opacity-100' + }`}> {isGroupbyStatus ? (
{ return tasks.find(t => t.id === item) }, [JSON.stringify(tasks), item]) if (!task) return null + // This will filter tasks by Status + // it fast, but can not update the total + + // if ( + // filteredStatusIds.length && + // task.taskStatusId && + // !filteredStatusIds.includes('ALL') + // ) { + // if (!filteredStatusIds.includes(task.taskStatusId)) { + // return null + // } + // } + return ( {provided => ( diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardList.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardList.tsx index 8c412114..b6197d69 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardList.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/board/BoardList.tsx @@ -2,7 +2,6 @@ import { Droppable } from 'react-beautiful-dnd' import BoardItemDraggable from './BoardItemDraggable' import { BoardActionCreateTask } from './BoardActionCreateTask' -import { useTaskStore } from '@/store/task' export default function BoardList({ items, diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardAction.ts b/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardAction.ts index c67ef515..1669c153 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardAction.ts +++ b/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardAction.ts @@ -1,4 +1,5 @@ import { useTaskFilter } from '@/features/TaskFilter/context' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' import { projectStatusUpdateOrder } from '@/services/status' import { taskUpdate } from '@/services/task' import { useProjectStatusStore } from '@/store/status' @@ -12,7 +13,7 @@ import { useEffect, useState } from 'react' export const useBoardAction = () => { const { updateTask } = useTaskStore() const { isGroupbyStatus, isGroupbyAssignee, isGroupbyPriority } = - useTaskFilter() + useTaskFilterContext() const { projectId } = useParams() const [updateSttCounter, setUpdateSttCounter] = useState(0) const { statuses, swapOrder } = useProjectStatusStore() diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardDndAction.ts b/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardDndAction.ts index 6a3d9bdd..3b40da1d 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardDndAction.ts +++ b/packages/ui-app/app/[orgID]/project/[projectId]/board/useBoardDndAction.ts @@ -6,10 +6,11 @@ import { useBoardAction } from './useBoardAction' import { useBoardItemReorder } from './useBoardItemReorder' import { serviceTask } from '@/services/task' import { useUrl } from '@/hooks/useUrl' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' export const useBoardDndAction = () => { const { moveTaskToAnotherGroup, rearrangeColumn } = useBoardAction() - const { setGroupbyItems } = useTaskFilter() + const { setGroupbyItems } = useTaskFilterContext() const { reorderTask } = useBoardItemReorder() const { projectId } = useUrl() diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/calendar/CalMonthTask.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/calendar/CalMonthTask.tsx index d16b8821..0c9c4979 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/calendar/CalMonthTask.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/calendar/CalMonthTask.tsx @@ -7,6 +7,7 @@ import { ICalendarView, useCalendarContext } from './context' import CalTaskInWeek from './CalTaskInWeek' import { useTaskFilter } from '@/features/TaskFilter/context' import Link from 'next/link' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' interface ICalMonthTaskProps { id: string @@ -27,37 +28,39 @@ export default function CalMonthTask({ assigneeId, taskStatusId }: ICalMonthTaskProps) { - const { filter } = useTaskFilter() + const { filter } = useTaskFilterContext() const { status: filterStatus, statusIds: filterStatusIds } = filter const { color, type } = useStatusData(taskStatusId || '') const { calendarView } = useCalendarContext() - const view = () => - - {provided => ( -
- {calendarView === ICalendarView.WEEK ? ( - - ) : ( - - )} -
- )} -
- + const view = () => ( + + + {provided => ( +
+ {calendarView === ICalendarView.WEEK ? ( + + ) : ( + + )} +
+ )} +
+ + ) // if statusIds contain ALL or nothing // display view @@ -65,7 +68,6 @@ export default function CalMonthTask({ return view() } - // if statusIds have some // display tasks that have the same status id if (filterStatusIds.includes(taskStatusId)) { diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/views/ListMode.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/views/ListMode.tsx index c9ec0001..3a95aa52 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/views/ListMode.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/views/ListMode.tsx @@ -9,30 +9,7 @@ import ListCreateTask from './ListCreateTask' import { useTaskFilter } from '@/features/TaskFilter/context' import TaskMultipleActions from '@/features/TaskMultipleActions' import ListRow from './ListRow' - -// const useListFilterCondition = ({ -// isGroupbyStatus -// }: { -// isGroupbyStatus: boolean -// }) => { -// const groupedByStatusButNotMeetCondition = ( -// task: ExtendedTask, -// groupStatusId: string -// ) => { -// if (isGroupbyStatus && task.taskStatusId !== groupStatusId) { -// if (groupStatusId === 'NONE') { -// return -// } -// return null -// } -// -// return null -// } -// -// return { -// groupedByStatusButNotMeetCondition -// } -// } +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' export default function ListMode() { const { @@ -42,9 +19,10 @@ export default function ListMode() { isGroupbyPriority, isGroupbyAssignee, isGroupbyStatus - } = useTaskFilter() + } = useTaskFilterContext() const { tasks, taskLoading } = useTaskStore() + console.log('listmode render', taskLoading) // const { groupedByStatusButNotMeetCondition } = useListFilterCondition({ // isGroupbyStatus // }) @@ -90,7 +68,7 @@ export default function ListMode() {
- {taskLoading || groupByLoading ? ( + {taskLoading ? ( ) : null} diff --git a/packages/ui-app/app/[orgID]/project/[projectId]/views/TaskCheckAll.tsx b/packages/ui-app/app/[orgID]/project/[projectId]/views/TaskCheckAll.tsx index 5fdff0e5..3c7d47db 100644 --- a/packages/ui-app/app/[orgID]/project/[projectId]/views/TaskCheckAll.tsx +++ b/packages/ui-app/app/[orgID]/project/[projectId]/views/TaskCheckAll.tsx @@ -1,4 +1,5 @@ import { useTaskFilter } from '@/features/TaskFilter/context' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' import { useTaskStore } from '@/store/task' import { Form } from '@shared/ui' import { useEffect, useMemo, useState } from 'react' @@ -8,7 +9,7 @@ export default function TaskCheckAll({ groupId }: { groupId: string }) { const { tasks, selected, taskLoading, toggleMultipleSelected } = useTaskStore() const { groupBy, isGroupbyStatus, isGroupbyAssignee, isGroupbyPriority } = - useTaskFilter() + useTaskFilterContext() const taskIds = useMemo(() => { const ids: string[] = [] diff --git a/packages/ui-app/app/_features/Project/Vision/VisionMonthNavigator.tsx b/packages/ui-app/app/_features/Project/Vision/VisionMonthNavigator.tsx index 605f9b5e..a871e47e 100644 --- a/packages/ui-app/app/_features/Project/Vision/VisionMonthNavigator.tsx +++ b/packages/ui-app/app/_features/Project/Vision/VisionMonthNavigator.tsx @@ -1,9 +1,10 @@ import ListPreset from '@/components/ListPreset' import { useTaskFilter } from '@/features/TaskFilter/context' import { useVisionContext } from './context' +import useTaskFilterContext from '@/features/TaskFilter/useTaskFilterContext' export default function VisionMonthNavigator() { - const { setDateRangeByMonth } = useTaskFilter() + const { setDateRangeByMonth } = useTaskFilterContext() const { filter, setFilter } = useVisionContext() const { month } = filter const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] diff --git a/packages/ui-app/app/_features/ProjectContainer/index.tsx b/packages/ui-app/app/_features/ProjectContainer/index.tsx index 9c151d7a..4d13e1f9 100644 --- a/packages/ui-app/app/_features/ProjectContainer/index.tsx +++ b/packages/ui-app/app/_features/ProjectContainer/index.tsx @@ -15,6 +15,7 @@ import { useGetMembers } from './useGetMembers' import useGetProjectPoint from './useGetProjectPoint' import { useUser } from '@goalie/nextjs' import { useGenTaskMappingObject } from '@/hooks/useGenTaskMappingObject' +import useUpdateGroupbyItem from '../TaskFilter/useUpdateGroupbyItem' export default function ProjectContainer() { const { projectId, orgID } = useParams() @@ -22,6 +23,7 @@ export default function ProjectContainer() { const { getSp } = useUrl() const { user } = useUser() + useUpdateGroupbyItem() useTodoFilter() useGetProjectStatus() useGetTask() diff --git a/packages/ui-app/app/_features/ProjectContainer/useGetTask.ts b/packages/ui-app/app/_features/ProjectContainer/useGetTask.ts index 435a1c71..c039b47f 100644 --- a/packages/ui-app/app/_features/ProjectContainer/useGetTask.ts +++ b/packages/ui-app/app/_features/ProjectContainer/useGetTask.ts @@ -1,18 +1,19 @@ -import { useEffect, useState } from 'react' -import { useTaskFilter } from '../TaskFilter/context' +import { useEffect } from 'react' import { extractDueDate } from '@shared/libs' import { ExtendedTask, useTaskStore } from '@/store/task' import { taskGetByCond } from '@/services/task' import { useParams } from 'next/navigation' import { messageError } from '@shared/ui' import localforage from 'localforage' +import useTaskFilterContext from '../TaskFilter/useTaskFilterContext' export default function useGetTask() { const { projectId } = useParams() const { addAllTasks, setTaskLoading } = useTaskStore() - const { filter } = useTaskFilter() + const { filter } = useTaskFilterContext() - const { groupBy, status, ...filterWithoutGroupBy } = filter + const { groupBy, status, statusIds, ...filterWithoutGroupBy } = filter + // const { groupBy, status,...filterWithoutGroupBy } = filter const key = `TASKLIST_${projectId}` const getAssigneeIds = (assigneeIds: string[]) => { @@ -27,6 +28,7 @@ export default function useGetTask() { .getItem(key) .then(val => { if (val) { + console.log('appied caches from indexdb') addAllTasks(val as ExtendedTask[]) } }) @@ -60,7 +62,7 @@ export default function useGetTask() { }) setTaskLoading(true) - console.log('before getting task =======================', point) + taskGetByCond( { title: term || undefined, @@ -75,7 +77,8 @@ export default function useGetTask() { ) .then(res => { const { data, status, error } = res.data - console.log('fetch task data', data) + console.log('fetched task from server') + if (status !== 200) { addAllTasks([]) localforage.removeItem(key) @@ -85,7 +88,6 @@ export default function useGetTask() { localforage.setItem(key, data) setTimeout(() => { - console.log('update all task', data) addAllTasks(data) }, 300) }) diff --git a/packages/ui-app/app/_features/ProjectView/ProjectViewModalForm.tsx b/packages/ui-app/app/_features/ProjectView/ProjectViewModalForm.tsx index 2412e76e..86d398a3 100644 --- a/packages/ui-app/app/_features/ProjectView/ProjectViewModalForm.tsx +++ b/packages/ui-app/app/_features/ProjectView/ProjectViewModalForm.tsx @@ -52,6 +52,8 @@ export default function ProjectViewModalForm({ }) } + console.log('type', type) + return (
diff --git a/packages/ui-app/app/_features/ProjectView/index.tsx b/packages/ui-app/app/_features/ProjectView/index.tsx index 3834ff87..5d94a2b0 100644 --- a/packages/ui-app/app/_features/ProjectView/index.tsx +++ b/packages/ui-app/app/_features/ProjectView/index.tsx @@ -11,29 +11,6 @@ import DynamicIcon from '@/components/DynamicIcon' import HasRole from '../UserPermission/HasRole' import useSetViewFilter from './useSetViewFilter' -// function ProjectViewItem({view}: {view: }) { -// const clickOnView = (name: string) => { -// push(`${params.orgID}/project/${params.projectId}?mode=${name}`) -// } -// return
clickOnView(view.id)} -// className={`project-view-item group relative ${active ? 'active' : '' -// }`} -// key={index}> -// {icon ? ( -// -// ) : ( -// -// )} -// {view.name} -// -//
-// } - export default function ProjectView() { const searchParams = useSearchParams() const { push } = useRouter() diff --git a/packages/ui-app/app/_features/ProjectView/useSetViewFilter.ts b/packages/ui-app/app/_features/ProjectView/useSetViewFilter.ts index 4b1b4f6f..b9450bc7 100644 --- a/packages/ui-app/app/_features/ProjectView/useSetViewFilter.ts +++ b/packages/ui-app/app/_features/ProjectView/useSetViewFilter.ts @@ -1,13 +1,13 @@ -import { useDebounce } from "@/hooks/useDebounce" -import { useSearchParams } from "next/navigation" -import { useProjectViewList } from "./useProjectViewList" -import { useTaskFilter } from "../TaskFilter/context" -import { IBoardFilter } from "./context" +import { useDebounce } from '@/hooks/useDebounce' +import { useSearchParams } from 'next/navigation' +import { useProjectViewList } from './useProjectViewList' +import { IBoardFilter } from './context' +import useTaskFilterContext from '../TaskFilter/useTaskFilterContext' export default function useSetViewFilter() { const searchParams = useSearchParams() const { views } = useProjectViewList() - const { setFilter, setDefaultFilter } = useTaskFilter() + const { setFilter, setDefaultFilter } = useTaskFilterContext() const mode = searchParams.get('mode') // update task filter once user change to another view diff --git a/packages/ui-app/app/_features/ProjectViewFilter/BoardFilter.tsx b/packages/ui-app/app/_features/ProjectViewFilter/BoardFilter.tsx index 50252469..1068e7df 100644 --- a/packages/ui-app/app/_features/ProjectViewFilter/BoardFilter.tsx +++ b/packages/ui-app/app/_features/ProjectViewFilter/BoardFilter.tsx @@ -1,31 +1,35 @@ -import { ProjectViewType } from "@prisma/client"; -import { Button } from "@shared/ui"; -import FilterForm from "./FilterForm"; +import { ProjectViewType } from '@prisma/client' +import { Button } from '@shared/ui' +import FilterForm from './FilterForm' -export default function ProjectViewFilterByBoard({ type, desc, onAdd }: { +export default function ProjectViewFilterByBoard({ + type, + desc, + onAdd +}: { type: ProjectViewType desc: string onAdd: () => void }) { if (type !== ProjectViewType.BOARD) return null - return <>View image -
-

Board

-

{desc}

+ return ( + <> + View image +
+

Board

+

{desc}

- + -
-
-
+ + ) } diff --git a/packages/ui-app/app/_features/ProjectViewFilter/FilterForm.tsx b/packages/ui-app/app/_features/ProjectViewFilter/FilterForm.tsx index a3714278..a0c6d47b 100644 --- a/packages/ui-app/app/_features/ProjectViewFilter/FilterForm.tsx +++ b/packages/ui-app/app/_features/ProjectViewFilter/FilterForm.tsx @@ -1,103 +1,113 @@ -import ListPreset from "@/components/ListPreset"; -import PointSelect from "@/components/PointSelect"; -import PrioritySelect from "@/components/PrioritySelect"; -import { Form } from "@shared/ui"; -import { ETaskFilterGroupByType } from "../TaskFilter/context"; -import { useProjectViewContext } from "../ProjectView/context"; -import StatusSelectMultiple from "@/components/StatusSelectMultiple"; -import { ProjectViewType } from "@prisma/client"; +import ListPreset from '@/components/ListPreset' +import PointSelect from '@/components/PointSelect' +import PrioritySelect from '@/components/PrioritySelect' +import { Form } from '@shared/ui' +import { ETaskFilterGroupByType } from '../TaskFilter/context' +import { useProjectViewContext } from '../ProjectView/context' +import StatusSelectMultiple from '@/components/StatusSelectMultiple' +import { ProjectViewType } from '@prisma/client' export default function FilterForm({ type }: { type?: ProjectViewType }) { - - const { customView, setCustomView, filter, setFilterValue } = useProjectViewContext() + const { customView, setCustomView, filter, setFilterValue } = + useProjectViewContext() const hidden = customView ? '' : 'hidden' const { date, point, priority, groupBy, statusIds } = filter - return <> -
- setCustomView(stt)} /> - Add some filter to make an unique view. -
- - -
- { - setFilterValue('date', val) - }} - width={200} - options={[ - { id: 'today', title: '📆 Today' }, - { id: 'yesterday', title: '📆 Yesterday' }, - { id: 'tomorrow', title: '📆 Tomorrow' }, - { id: 'prev-week', title: '📆 Prev week' }, - { id: 'prev-month', title: '📆 Prev month' }, - { id: 'this-week', title: '📆 This week' }, - { id: 'this-month', title: '📆 This month' }, - { id: 'next-week', title: '📆 Next week' }, - { id: 'next-month', title: '📆 Next month' }, - { id: 'not-set', title: '📆 Not set' }, - ]} - /> + console.log('type', type) - +
+ setCustomView(stt)} + /> + + Add some filter to make an unique view. + +
- value={point} - onChange={val => { - setFilterValue('point', val) - }} - zero={true} - infinite={true} /> +
+ { + setFilterValue('date', val) + }} + width={200} + options={[ + { id: 'today', title: '📆 Today' }, + { id: 'yesterday', title: '📆 Yesterday' }, + { id: 'tomorrow', title: '📆 Tomorrow' }, + { id: 'prev-week', title: '📆 Prev week' }, + { id: 'prev-month', title: '📆 Prev month' }, + { id: 'this-week', title: '📆 This week' }, + { id: 'this-month', title: '📆 This month' }, + { id: 'next-week', title: '📆 Next week' }, + { id: 'next-month', title: '📆 Next month' }, + { id: 'not-set', title: '📆 Not set' } + ]} + /> - { - setFilterValue('priority', val) - }} - /> - {type === ProjectViewType.CALENDAR ? - { - setFilterValue('statusIds', val) + { + setFilterValue('point', val) + }} + zero={true} + infinite={true} + /> - }} /> - : null} -
+ { + setFilterValue('priority', val) + }} + /> + {type === ProjectViewType.CALENDAR || type === ProjectViewType.BOARD ? ( + { + setFilterValue('statusIds', val) + }} + /> + ) : null} +
-
-
Group this by.
- { - setFilterValue('groupBy', val as ETaskFilterGroupByType) - }} - className="w-[150px] mr-1" - width={150} - options={[ - { - id: ETaskFilterGroupByType.STATUS, - title: 'Status', - icon: '🚦' - }, - { - id: ETaskFilterGroupByType.ASSIGNEE, - title: 'Assignees', - icon: '🤓' - }, - { - id: ETaskFilterGroupByType.PRIORITY, - title: 'Priority', - icon: '🚩' - } - // { id: ETaskFilterGroupByType.WEEK, title: 'Week', icon: '📅' } - ]} - /> -
- +
+
Group this by.
+ { + setFilterValue('groupBy', val as ETaskFilterGroupByType) + }} + className="w-[150px] mr-1" + width={150} + options={[ + { + id: ETaskFilterGroupByType.STATUS, + title: 'Status', + icon: '🚦' + }, + { + id: ETaskFilterGroupByType.ASSIGNEE, + title: 'Assignees', + icon: '🤓' + }, + { + id: ETaskFilterGroupByType.PRIORITY, + title: 'Priority', + icon: '🚩' + } + // { id: ETaskFilterGroupByType.WEEK, title: 'Week', icon: '📅' } + ]} + /> +
+ + ) } diff --git a/packages/ui-app/app/_features/Report/ReportContent.tsx b/packages/ui-app/app/_features/Report/ReportContent.tsx index fe005c64..d878eabb 100644 --- a/packages/ui-app/app/_features/Report/ReportContent.tsx +++ b/packages/ui-app/app/_features/Report/ReportContent.tsx @@ -5,13 +5,13 @@ import { useOrgMemberStore } from '@/store/orgMember' import { useProjectStore } from '@/store/project' import { extractDueDate } from '@shared/libs' import { useEffect, useState } from 'react' -import { useTaskFilter } from '../TaskFilter/context' import { ReportProvider } from './context' import { Task } from '@prisma/client' import ReportLayout from './ReportLayout' +import useTaskFilterContext from '../TaskFilter/useTaskFilterContext' export default function ReportContent() { - const { filter } = useTaskFilter() + const { filter } = useTaskFilterContext() const { projects } = useProjectStore() const { orgMembers } = useOrgMemberStore() const [tasks, setTasks] = useState([]) diff --git a/packages/ui-app/app/_features/TaskFilter/CalendarModeFilter.tsx b/packages/ui-app/app/_features/TaskFilter/CalendarModeFilter.tsx index 42940015..d2913444 100644 --- a/packages/ui-app/app/_features/TaskFilter/CalendarModeFilter.tsx +++ b/packages/ui-app/app/_features/TaskFilter/CalendarModeFilter.tsx @@ -3,11 +3,15 @@ import { useSearchParams } from 'next/navigation' import { useEffect } from 'react' import { HiChevronLeft, HiChevronRight } from 'react-icons/hi2' import { useTaskFilter } from './context' -import { ICalendarView, useCalendarContext } from '../../[orgID]/project/[projectId]/calendar/context' +import { + ICalendarView, + useCalendarContext +} from '../../[orgID]/project/[projectId]/calendar/context' import { getMonthList } from '@shared/libs' +import useTaskFilterContext from './useTaskFilterContext' const CalendarFilter = () => { - const { filter, setFilterValue } = useTaskFilter() + const { filter, setFilterValue } = useTaskFilterContext() const { month, setMonth } = useCalendarContext() const search = useSearchParams() const { setCalendarView } = useCalendarContext() @@ -36,20 +40,19 @@ const CalendarFilter = () => { return ( <> - -
+
{ setCalendarView(val as ICalendarView) }} width={150} options={[ { id: ICalendarView.WEEK, title: 'Week view' }, - { id: ICalendarView.MONTH, title: 'Month view' }, - ]} /> + { id: ICalendarView.MONTH, title: 'Month view' } + ]} + /> {/*
*/} {/* { {/* { id: 'ALL', title: 'All Task' }, */} {/* ]} /> */}
-
+
> } -const TaskFilterContext = createContext({ +export const TaskFilterContext = createContext({ groupByItems: [], groupByLoading: false, setGroupbyItems: () => { @@ -196,7 +196,6 @@ export const useTaskFilter = () => { } }) - if (noneItems.length) { groupStatuses.push({ id: 'NONE', @@ -248,7 +247,7 @@ export const useTaskFilter = () => { tasks.forEach(t => { if (ignored.includes(t.id)) return - if (!t.assigneeIds.length) { + if (!t.assigneeIds.length && !taskWithoutAssignee.includes(t.id)) { taskWithoutAssignee.push(t.id) return } @@ -356,6 +355,7 @@ export const useTaskFilter = () => { timeout = setTimeout(() => { if (oldGroupByType.current !== filter.groupBy) { + console.log('called groupby') updateGroupbyItems() oldGroupByType.current = filter.groupBy } diff --git a/packages/ui-app/app/_features/TaskFilter/index.tsx b/packages/ui-app/app/_features/TaskFilter/index.tsx index 755d312b..0a0b2dac 100644 --- a/packages/ui-app/app/_features/TaskFilter/index.tsx +++ b/packages/ui-app/app/_features/TaskFilter/index.tsx @@ -14,6 +14,7 @@ import { useProjectViewList } from '../ProjectView/useProjectViewList' import { ProjectViewType } from '@prisma/client' import StatusSelect from '@/components/StatusSelect' import StatusSelectMultiple from '@/components/StatusSelectMultiple' +import useTaskFilterContext from './useTaskFilterContext' let timeout = 0 interface ITaskFilterProps { @@ -29,7 +30,7 @@ export default function TaskFilter({ importEnable = true }: ITaskFilterProps) { const [txt, setTxt] = useState('') - const { filter, setFilterValue, updateGroupByFilter } = useTaskFilter() + const { filter, setFilterValue, updateGroupByFilter } = useTaskFilterContext() const { currentViewType } = useProjectViewList() const { @@ -45,6 +46,10 @@ export default function TaskFilter({ const isDateRange = date === 'date-range' const isCalendarMode = currentViewType === ProjectViewType.CALENDAR + const isTeamMode = currentViewType === ProjectViewType.TEAM + const isShowStatusFilter = + currentViewType === ProjectViewType.CALENDAR || + currentViewType === ProjectViewType.BOARD const showOperator = ['this-month', 'this-week', 'today'] useEffect(() => { @@ -53,7 +58,6 @@ export default function TaskFilter({ } timeout = setTimeout(() => { - setFilterValue('term', txt) }, 250) as unknown as number }, [txt]) @@ -146,9 +150,15 @@ export default function TaskFilter({ /> ) : null} - {isCalendarMode ? { - setFilterValue('statusIds', val) - }} /> : null} + {isShowStatusFilter ? ( + { + setFilterValue('statusIds', val) + }} + /> + ) : null} ) : null} - {isCalendarMode ? null : ( + {isCalendarMode || isTeamMode ? null : ( { diff --git a/packages/ui-app/app/_features/TaskFilter/useTaskFilterContext.ts b/packages/ui-app/app/_features/TaskFilter/useTaskFilterContext.ts new file mode 100644 index 00000000..762ec527 --- /dev/null +++ b/packages/ui-app/app/_features/TaskFilter/useTaskFilterContext.ts @@ -0,0 +1,120 @@ +import { useContext } from 'react' +import { + ETaskFilterGroupByType, + ITaskFilterFields, + TaskFilterContext +} from './context' +import { getLastDateOfMonth } from '@shared/libs' + +const d = new Date() +const firstDate = new Date(d.getFullYear(), d.getMonth(), 1) +const lastDate = getLastDateOfMonth(new Date()) + +const defaultFilter: ITaskFilterFields = { + term: '', + groupBy: ETaskFilterGroupByType.STATUS, + dateOperator: '=', + date: 'this-month', + startDate: firstDate, + endDate: lastDate, + point: '-1', + priority: 'ALL', + assigneeIds: ['ALL'], + statusIds: ['ALL'], + status: 'ALL' +} + +export default function useTaskFilterContext() { + const { + filter, + setFilter, + groupByItems, + setGroupbyItems, + groupByLoading, + setGroupbyLoading + } = useContext(TaskFilterContext) + + const setFilterValue = ( + name: keyof ITaskFilterFields, + val: string | string[] | Date | ETaskFilterGroupByType + ) => { + setFilter(filter => ({ ...filter, [name]: val })) + } + + const setDefaultFilter = () => { + setFilter(defaultFilter) + } + + const updateGroupByFilter = (val: ETaskFilterGroupByType) => { + if (val === filter.groupBy) return + setGroupbyLoading(true) + setFilterValue('groupBy', val) + } + + const setDateRangeByMonth = (month: string) => { + const today = new Date() + const lastDayOfMonth = new Date(today.getFullYear(), +month + 1, 0) + const startDayOfMonth = new Date(today.getFullYear(), +month, 1) + + setFilter(prev => ({ + ...prev, + date: 'date-range', + startDate: startDayOfMonth, + endDate: lastDayOfMonth + })) + } + + const swapGroupItemOrder = (sourceId: number, destId: number) => { + const cloned = structuredClone(groupByItems) + + const destItem = cloned[sourceId] + cloned.splice(sourceId, 1) + cloned.splice(destId, 0, destItem) + + setGroupbyItems(cloned) + } + + const swapTaskOrder = ( + dropId: string, + sourceIndex: number, + destIndex: number + ) => { + const cloned = structuredClone(groupByItems) + const groupItem = cloned.find(c => c.id === dropId) + + if (!groupItem) return + + const items = groupItem.items + + // const destItem = items[sourceIndex] + // items.splice(sourceIndex, 1) + // items.splice(destIndex, 0, destItem) + + const [removed] = items.splice(sourceIndex, 1) + items.splice(destIndex, 0, removed) + + setGroupbyItems(cloned) + } + + const isGroupbyStatus = filter.groupBy === ETaskFilterGroupByType.STATUS + const isGroupbyAssignee = filter.groupBy === ETaskFilterGroupByType.ASSIGNEE + const isGroupbyPriority = filter.groupBy === ETaskFilterGroupByType.PRIORITY + + return { + groupBy: filter.groupBy, + groupByLoading, + groupByItems, + setGroupbyItems, + swapTaskOrder, + swapGroupItemOrder, + filter, + setFilter, + setDefaultFilter, + setFilterValue, + isGroupbyStatus, + updateGroupByFilter, + setDateRangeByMonth, + isGroupbyAssignee, + isGroupbyPriority + } +} diff --git a/packages/ui-app/app/_features/TaskFilter/useTodoFilter.ts b/packages/ui-app/app/_features/TaskFilter/useTodoFilter.ts index 0242edfa..1d088b50 100644 --- a/packages/ui-app/app/_features/TaskFilter/useTodoFilter.ts +++ b/packages/ui-app/app/_features/TaskFilter/useTodoFilter.ts @@ -1,11 +1,11 @@ import { useSearchParams } from 'next/navigation' -import { useTaskFilter } from './context' import { useUser } from '@goalie/nextjs' import { useDebounce } from '@/hooks/useDebounce' +import useTaskFilterContext from './useTaskFilterContext' export const useTodoFilter = () => { - const { setFilter } = useTaskFilter() + const { setFilter } = useTaskFilterContext() const sp = useSearchParams() const { user } = useUser() diff --git a/packages/ui-app/app/_features/TaskFilter/useUpdateGroupbyItem.ts b/packages/ui-app/app/_features/TaskFilter/useUpdateGroupbyItem.ts new file mode 100644 index 00000000..feda2ef7 --- /dev/null +++ b/packages/ui-app/app/_features/TaskFilter/useUpdateGroupbyItem.ts @@ -0,0 +1,245 @@ +import { TaskPriority } from '@prisma/client' +import { + ETaskFilterGroupByType, + ITaskFilterGroupbyItem, + TaskFilterContext +} from './context' +import { useProjectStatusStore } from '@/store/status' +import { useMemberStore } from '@/store/member' +import { useTaskStore } from '@/store/task' +import { useParams } from 'next/navigation' +import { useContext, useEffect, useRef } from 'react' + +let timeout = 0 +export default function useUpdateGroupbyItem() { + const { statuses } = useProjectStatusStore() + const { members } = useMemberStore() + const { tasks } = useTaskStore() + const { projectId } = useParams() + + const oldGroupByType = useRef('') + const oldStatusList = useRef(statuses) + const oldTaskList = useRef(tasks) + + const { filter, setGroupbyItems, setGroupbyLoading } = + useContext(TaskFilterContext) + + const _groupByStatus = (): ITaskFilterGroupbyItem[] => { + const ignored: string[] = [] + const statusIds = statuses.map(s => s.id) + const noneItems: string[] = [] + + const groupStatuses = statuses.map(stt => { + const { id, name, color } = stt + const items: string[] = [] + + tasks.forEach(t => { + if (ignored.includes(t.id)) return + + const { taskStatusId } = t + + if (taskStatusId === id) { + items.push(t.id) + ignored.push(t.id) + return + } + + if (!statusIds.includes(taskStatusId || '')) { + noneItems.push(t.id) + ignored.push(t.id) + return + } + + if (!taskStatusId || !statusIds.includes(taskStatusId)) { + noneItems.push(t.id) + + ignored.push(t.id) + } + }) + + return { + id, + color, + name, + items + } + }) + + if (noneItems.length) { + groupStatuses.push({ + id: 'NONE', + color: '#cecece', + name: 'None', + items: noneItems + }) + } + + return groupStatuses + } + + const _groupByPriority = (): ITaskFilterGroupbyItem[] => { + const priorities = [ + [TaskPriority.LOW, '#ababab'], + [TaskPriority.NORMAL, '#13cfff'], + [TaskPriority.HIGH, '#ffce37'], + [TaskPriority.URGENT, '#ff1345'] + ] + + const filteredStatusIds = filter.statusIds + + const ignored: string[] = [] + return priorities.map(p => { + const items: string[] = [] + + tasks.forEach(t => { + if (ignored.includes(t.id)) return + + if ( + filteredStatusIds.length && + t.taskStatusId && + !filteredStatusIds.includes('ALL') + ) { + if (!filteredStatusIds.includes(t.taskStatusId)) { + return + } + } + + if (t.priority === p[0]) { + items.push(t.id) + ignored.push(t.id) + } + }) + + return { + id: p[0], + name: p[0], + color: p[1], + items + } + }) + } + + const _groupByAssignee = (): ITaskFilterGroupbyItem[] => { + const ignored: string[] = [] + const taskWithoutAssignee: string[] = [] + const filteredStatusIds = filter.statusIds + + const newMembers = members.map(mem => { + const items: string[] = [] + + tasks.forEach(t => { + if (ignored.includes(t.id)) return + + if ( + t.taskStatusId && + filteredStatusIds.length && + !filteredStatusIds.includes('ALL') + ) { + if (!filteredStatusIds.includes(t.taskStatusId)) { + return + } + } + + if (!t.assigneeIds.length && !taskWithoutAssignee.includes(t.id)) { + taskWithoutAssignee.push(t.id) + return + } + + if (t.assigneeIds.includes(mem.id)) { + items.push(t.id) + ignored.push(t.id) + } + }) + return { + id: mem.id, + name: mem.name || '', + icon: mem.photo || '', + items + } + }) + + newMembers.push({ + id: 'NONE', + name: 'Not assigned', + icon: '', + items: taskWithoutAssignee + }) + + return newMembers + } + + const updateGroupbyItems = () => { + let groupItems: ITaskFilterGroupbyItem[] = [] + + switch (filter.groupBy) { + case ETaskFilterGroupByType.STATUS: + groupItems = _groupByStatus() + break + + case ETaskFilterGroupByType.PRIORITY: + groupItems = _groupByPriority() + break + + case ETaskFilterGroupByType.ASSIGNEE: + groupItems = _groupByAssignee() + break + } + + setGroupbyLoading(false) + setGroupbyItems(groupItems) + } + + // Only update groupByItems as groupBy option changed + // keep logic simple + useEffect(() => { + if (timeout) { + clearTimeout(timeout) + } + + timeout = setTimeout(() => { + if (oldGroupByType.current !== filter.groupBy) { + updateGroupbyItems() + oldGroupByType.current = filter.groupBy + } + }, 350) as unknown as number + }, [ + filter.groupBy, + JSON.stringify(members), + JSON.stringify(statuses), + JSON.stringify(tasks) + ]) + + useEffect(() => { + updateGroupbyItems() + }, [filter.statusIds.toString()]) + + useEffect(() => { + if (oldStatusList.current) { + const oldStatusArr = oldStatusList.current + + // When page reload, the status list is empty + // after a few seconds it will be fetched from servers + // so we need to update the groupByItems + if (!oldStatusArr.length && statuses.length) { + updateGroupbyItems() + } + } + }, [statuses]) + + useEffect(() => { + if (oldTaskList.current) { + const oldTaskArr = oldTaskList.current + + // When page reload, the task list is empty + // after a few seconds it will be fetched from servers + // so we need to update the groupByItems + if (!oldTaskArr.length && tasks.length) { + updateGroupbyItems() + } + } + }, [tasks]) + + useEffect(() => { + updateGroupbyItems() + }, [projectId, tasks]) +} diff --git a/packages/ui-app/services/task.ts b/packages/ui-app/services/task.ts index c1fe1aab..72f68c0d 100644 --- a/packages/ui-app/services/task.ts +++ b/packages/ui-app/services/task.ts @@ -28,7 +28,6 @@ export interface ITaskQuery { } export const taskGetByCond = (query: ITaskQuery, signal?: AbortSignal) => { - console.log('task get by cond', query) return httpGet(`/api/project/task/query`, { params: query, signal: signal @@ -95,10 +94,7 @@ export const taskMakeCover = (data: { } export const serviceTask = { - reorder: (data: { - updatedOrder: [string, number][] - projectId: string - }) => { + reorder: (data: { updatedOrder: [string, number][]; projectId: string }) => { return httpPost('/api/task/reorder', data) } }