From 8782782ff049c8da1a59a74264f2fa4254358c78 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Fri, 8 Mar 2024 02:37:22 +0500 Subject: [PATCH 01/17] fix(kanban): working on kanban section --- apps/web/app/[locale]/kanban/page.tsx | 2 +- apps/web/app/hooks/features/useKanban.ts | 2 +- .../app/hooks/features/useTeamKanbanUpdate.ts | 52 ++ apps/web/app/hooks/features/useTeamTasks.ts | 2 +- .../pages/kanban/create-task-modal.tsx | 23 + apps/web/lib/components/Kanban.tsx | 13 +- apps/web/lib/components/image-overlapper.tsx | 6 +- apps/web/lib/components/kanban-card.tsx | 6 +- .../lib/features/task/task-input-kanban.tsx | 484 ++++++++++++++++++ 9 files changed, 582 insertions(+), 8 deletions(-) create mode 100644 apps/web/app/hooks/features/useTeamKanbanUpdate.ts create mode 100644 apps/web/components/pages/kanban/create-task-modal.tsx create mode 100644 apps/web/lib/features/task/task-input-kanban.tsx diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 0310c07e5..2029d6e0c 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -21,7 +21,7 @@ import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; import { userTimezone } from '@app/helpers'; const Kanban = () => { - const { data } = useKanban(); + const { data, } = useKanban(); const { activeTeam } = useOrganizationTeams(); const t = useTranslations(); diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index c38e4a465..0d1434435 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -10,7 +10,7 @@ export function useKanban() { const [loading, setLoading] = useState(true); const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); const taskStatusHook = useTaskStatus(); - const { tasks, tasksFetching, updateTask } = useTeamTasks(); + const { tasks, tasksFetching, updateTask, } = useTeamTasks(); /** * format data for kanban board */ diff --git a/apps/web/app/hooks/features/useTeamKanbanUpdate.ts b/apps/web/app/hooks/features/useTeamKanbanUpdate.ts new file mode 100644 index 000000000..ef097ef3b --- /dev/null +++ b/apps/web/app/hooks/features/useTeamKanbanUpdate.ts @@ -0,0 +1,52 @@ +'use client'; + +/* eslint-disable no-mixed-spaces-and-tabs */ + +import { getTeamTasksAPI } from '@app/services/client/api'; +import { activeTeamState } from '@app/stores'; +import { teamTasksState } from '@app/stores'; +import { useCallback } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useQuery } from '../useQuery'; +import { useSyncRef } from '../useSyncRef'; +import { useAuthenticateUser } from './useAuthenticateUser'; + +export function useTeamTasksMutate() { + const { user } = useAuthenticateUser(); + + const setAllTasks = useSetRecoilState(teamTasksState); + + const activeTeam = useRecoilValue(activeTeamState); + const activeTeamRef = useSyncRef(activeTeam); + + // Queries hooks + const { queryCall, loadingRef } = useQuery(getTeamTasksAPI); + + const loadTeamTasksData = useCallback(async () => { + console.log("mutate.rendar") + // if (loadingRef.current || !user || !activeTeamRef.current?.id) { + // return new Promise((response) => { + // response(true); + // }); + // } + + const res = await queryCall( + user?.employee.organizationId, + user?.employee.tenantId, + activeTeamRef.current?.projects && activeTeamRef.current?.projects.length + ? activeTeamRef.current?.projects[0].id + : '', + activeTeamRef.current?.id || '' + ); + setAllTasks(res?.data?.items); + return res; + }, [loadingRef, user, activeTeamRef, queryCall, setAllTasks]); + + // Global loading state + // Reload tasks after active team changed + // Queries calls + + return { + loadTeamTasksData + }; +} diff --git a/apps/web/app/hooks/features/useTeamTasks.ts b/apps/web/app/hooks/features/useTeamTasks.ts index 238fe0fa6..2b83ec20f 100644 --- a/apps/web/app/hooks/features/useTeamTasks.ts +++ b/apps/web/app/hooks/features/useTeamTasks.ts @@ -57,7 +57,7 @@ export function useTeamTasks() { const { firstLoad, firstLoadData: firstLoadTasksData } = useFirstLoad(); // Queries hooks - const { queryCall, loading, loadingRef } = useQuery(getTeamTasksAPI); + const { queryCall, loading, loadingRef, } = useQuery(getTeamTasksAPI); const { queryCall: getTasksByIdQueryCall, loading: getTasksByIdLoading } = useQuery(getTasksByIdAPI); const { queryCall: getTasksByEmployeeIdQueryCall, loading: getTasksByEmployeeIdLoading } = useQuery(getTasksByEmployeeIdAPI); diff --git a/apps/web/components/pages/kanban/create-task-modal.tsx b/apps/web/components/pages/kanban/create-task-modal.tsx new file mode 100644 index 000000000..a7333efb1 --- /dev/null +++ b/apps/web/components/pages/kanban/create-task-modal.tsx @@ -0,0 +1,23 @@ +import { TaskInputKanban } from 'lib/features/task/task-input-kanban'; +import React from 'react'; + +const CreateTaskModal = (props: { task: any; initEditMode: boolean; tasks: any; title: string }) => { + return ( +
+ { + console.log(e); + }} + /> +
+ ); +}; + +export default CreateTaskModal; diff --git a/apps/web/lib/components/Kanban.tsx b/apps/web/lib/components/Kanban.tsx index aa8f24c4a..70b077aa1 100644 --- a/apps/web/lib/components/Kanban.tsx +++ b/apps/web/lib/components/Kanban.tsx @@ -19,6 +19,9 @@ import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover' import { Button } from '@components/ui/button'; import { useTranslations } from 'next-intl'; import { AddIcon } from 'assets/svg'; +import { useModal } from '@app/hooks'; +import { Modal } from './modal'; +import CreateTaskModal from '@components/pages/kanban/create-task-modal'; const grid = 8; @@ -329,6 +332,8 @@ const KanbanDraggable = ({ addNewTask: (value: ITeamTask, status: string) => void; }) => { const t = useTranslations(); + const { isOpen, closeModal, openModal } = useModal(); + // return ( <> @@ -360,7 +365,10 @@ const KanbanDraggable = ({ type={'TASK'} content={items} /> - @@ -371,6 +379,9 @@ const KanbanDraggable = ({ )} )} + + + ); }; diff --git a/apps/web/lib/components/image-overlapper.tsx b/apps/web/lib/components/image-overlapper.tsx index 08dfec156..494a64e6e 100644 --- a/apps/web/lib/components/image-overlapper.tsx +++ b/apps/web/lib/components/image-overlapper.tsx @@ -42,7 +42,11 @@ export default function ImageOverlapper({ className="absolute hover:!z-20 transition-all hover:scale-110" style={{ zIndex: index + 1, left: index * 30, top: isMoreThanDisplay ? -8 : -16 }} > - + {`${image.alt} - {item.issueType && ( + {/* {item.issueType && (
- )} + )} */} diff --git a/apps/web/lib/features/task/task-input-kanban.tsx b/apps/web/lib/features/task/task-input-kanban.tsx new file mode 100644 index 000000000..8edd52310 --- /dev/null +++ b/apps/web/lib/features/task/task-input-kanban.tsx @@ -0,0 +1,484 @@ +'use client'; + +import { + HostKeys, + RTuseTaskInput, + useAuthenticateUser, + useCallbackRef, + useHotkeys, + useOrganizationEmployeeTeams, + useOrganizationTeams, + useOutsideClick, + useTaskInput, + useTaskStatus +} from '@app/hooks'; +import { ITaskPriority, ITaskSize, ITeamTask, Nullable } from '@app/interfaces'; +import { timerStatusState } from '@app/stores'; +import { clsxm } from '@app/utils'; +import { PlusIcon } from '@heroicons/react/20/solid'; +import { Button, Card, Divider, InputField, SpinnerLoader, Tooltip } from 'lib/components'; +import { MutableRefObject, PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { TaskIssuesDropdown } from './task-issue'; +import { TaskItem } from './task-item'; +import { ActiveTaskPropertiesDropdown, ActiveTaskSizesDropdown } from './task-status'; +import { useTranslations } from 'next-intl'; +import { useTeamTasksMutate } from '@app/hooks/features/useTeamKanbanUpdate'; + +type Props = { + task?: Nullable; + tasks?: ITeamTask[]; + kanbanTitle?: string; + onTaskClick?: (task: ITeamTask) => void; + initEditMode?: boolean; + onCloseCombobox?: () => void; + inputLoader?: boolean; + onEnterKey?: (taskName: string, task: ITeamTask) => void; + keepOpen?: boolean; + loadingRef?: MutableRefObject; + closeable_fc?: () => void; + viewType?: 'input-trigger' | 'one-view'; + createOnEnterClick?: boolean; + showTaskNumber?: boolean; + showCombobox?: boolean; + autoAssignTaskAuth?: boolean; + fullWidthCombobox?: boolean; + fullHeightCombobox?: boolean; + placeholder?: string; + autoFocus?: boolean; + autoInputSelectText?: boolean; + usersTaskCreatedAssignTo?: { id: string }[]; + onTaskCreated?: (task: ITeamTask | undefined) => void; + cardWithoutShadow?: boolean; + + forParentChildRelationship?: boolean; +} & PropsWithChildren; + +/** + * If task passed then some function should not considered as global state + * + * @param param0 + * @returns + */ + +export function TaskInputKanban(props: Props) { + const t = useTranslations(); + + const { viewType = 'input-trigger', showTaskNumber = false, showCombobox = true } = props; + + const datas = useTaskInput({ + task: props.task, + initEditMode: props.initEditMode, + tasks: props.tasks + }); + + const { updateOrganizationTeamEmployee } = useOrganizationEmployeeTeams(); + const { activeTeam } = useOrganizationTeams(); + const { user } = useAuthenticateUser(); + + const onCloseComboboxRef = useCallbackRef(props.onCloseCombobox); + const closeable_fcRef = useCallbackRef(props.closeable_fc); + const $onTaskClick = useCallbackRef(props.onTaskClick); + const $onTaskCreated = useCallbackRef(props.onTaskCreated); + const inputRef = useRef(null); + const timerStatus = useRecoilValue(timerStatusState); + const timerRunningStatus = useMemo(() => { + return Boolean(timerStatus?.running); + }, [timerStatus]); + + const onTaskCreated = useCallback( + (task: ITeamTask | undefined) => $onTaskCreated.current && $onTaskCreated.current(task), + [$onTaskCreated] + ); + + const onTaskClick = useCallback( + (task: ITeamTask) => $onTaskClick.current && $onTaskClick.current(task), + [$onTaskClick] + ); + + const { inputTask, editMode, setEditMode, setQuery, updateLoading, updateTaskTitleHandler, setFilter, taskIssue } = + datas; + + const inputTaskTitle = useMemo(() => inputTask?.title || '', [inputTask?.title]); + + const [taskName, setTaskName] = useState(''); + + const { targetEl, ignoreElementRef } = useOutsideClick( + () => !props.keepOpen && setEditMode(false) + ); + + useEffect(() => { + setQuery(taskName === inputTask?.title ? '' : taskName); + }, [taskName, inputTask, setQuery]); + + useEffect(() => { + setTaskName(inputTaskTitle); + }, [editMode, inputTaskTitle]); + + useEffect(() => { + /** + * Call onCloseCombobox only when the menu has been closed + */ + !editMode && onCloseComboboxRef.current && onCloseComboboxRef.current(); + }, [editMode, onCloseComboboxRef]); + + /** + * set the active task for the authenticated user + */ + const setAuthActiveTask = useCallback( + (task: ITeamTask) => { + if (datas.setActiveTask) { + datas.setActiveTask(task); + + // Update Current user's active task to sync across multiple devices + const currentEmployeeDetails = activeTeam?.members.find( + (member) => member.employeeId === user?.employee?.id + ); + if (currentEmployeeDetails && currentEmployeeDetails.id) { + updateOrganizationTeamEmployee(currentEmployeeDetails.id, { + organizationId: task.organizationId, + activeTaskId: task.id, + organizationTeamId: activeTeam?.id, + tenantId: activeTeam?.tenantId + }); + } + } + setEditMode(false); + }, + [datas, setEditMode, activeTeam, user, updateOrganizationTeamEmployee] + ); + + /** + * On update task name + */ + const updateTaskNameHandler = useCallback( + (task: ITeamTask, title: string) => { + if (task.title !== title) { + !updateLoading && updateTaskTitleHandler(task, title); + } + }, + [updateLoading, updateTaskTitleHandler] + ); + + /** + * Signle parent about updating and close event (that can trigger close component e.g) + */ + useEffect(() => { + if (props.loadingRef?.current && !updateLoading) { + closeable_fcRef.current && closeable_fcRef.current(); + } + + if (props.loadingRef) { + props.loadingRef.current = updateLoading; + } + }, [updateLoading, props.loadingRef, closeable_fcRef]); + + /* Setting the filter to open when the edit mode is true. */ + useEffect(() => { + editMode && setFilter('open'); + }, [editMode, setFilter]); + + /* + If task is passed then we don't want to set the active task for the authenticated user. + after task creation + */ + const autoActiveTask = props.task !== undefined ? false : true; + const handleTaskCreation = useCallback(async () => { + /* Checking if the `handleTaskCreation` is available and if the `hasCreateForm` is true. */ + datas && + datas.handleTaskCreation && + datas.hasCreateForm && + datas + .handleTaskCreation({ + autoActiveTask, + autoAssignTaskAuth: props.autoAssignTaskAuth, + assignToUsers: props.usersTaskCreatedAssignTo || [] + }) + ?.then(onTaskCreated) + .finally(async () => { + setTaskName(''); + }); + }, [datas, autoActiveTask, props.autoAssignTaskAuth, props.usersTaskCreatedAssignTo, onTaskCreated]); + + let updatedTaskList: ITeamTask[] = []; + if (props.forParentChildRelationship) { + if ( + // Story can have ParentId set to Epic ID + props.task?.issueType === 'Story' + ) { + updatedTaskList = datas.filteredTasks.filter((item) => item.issueType === 'Epic'); + } else if ( + // TASK|BUG can have ParentId to be set either to Story ID or Epic ID + props.task?.issueType === 'Task' || + props.task?.issueType === 'Bug' || + !props.task?.issueType + ) { + updatedTaskList = datas.filteredTasks.filter( + (item) => item.issueType === 'Epic' || item.issueType === 'Story' + ); + } else { + updatedTaskList = datas.filteredTasks; + } + + if (props.task?.children && props.task?.children?.length) { + const childrenTaskIds = props.task?.children?.map((item) => item.id); + updatedTaskList = updatedTaskList.filter((item) => !childrenTaskIds.includes(item.id)); + } + } + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (inputRef.current && !inputRef.current.contains(event.target as Node) && editMode) { + inputTask && updateTaskNameHandler(inputTask, taskName); + // console.log('func active'); + } + }; + + // Attach the event listener + document.addEventListener('mousedown', handleClickOutside); + + // Clean up the event listener on component unmount + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [inputTask, taskName, updateTaskNameHandler, editMode]); + + // Handling Hotkeys + const handleCommandKeySequence = useCallback(() => { + if (!editMode) { + setEditMode(true); + if (targetEl.current) { + targetEl.current.focus(); + } + } else { + setEditMode(false); + } + }, [setEditMode, editMode, targetEl]); + useHotkeys(HostKeys.CREATE_TASK, handleCommandKeySequence); + + useEffect(() => { + if (props.autoFocus && targetEl.current) { + targetEl.current.focus(); + } + }, [props.autoFocus, targetEl]); + + const inputField = ( + { + setEditMode(true); + props.autoInputSelectText && setTimeout(() => e?.target?.select(), 10); + }} + onChange={(event) => { + setTaskName(event.target.value); + }} + onKeyUp={(e) => { + if (e.key === 'Enter' && inputTask) { + /* If createOnEnterClick is false then updateTaskNameHandler is called. */ + !props.createOnEnterClick && updateTaskNameHandler(inputTask, taskName); + + props.onEnterKey && props.onEnterKey(taskName, inputTask); + } + /* Creating a new task when the enter key is pressed. */ + if (e.key === 'Enter') { + props.createOnEnterClick && handleTaskCreation(); + } + }} + trailingNode={ + /* Showing the spinner when the task is being updated. */ +
+ {props.task ? ( + (updateLoading || props.inputLoader) && + ) : ( + <>{updateLoading && } + )} +
+ } + className={clsxm( + showTaskNumber && inputTask && ['pl-2'], + 'dark:bg-[#1B1D22]', + props.initEditMode && 'h-10' + )} + /* Showing the task number. */ + leadingNode={ + // showTaskNumber && + // inputTask && +
+ { + taskIssue.current = v; + }} + /> +
+ } + /> + ); + + const taskCard = ( + + ); + + return taskCard; +} + +/** + * A component that is used to render the task list. + */ +function TaskCard({ + datas, + onItemClick, + inputField, + kanbanTitle, + handleTaskCreation, + forParentChildRelationship +}: { + datas: Partial; + onItemClick?: (task: ITeamTask) => void; + inputField?: JSX.Element; + kanbanTitle: string; + fullWidth?: boolean; + fullHeight?: boolean; + handleTaskCreation: () => void; + cardWithoutShadow?: boolean; + forParentChildRelationship?: boolean; + updatedTaskList?: ITeamTask[]; +}) { + const t = useTranslations(); + const activeTaskEl = useRef(null); + const { loadTeamTasksData } = useTeamTasksMutate(); + + const { taskStatus, taskPriority, taskSize, taskDescription } = datas; + useEffect(() => { + if (taskStatus) { + taskStatus.current = kanbanTitle ?? 'open'; + } + }, [taskStatus, datas.hasCreateForm]); + useEffect(() => { + if (datas.editMode) { + window.setTimeout(() => { + activeTaskEl?.current?.scrollIntoView({ + block: 'nearest', + inline: 'start' + }); + }, 10); + } + }, [datas.editMode]); + const taskStatusHook = useTaskStatus(); + + return ( + <> + + {inputField} +
+ {/* Create team button */} + +
+ {datas.hasCreateForm && ( +
+ { + if (taskDescription) { + taskDescription.current = e.target.value; + } + }} + className={'dark:bg-[#1B1D22]'} + /> + +
+ { + if (v && taskPriority) { + taskPriority.current = v; + } + }} + defaultValue={taskPriority?.current as ITaskPriority} + task={null} + /> + + { + if (v && taskSize) { + taskSize.current = v; + } + }} + defaultValue={taskSize?.current as ITaskSize} + task={null} + /> +
+
+ )} + + + + +
+
+ + + {!forParentChildRelationship && + datas.filteredTasks?.map((task, i) => { + const last = (datas.filteredTasks?.length || 0) - 1 === i; + const active = datas.inputTask === task; + + return ( +
  • + + + {!last && } +
  • + ); + })} +
    + + {/* Just some spaces at the end */} +
    {'|'}
    + + ); +} From 280edd0049ddf733bc4762efcff7be15f371aa55 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Sat, 9 Mar 2024 22:22:27 +0500 Subject: [PATCH 02/17] [Fix]: added the kanban create task --- apps/web/app/[locale]/kanban/page.tsx | 2 +- apps/web/app/hooks/features/useKanban.ts | 2 +- .../pages/kanban/create-task-modal.tsx | 23 + apps/web/lib/components/Kanban.tsx | 20 +- apps/web/lib/components/image-overlapper.tsx | 6 +- apps/web/lib/components/kanban-card.tsx | 5 +- .../lib/features/task/task-input-kanban.tsx | 484 ++++++++++++++++++ 7 files changed, 527 insertions(+), 15 deletions(-) create mode 100644 apps/web/components/pages/kanban/create-task-modal.tsx create mode 100644 apps/web/lib/features/task/task-input-kanban.tsx diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 0310c07e5..2029d6e0c 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -21,7 +21,7 @@ import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; import { userTimezone } from '@app/helpers'; const Kanban = () => { - const { data } = useKanban(); + const { data, } = useKanban(); const { activeTeam } = useOrganizationTeams(); const t = useTranslations(); diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index c38e4a465..0d1434435 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -10,7 +10,7 @@ export function useKanban() { const [loading, setLoading] = useState(true); const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); const taskStatusHook = useTaskStatus(); - const { tasks, tasksFetching, updateTask } = useTeamTasks(); + const { tasks, tasksFetching, updateTask, } = useTeamTasks(); /** * format data for kanban board */ diff --git a/apps/web/components/pages/kanban/create-task-modal.tsx b/apps/web/components/pages/kanban/create-task-modal.tsx new file mode 100644 index 000000000..a7333efb1 --- /dev/null +++ b/apps/web/components/pages/kanban/create-task-modal.tsx @@ -0,0 +1,23 @@ +import { TaskInputKanban } from 'lib/features/task/task-input-kanban'; +import React from 'react'; + +const CreateTaskModal = (props: { task: any; initEditMode: boolean; tasks: any; title: string }) => { + return ( +
    + { + console.log(e); + }} + /> +
    + ); +}; + +export default CreateTaskModal; diff --git a/apps/web/lib/components/Kanban.tsx b/apps/web/lib/components/Kanban.tsx index aa8f24c4a..a53d588df 100644 --- a/apps/web/lib/components/Kanban.tsx +++ b/apps/web/lib/components/Kanban.tsx @@ -19,6 +19,9 @@ import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover' import { Button } from '@components/ui/button'; import { useTranslations } from 'next-intl'; import { AddIcon } from 'assets/svg'; +import { useModal } from '@app/hooks'; +import { Modal } from './modal'; +import CreateTaskModal from '@components/pages/kanban/create-task-modal'; const grid = 8; @@ -135,9 +138,6 @@ export const KanbanDroppable = ({ {(dropProvided: DroppableProvided, dropSnapshot: DroppableStateSnapshot) => (
    @@ -371,6 +372,9 @@ const KanbanDraggable = ({ )} )} + + + ); }; diff --git a/apps/web/lib/components/image-overlapper.tsx b/apps/web/lib/components/image-overlapper.tsx index 08dfec156..494a64e6e 100644 --- a/apps/web/lib/components/image-overlapper.tsx +++ b/apps/web/lib/components/image-overlapper.tsx @@ -42,7 +42,11 @@ export default function ImageOverlapper({ className="absolute hover:!z-20 transition-all hover:scale-110" style={{ zIndex: index + 1, left: index * 30, top: isMoreThanDisplay ? -8 : -16 }} > - + {`${image.alt}
    diff --git a/apps/web/lib/features/task/task-input-kanban.tsx b/apps/web/lib/features/task/task-input-kanban.tsx new file mode 100644 index 000000000..8edd52310 --- /dev/null +++ b/apps/web/lib/features/task/task-input-kanban.tsx @@ -0,0 +1,484 @@ +'use client'; + +import { + HostKeys, + RTuseTaskInput, + useAuthenticateUser, + useCallbackRef, + useHotkeys, + useOrganizationEmployeeTeams, + useOrganizationTeams, + useOutsideClick, + useTaskInput, + useTaskStatus +} from '@app/hooks'; +import { ITaskPriority, ITaskSize, ITeamTask, Nullable } from '@app/interfaces'; +import { timerStatusState } from '@app/stores'; +import { clsxm } from '@app/utils'; +import { PlusIcon } from '@heroicons/react/20/solid'; +import { Button, Card, Divider, InputField, SpinnerLoader, Tooltip } from 'lib/components'; +import { MutableRefObject, PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { TaskIssuesDropdown } from './task-issue'; +import { TaskItem } from './task-item'; +import { ActiveTaskPropertiesDropdown, ActiveTaskSizesDropdown } from './task-status'; +import { useTranslations } from 'next-intl'; +import { useTeamTasksMutate } from '@app/hooks/features/useTeamKanbanUpdate'; + +type Props = { + task?: Nullable; + tasks?: ITeamTask[]; + kanbanTitle?: string; + onTaskClick?: (task: ITeamTask) => void; + initEditMode?: boolean; + onCloseCombobox?: () => void; + inputLoader?: boolean; + onEnterKey?: (taskName: string, task: ITeamTask) => void; + keepOpen?: boolean; + loadingRef?: MutableRefObject; + closeable_fc?: () => void; + viewType?: 'input-trigger' | 'one-view'; + createOnEnterClick?: boolean; + showTaskNumber?: boolean; + showCombobox?: boolean; + autoAssignTaskAuth?: boolean; + fullWidthCombobox?: boolean; + fullHeightCombobox?: boolean; + placeholder?: string; + autoFocus?: boolean; + autoInputSelectText?: boolean; + usersTaskCreatedAssignTo?: { id: string }[]; + onTaskCreated?: (task: ITeamTask | undefined) => void; + cardWithoutShadow?: boolean; + + forParentChildRelationship?: boolean; +} & PropsWithChildren; + +/** + * If task passed then some function should not considered as global state + * + * @param param0 + * @returns + */ + +export function TaskInputKanban(props: Props) { + const t = useTranslations(); + + const { viewType = 'input-trigger', showTaskNumber = false, showCombobox = true } = props; + + const datas = useTaskInput({ + task: props.task, + initEditMode: props.initEditMode, + tasks: props.tasks + }); + + const { updateOrganizationTeamEmployee } = useOrganizationEmployeeTeams(); + const { activeTeam } = useOrganizationTeams(); + const { user } = useAuthenticateUser(); + + const onCloseComboboxRef = useCallbackRef(props.onCloseCombobox); + const closeable_fcRef = useCallbackRef(props.closeable_fc); + const $onTaskClick = useCallbackRef(props.onTaskClick); + const $onTaskCreated = useCallbackRef(props.onTaskCreated); + const inputRef = useRef(null); + const timerStatus = useRecoilValue(timerStatusState); + const timerRunningStatus = useMemo(() => { + return Boolean(timerStatus?.running); + }, [timerStatus]); + + const onTaskCreated = useCallback( + (task: ITeamTask | undefined) => $onTaskCreated.current && $onTaskCreated.current(task), + [$onTaskCreated] + ); + + const onTaskClick = useCallback( + (task: ITeamTask) => $onTaskClick.current && $onTaskClick.current(task), + [$onTaskClick] + ); + + const { inputTask, editMode, setEditMode, setQuery, updateLoading, updateTaskTitleHandler, setFilter, taskIssue } = + datas; + + const inputTaskTitle = useMemo(() => inputTask?.title || '', [inputTask?.title]); + + const [taskName, setTaskName] = useState(''); + + const { targetEl, ignoreElementRef } = useOutsideClick( + () => !props.keepOpen && setEditMode(false) + ); + + useEffect(() => { + setQuery(taskName === inputTask?.title ? '' : taskName); + }, [taskName, inputTask, setQuery]); + + useEffect(() => { + setTaskName(inputTaskTitle); + }, [editMode, inputTaskTitle]); + + useEffect(() => { + /** + * Call onCloseCombobox only when the menu has been closed + */ + !editMode && onCloseComboboxRef.current && onCloseComboboxRef.current(); + }, [editMode, onCloseComboboxRef]); + + /** + * set the active task for the authenticated user + */ + const setAuthActiveTask = useCallback( + (task: ITeamTask) => { + if (datas.setActiveTask) { + datas.setActiveTask(task); + + // Update Current user's active task to sync across multiple devices + const currentEmployeeDetails = activeTeam?.members.find( + (member) => member.employeeId === user?.employee?.id + ); + if (currentEmployeeDetails && currentEmployeeDetails.id) { + updateOrganizationTeamEmployee(currentEmployeeDetails.id, { + organizationId: task.organizationId, + activeTaskId: task.id, + organizationTeamId: activeTeam?.id, + tenantId: activeTeam?.tenantId + }); + } + } + setEditMode(false); + }, + [datas, setEditMode, activeTeam, user, updateOrganizationTeamEmployee] + ); + + /** + * On update task name + */ + const updateTaskNameHandler = useCallback( + (task: ITeamTask, title: string) => { + if (task.title !== title) { + !updateLoading && updateTaskTitleHandler(task, title); + } + }, + [updateLoading, updateTaskTitleHandler] + ); + + /** + * Signle parent about updating and close event (that can trigger close component e.g) + */ + useEffect(() => { + if (props.loadingRef?.current && !updateLoading) { + closeable_fcRef.current && closeable_fcRef.current(); + } + + if (props.loadingRef) { + props.loadingRef.current = updateLoading; + } + }, [updateLoading, props.loadingRef, closeable_fcRef]); + + /* Setting the filter to open when the edit mode is true. */ + useEffect(() => { + editMode && setFilter('open'); + }, [editMode, setFilter]); + + /* + If task is passed then we don't want to set the active task for the authenticated user. + after task creation + */ + const autoActiveTask = props.task !== undefined ? false : true; + const handleTaskCreation = useCallback(async () => { + /* Checking if the `handleTaskCreation` is available and if the `hasCreateForm` is true. */ + datas && + datas.handleTaskCreation && + datas.hasCreateForm && + datas + .handleTaskCreation({ + autoActiveTask, + autoAssignTaskAuth: props.autoAssignTaskAuth, + assignToUsers: props.usersTaskCreatedAssignTo || [] + }) + ?.then(onTaskCreated) + .finally(async () => { + setTaskName(''); + }); + }, [datas, autoActiveTask, props.autoAssignTaskAuth, props.usersTaskCreatedAssignTo, onTaskCreated]); + + let updatedTaskList: ITeamTask[] = []; + if (props.forParentChildRelationship) { + if ( + // Story can have ParentId set to Epic ID + props.task?.issueType === 'Story' + ) { + updatedTaskList = datas.filteredTasks.filter((item) => item.issueType === 'Epic'); + } else if ( + // TASK|BUG can have ParentId to be set either to Story ID or Epic ID + props.task?.issueType === 'Task' || + props.task?.issueType === 'Bug' || + !props.task?.issueType + ) { + updatedTaskList = datas.filteredTasks.filter( + (item) => item.issueType === 'Epic' || item.issueType === 'Story' + ); + } else { + updatedTaskList = datas.filteredTasks; + } + + if (props.task?.children && props.task?.children?.length) { + const childrenTaskIds = props.task?.children?.map((item) => item.id); + updatedTaskList = updatedTaskList.filter((item) => !childrenTaskIds.includes(item.id)); + } + } + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (inputRef.current && !inputRef.current.contains(event.target as Node) && editMode) { + inputTask && updateTaskNameHandler(inputTask, taskName); + // console.log('func active'); + } + }; + + // Attach the event listener + document.addEventListener('mousedown', handleClickOutside); + + // Clean up the event listener on component unmount + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [inputTask, taskName, updateTaskNameHandler, editMode]); + + // Handling Hotkeys + const handleCommandKeySequence = useCallback(() => { + if (!editMode) { + setEditMode(true); + if (targetEl.current) { + targetEl.current.focus(); + } + } else { + setEditMode(false); + } + }, [setEditMode, editMode, targetEl]); + useHotkeys(HostKeys.CREATE_TASK, handleCommandKeySequence); + + useEffect(() => { + if (props.autoFocus && targetEl.current) { + targetEl.current.focus(); + } + }, [props.autoFocus, targetEl]); + + const inputField = ( + { + setEditMode(true); + props.autoInputSelectText && setTimeout(() => e?.target?.select(), 10); + }} + onChange={(event) => { + setTaskName(event.target.value); + }} + onKeyUp={(e) => { + if (e.key === 'Enter' && inputTask) { + /* If createOnEnterClick is false then updateTaskNameHandler is called. */ + !props.createOnEnterClick && updateTaskNameHandler(inputTask, taskName); + + props.onEnterKey && props.onEnterKey(taskName, inputTask); + } + /* Creating a new task when the enter key is pressed. */ + if (e.key === 'Enter') { + props.createOnEnterClick && handleTaskCreation(); + } + }} + trailingNode={ + /* Showing the spinner when the task is being updated. */ +
    + {props.task ? ( + (updateLoading || props.inputLoader) && + ) : ( + <>{updateLoading && } + )} +
    + } + className={clsxm( + showTaskNumber && inputTask && ['pl-2'], + 'dark:bg-[#1B1D22]', + props.initEditMode && 'h-10' + )} + /* Showing the task number. */ + leadingNode={ + // showTaskNumber && + // inputTask && +
    + { + taskIssue.current = v; + }} + /> +
    + } + /> + ); + + const taskCard = ( + + ); + + return taskCard; +} + +/** + * A component that is used to render the task list. + */ +function TaskCard({ + datas, + onItemClick, + inputField, + kanbanTitle, + handleTaskCreation, + forParentChildRelationship +}: { + datas: Partial; + onItemClick?: (task: ITeamTask) => void; + inputField?: JSX.Element; + kanbanTitle: string; + fullWidth?: boolean; + fullHeight?: boolean; + handleTaskCreation: () => void; + cardWithoutShadow?: boolean; + forParentChildRelationship?: boolean; + updatedTaskList?: ITeamTask[]; +}) { + const t = useTranslations(); + const activeTaskEl = useRef(null); + const { loadTeamTasksData } = useTeamTasksMutate(); + + const { taskStatus, taskPriority, taskSize, taskDescription } = datas; + useEffect(() => { + if (taskStatus) { + taskStatus.current = kanbanTitle ?? 'open'; + } + }, [taskStatus, datas.hasCreateForm]); + useEffect(() => { + if (datas.editMode) { + window.setTimeout(() => { + activeTaskEl?.current?.scrollIntoView({ + block: 'nearest', + inline: 'start' + }); + }, 10); + } + }, [datas.editMode]); + const taskStatusHook = useTaskStatus(); + + return ( + <> + + {inputField} +
    + {/* Create team button */} + +
    + {datas.hasCreateForm && ( +
    + { + if (taskDescription) { + taskDescription.current = e.target.value; + } + }} + className={'dark:bg-[#1B1D22]'} + /> + +
    + { + if (v && taskPriority) { + taskPriority.current = v; + } + }} + defaultValue={taskPriority?.current as ITaskPriority} + task={null} + /> + + { + if (v && taskSize) { + taskSize.current = v; + } + }} + defaultValue={taskSize?.current as ITaskSize} + task={null} + /> +
    +
    + )} + + + + +
    +
    + + + {!forParentChildRelationship && + datas.filteredTasks?.map((task, i) => { + const last = (datas.filteredTasks?.length || 0) - 1 === i; + const active = datas.inputTask === task; + + return ( +
  • + + + {!last && } +
  • + ); + })} +
    + + {/* Just some spaces at the end */} +
    {'|'}
    + + ); +} From 08354605830eeefa9ef3754222395cf575c777ce Mon Sep 17 00:00:00 2001 From: anishali2 Date: Sat, 9 Mar 2024 22:24:43 +0500 Subject: [PATCH 03/17] [Fix]: added the kanban create task --- apps/web/lib/features/task/task-input-kanban.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/web/lib/features/task/task-input-kanban.tsx b/apps/web/lib/features/task/task-input-kanban.tsx index 8edd52310..3cc9af903 100644 --- a/apps/web/lib/features/task/task-input-kanban.tsx +++ b/apps/web/lib/features/task/task-input-kanban.tsx @@ -23,7 +23,6 @@ import { TaskIssuesDropdown } from './task-issue'; import { TaskItem } from './task-item'; import { ActiveTaskPropertiesDropdown, ActiveTaskSizesDropdown } from './task-status'; import { useTranslations } from 'next-intl'; -import { useTeamTasksMutate } from '@app/hooks/features/useTeamKanbanUpdate'; type Props = { task?: Nullable; @@ -64,7 +63,7 @@ type Props = { export function TaskInputKanban(props: Props) { const t = useTranslations(); - const { viewType = 'input-trigger', showTaskNumber = false, showCombobox = true } = props; + const { viewType = 'input-trigger', showTaskNumber = false } = props; const datas = useTaskInput({ task: props.task, @@ -363,14 +362,13 @@ function TaskCard({ }) { const t = useTranslations(); const activeTaskEl = useRef(null); - const { loadTeamTasksData } = useTeamTasksMutate(); const { taskStatus, taskPriority, taskSize, taskDescription } = datas; useEffect(() => { if (taskStatus) { taskStatus.current = kanbanTitle ?? 'open'; } - }, [taskStatus, datas.hasCreateForm]); + }, [taskStatus, datas.hasCreateForm, kanbanTitle]); useEffect(() => { if (datas.editMode) { window.setTimeout(() => { From cf557e30e670d92b451b23ab94873dd0147d7180 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Sat, 9 Mar 2024 22:27:28 +0500 Subject: [PATCH 04/17] [Fix]: added the kanban create task --- .../app/hooks/features/useTeamKanbanUpdate.ts | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 apps/web/app/hooks/features/useTeamKanbanUpdate.ts diff --git a/apps/web/app/hooks/features/useTeamKanbanUpdate.ts b/apps/web/app/hooks/features/useTeamKanbanUpdate.ts deleted file mode 100644 index ef097ef3b..000000000 --- a/apps/web/app/hooks/features/useTeamKanbanUpdate.ts +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -/* eslint-disable no-mixed-spaces-and-tabs */ - -import { getTeamTasksAPI } from '@app/services/client/api'; -import { activeTeamState } from '@app/stores'; -import { teamTasksState } from '@app/stores'; -import { useCallback } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { useQuery } from '../useQuery'; -import { useSyncRef } from '../useSyncRef'; -import { useAuthenticateUser } from './useAuthenticateUser'; - -export function useTeamTasksMutate() { - const { user } = useAuthenticateUser(); - - const setAllTasks = useSetRecoilState(teamTasksState); - - const activeTeam = useRecoilValue(activeTeamState); - const activeTeamRef = useSyncRef(activeTeam); - - // Queries hooks - const { queryCall, loadingRef } = useQuery(getTeamTasksAPI); - - const loadTeamTasksData = useCallback(async () => { - console.log("mutate.rendar") - // if (loadingRef.current || !user || !activeTeamRef.current?.id) { - // return new Promise((response) => { - // response(true); - // }); - // } - - const res = await queryCall( - user?.employee.organizationId, - user?.employee.tenantId, - activeTeamRef.current?.projects && activeTeamRef.current?.projects.length - ? activeTeamRef.current?.projects[0].id - : '', - activeTeamRef.current?.id || '' - ); - setAllTasks(res?.data?.items); - return res; - }, [loadingRef, user, activeTeamRef, queryCall, setAllTasks]); - - // Global loading state - // Reload tasks after active team changed - // Queries calls - - return { - loadTeamTasksData - }; -} From 2c576d3633a67c7858bb8865e547cbe8d5e2da6d Mon Sep 17 00:00:00 2001 From: anishali2 Date: Sat, 9 Mar 2024 22:29:04 +0500 Subject: [PATCH 05/17] [Fix]: added the kanban create task --- apps/web/lib/components/kanban-card.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/components/kanban-card.tsx b/apps/web/lib/components/kanban-card.tsx index 961c773fe..0247f3dc8 100644 --- a/apps/web/lib/components/kanban-card.tsx +++ b/apps/web/lib/components/kanban-card.tsx @@ -262,14 +262,14 @@ export default function Item(props: ItemProps) { )}
    - {/* {item.issueType && ( -
    + {item.issueType && ( +
    - )} */} + )}
    From 512cc356a55ab2efa51b31e258a36eb7f1124969 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Sat, 9 Mar 2024 22:43:15 +0500 Subject: [PATCH 06/17] [Fix]: added the kanban create task --- apps/web/lib/features/task/task-input-kanban.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/lib/features/task/task-input-kanban.tsx b/apps/web/lib/features/task/task-input-kanban.tsx index 3cc9af903..c29741f0c 100644 --- a/apps/web/lib/features/task/task-input-kanban.tsx +++ b/apps/web/lib/features/task/task-input-kanban.tsx @@ -327,7 +327,7 @@ export function TaskInputKanban(props: Props) { inputField={viewType ? inputField : undefined} fullWidth={props.fullWidthCombobox} fullHeight={props.fullHeightCombobox} - kanbanTitle={props?.kanbanTitle ?? 'open'} + kanbanTitle={props.kanbanTitle ?? 'open'} handleTaskCreation={handleTaskCreation} cardWithoutShadow={props.cardWithoutShadow} updatedTaskList={updatedTaskList} From 224268cbd55288d283ce1eea2422fd19754ba995 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Sun, 10 Mar 2024 21:39:06 +0500 Subject: [PATCH 07/17] [Fix]: fix the kanban scroll again --- apps/web/app/[locale]/kanban/page.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 0310c07e5..9a3a2227e 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -58,8 +58,8 @@ const Kanban = () => { <>
    -
    -
    +
    +
    @@ -68,7 +68,7 @@ const Kanban = () => {
    -
    +

    {t('common.KANBAN')} {t('common.BOARD')}

    @@ -179,7 +179,7 @@ const Kanban = () => {
    {/*
    */}
    -
    +
    {/** TODO:fetch teamtask based on days */} {activeTab && ( // add filter for today, yesterday and tomorrow
    From e0e5dbac96af1e88852b8e863de1a9929566b1fb Mon Sep 17 00:00:00 2001 From: anishali2 Date: Tue, 12 Mar 2024 03:06:45 +0500 Subject: [PATCH 08/17] [fix]: added search and not found --- .vscode/settings.json | 6 ++ apps/web/app/[locale]/kanban/page.tsx | 90 +++++-------------- apps/web/app/hooks/features/useKanban.ts | 33 +++++-- .../components/pages/kanban/search-bar.tsx | 54 +++++++++++ apps/web/lib/components/Kanban.tsx | 46 ++++++---- apps/web/lib/features/task/task-filters.tsx | 2 +- .../lib/features/team-members-kanban-view.tsx | 3 +- 7 files changed, 142 insertions(+), 92 deletions(-) create mode 100644 apps/web/components/pages/kanban/search-bar.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index ba689581a..16a5eb284 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,6 +35,12 @@ "**/node_modules": true, "**/bower_components": true, "**/*.code-search": true, + "**/apps/mobile/**":true, + "**/apps/desktop/**":true, + "**/apps/extensions/**":true, + "**/apps/server-api/**":true, + "**/apps/server-web/**":true, + "**/web/components/**": true }, "docwriter.style": "Auto-detect" diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 9a3a2227e..d45055729 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -5,7 +5,7 @@ import { useAuthenticateUser, useModal, useOrganizationTeams } from '@app/hooks' import { useKanban } from '@app/hooks/features/useKanban'; import KanbanBoardSkeleton from '@components/shared/skeleton/KanbanBoardSkeleton'; import { withAuthentication } from 'lib/app/authenticator'; -import { Breadcrumb, Button, InputField } from 'lib/components'; +import { Breadcrumb } from 'lib/components'; import { KanbanView } from 'lib/features/team-members-kanban-view'; import { MainLayout } from 'lib/layout'; import { useState } from 'react'; @@ -13,16 +13,15 @@ import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; import ImageComponent, { ImageOverlapperProps } from 'lib/components/image-overlapper'; import Separator from '@components/ui/separator'; -import { clsxm } from '@app/utils'; import HeaderTabs from '@components/pages/main/header-tabs'; -import { AddIcon, SearchNormalIcon, SettingFilterIcon, PeoplesIcon } from 'assets/svg'; -import { Select, SelectContent, SelectItem, SelectTrigger } from '@components/ui/select'; +import { AddIcon, PeoplesIcon } from 'assets/svg'; import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; import { userTimezone } from '@app/helpers'; +import KanbanSearch from '@components/pages/kanban/search-bar'; +// import { TaskPropertiesDropdown, TaskSizesDropdown } from 'lib/features'; const Kanban = () => { - const { data } = useKanban(); - + const { data, setSearchTasks, searchTasks, isLoading } = useKanban(); const { activeTeam } = useOrganizationTeams(); const t = useTranslations(); const params = useParams<{ locale: string }>(); @@ -58,7 +57,7 @@ const Kanban = () => { <>
    -
    +
    @@ -115,76 +114,35 @@ const Kanban = () => { ))}
    - - - -
    - + {/*
    + hook.onChangeStatusFilter('priority', values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + />
    -
    - - - - } +
    + hook.onChangeStatusFilter('size', values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} />
    +
    + +
    */} + +
    {/*
    */}
    -
    +
    {/** TODO:fetch teamtask based on days */} {activeTab && ( // add filter for today, yesterday and tomorrow
    {Object.keys(data).length > 0 ? ( - + ) : ( )} diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index c38e4a465..8844c9fa5 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -5,19 +5,37 @@ import { useEffect, useState } from 'react'; import { ITaskStatusItemList, ITeamTask } from '@app/interfaces'; import { useTeamTasks } from './useTeamTasks'; import { IKanban } from '@app/interfaces/IKanban'; +import { useUserProfilePage } from './useUserProfilePage'; +import { useTaskFilter } from 'lib/features'; export function useKanban() { + const profile = useUserProfilePage(); + const hook = useTaskFilter(profile); const [loading, setLoading] = useState(true); + const [searchTasks, setSearchTasks] = useState(''); const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); const taskStatusHook = useTaskStatus(); - const { tasks, tasksFetching, updateTask } = useTeamTasks(); - /** - * format data for kanban board - */ + const { tasks: newTa, tasksFetching, updateTask } = useTeamTasks(); + console.log('sss', newTa); + useEffect(() => { if (!taskStatusHook.loading && !tasksFetching) { let kanban = {}; + setLoading(true); + const priority = hook.statusFilter.priority; + const status = hook.statusFilter.status; + const tasks = newTa + .filter((task: ITeamTask) => { + return task.title.toLowerCase().includes(searchTasks.toLowerCase()); + }) + // if (Array.isArray(priority) && priority.length > 0) { + // } + // if (Array.isArray(status) && status.length > 0) { + // tasks.filter((task: ITeamTask) => { + // return status.includes(task.status); + // }); + // } const getTasksByStatus = (status: string | undefined) => { return tasks.filter((task: ITeamTask) => { return task.status === status; @@ -34,7 +52,7 @@ export function useKanban() { setLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [taskStatusHook.loading, tasksFetching]); + }, [taskStatusHook.loading, tasksFetching, newTa, searchTasks, hook.statusFilter]); /** * collapse or show kanban column @@ -81,12 +99,15 @@ export function useKanban() { return { data: kanbanBoard as IKanban, isLoading: loading, + hook, columns: taskStatusHook.taskStatus, + searchTasks, updateKanbanBoard: setKanbanBoard, updateTaskStatus: updateTask, toggleColumn, isColumnCollapse, reorderStatus, - addNewTask + addNewTask, + setSearchTasks }; } diff --git a/apps/web/components/pages/kanban/search-bar.tsx b/apps/web/components/pages/kanban/search-bar.tsx new file mode 100644 index 000000000..edb015f63 --- /dev/null +++ b/apps/web/components/pages/kanban/search-bar.tsx @@ -0,0 +1,54 @@ +import { Button, InputField } from 'lib/components'; +import { useState, useEffect, useRef } from 'react'; +import { SearchNormalIcon } from 'assets/svg'; +import { useTranslations } from 'next-intl'; + +const KanbanSearch = ({ + setSearchTasks, + searchTasks +}: { + setSearchTasks: (value: string) => void; + searchTasks: string; +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const searchRef: any = useRef(null); + const t = useTranslations(); + + // Handle click outside search box to collapse + const handleClickOutside = (event: any) => { + if (searchRef.current && !searchRef.current.contains(event.target)) { + setIsExpanded(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + // Handle click on search box to expand + const handleSearchClick = () => { + setIsExpanded(true); + }; + + return ( +
    handleSearchClick()} ref={searchRef}> + setSearchTasks(target.value)} + placeholder={t('common.SEARCH')} + className={`mb-0 h-10 transition-all ${isExpanded ? 'w-64' : 'w-44'} !bg-transparent`} + leadingNode={ + + } + /> +
    + ); +}; + +export default KanbanSearch; diff --git a/apps/web/lib/components/Kanban.tsx b/apps/web/lib/components/Kanban.tsx index aa8f24c4a..2946e1928 100644 --- a/apps/web/lib/components/Kanban.tsx +++ b/apps/web/lib/components/Kanban.tsx @@ -19,6 +19,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover' import { Button } from '@components/ui/button'; import { useTranslations } from 'next-intl'; import { AddIcon } from 'assets/svg'; +import Skeleton from 'react-loading-skeleton'; const grid = 8; @@ -122,33 +123,39 @@ export const KanbanDroppable = ({ title, droppableId, type, + isLoading, content }: { title: string; + isLoading: boolean; droppableId: string; type: string; content: ITeamTask[]; }) => { return ( <> - - {(dropProvided: DroppableProvided, dropSnapshot: DroppableStateSnapshot) => ( -
    - -
    - )} -
    + {content.length > 0 ? ( + + {(dropProvided: DroppableProvided, dropSnapshot: DroppableStateSnapshot) => ( +
    + +
    + )} +
    + ) : ( +
    + {isLoading ? ( + + ) : ( + 'not found!' + )} +
    + )} ); }; @@ -319,11 +326,13 @@ const KanbanDraggableHeader = ({ const KanbanDraggable = ({ index, title, + isLoading, items, backgroundColor }: { index: number; title: string; + isLoading: boolean; backgroundColor: any; items: ITeamTask[]; addNewTask: (value: ITeamTask, status: string) => void; @@ -355,6 +364,7 @@ const KanbanDraggable = ({
    { +export const KanbanView = ({ kanbanBoardTasks, isLoading }: { kanbanBoardTasks: IKanban; isLoading: boolean }) => { const { data: items, columns: kanbanColumns, @@ -211,6 +211,7 @@ export const KanbanView = ({ kanbanBoardTasks }: { kanbanBoardTasks: IKanban }) <> Date: Tue, 12 Mar 2024 03:07:34 +0500 Subject: [PATCH 09/17] [fix]: added search and not found --- apps/web/app/hooks/features/useKanban.ts | 5 ++--- apps/web/lib/features/team/user-team-card/index.tsx | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index 8844c9fa5..7981d1b2a 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -16,14 +16,13 @@ export function useKanban() { const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); const taskStatusHook = useTaskStatus(); const { tasks: newTa, tasksFetching, updateTask } = useTeamTasks(); - console.log('sss', newTa); useEffect(() => { if (!taskStatusHook.loading && !tasksFetching) { let kanban = {}; setLoading(true); - const priority = hook.statusFilter.priority; - const status = hook.statusFilter.status; + // const priority = hook.statusFilter.priority; + // const status = hook.statusFilter.status; const tasks = newTa .filter((task: ITeamTask) => { return task.title.toLowerCase().includes(searchTasks.toLowerCase()); diff --git a/apps/web/lib/features/team/user-team-card/index.tsx b/apps/web/lib/features/team/user-team-card/index.tsx index d736791b9..bc9685060 100644 --- a/apps/web/lib/features/team/user-team-card/index.tsx +++ b/apps/web/lib/features/team/user-team-card/index.tsx @@ -41,8 +41,7 @@ export function UserTeamCard({ onDragStart = () => null, onDragEnd = () => null, onDragEnter = () => null, - onDragOver = () => null, - currentExit = false + onDragOver = () => null }: IUserTeamCard) { const t = useTranslations(); const memberInfo = useTeamMemberCard(member); From e4766fb25ee580c27376035351c68537d0c2744e Mon Sep 17 00:00:00 2001 From: anishali2 Date: Tue, 12 Mar 2024 03:12:01 +0500 Subject: [PATCH 10/17] [fix]: added search and not found --- .vscode/settings.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 16a5eb284..ba689581a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,12 +35,6 @@ "**/node_modules": true, "**/bower_components": true, "**/*.code-search": true, - "**/apps/mobile/**":true, - "**/apps/desktop/**":true, - "**/apps/extensions/**":true, - "**/apps/server-api/**":true, - "**/apps/server-web/**":true, - "**/web/components/**": true }, "docwriter.style": "Auto-detect" From cd1ed02aaf043f9ed769cc2bc0bf9d40912d82fd Mon Sep 17 00:00:00 2001 From: anishali2 Date: Tue, 12 Mar 2024 23:46:43 +0500 Subject: [PATCH 11/17] added filteration of kanban section --- apps/web/app/[locale]/kanban/page.tsx | 59 +++++++++++------------- apps/web/app/hooks/features/useKanban.ts | 21 +++++---- apps/web/lib/components/kanban-card.tsx | 30 ++++-------- 3 files changed, 46 insertions(+), 64 deletions(-) diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 3158ff760..b4b255dc7 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -1,11 +1,7 @@ 'use client'; import { KanbanTabs } from '@app/constants'; -import { - useAuthenticateUser, - useModal, - useOrganizationTeams -} from '@app/hooks'; +import { useAuthenticateUser, useModal, useOrganizationTeams } from '@app/hooks'; import { useKanban } from '@app/hooks/features/useKanban'; import KanbanBoardSkeleton from '@components/shared/skeleton/KanbanBoardSkeleton'; import { withAuthentication } from 'lib/app/authenticator'; @@ -15,19 +11,18 @@ import { MainLayout } from 'lib/layout'; import { useState } from 'react'; import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; -import ImageComponent, { - ImageOverlapperProps -} from 'lib/components/image-overlapper'; +import ImageComponent, { ImageOverlapperProps } from 'lib/components/image-overlapper'; import Separator from '@components/ui/separator'; import HeaderTabs from '@components/pages/main/header-tabs'; import { AddIcon, PeoplesIcon } from 'assets/svg'; import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; import { userTimezone } from '@app/helpers'; import KanbanSearch from '@components/pages/kanban/search-bar'; -// import { TaskPropertiesDropdown, TaskSizesDropdown } from 'lib/features'; +import { TaskPropertiesDropdown, TaskSizesDropdown } from 'lib/features'; const Kanban = () => { - const { data, setSearchTasks, searchTasks, isLoading } = useKanban(); + const { data, setSearchTasks, searchTasks, isLoading, setPriority, setSizes } = useKanban(); + const { activeTeam } = useOrganizationTeams(); const t = useTranslations(); const params = useParams<{ locale: string }>(); @@ -39,26 +34,25 @@ const Kanban = () => { { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` } ]; - const activeTeamMembers = activeTeam?.members ? activeTeam.members : []; - - const teamMembers: ImageOverlapperProps[] = []; + const activeTeamMembers = activeTeam?.members ? activeTeam.members : []; - activeTeamMembers.map((member: any) => { - teamMembers.push({ - id: member.employee.user.id, - url: member.employee.user.imageUrl, - alt: member.employee.user.firstName - }); - }); - const tabs = [ - { name: t('common.TODAY'), value: KanbanTabs.TODAY }, - { name: t('common.YESTERDAY'), value: KanbanTabs.YESTERDAY }, - { name: t('common.TOMORROW'), value: KanbanTabs.TOMORROW } - ]; - const { user } = useAuthenticateUser(); - const { openModal, isOpen, closeModal } = useModal(); - const timezone = userTimezone(); + const teamMembers: ImageOverlapperProps[] = []; + activeTeamMembers.map((member: any) => { + teamMembers.push({ + id: member.employee.user.id, + url: member.employee.user.imageUrl, + alt: member.employee.user.firstName + }); + }); + const tabs = [ + { name: t('common.TODAY'), value: KanbanTabs.TODAY }, + { name: t('common.YESTERDAY'), value: KanbanTabs.YESTERDAY }, + { name: t('common.TOMORROW'), value: KanbanTabs.TOMORROW } + ]; + const { user } = useAuthenticateUser(); + const { openModal, isOpen, closeModal } = useModal(); + const timezone = userTimezone(); return ( <> @@ -120,23 +114,23 @@ const Kanban = () => { ))}
    - {/*
    +
    hook.onChangeStatusFilter('priority', values || [])} + onValueChange={(_, values) => setPriority(values || [])} className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" multiple={true} />
    hook.onChangeStatusFilter('size', values || [])} + onValueChange={(_, values) => setSizes(values || [])} className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" multiple={true} />
    -
    */} +
    @@ -159,7 +153,6 @@ const Kanban = () => { ); - }; export default withAuthentication(Kanban, { displayName: 'Kanban' }); diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index 53fcd9ef8..44203b35d 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -5,28 +5,28 @@ import { useEffect, useState } from 'react'; import { ITaskStatusItemList, ITeamTask } from '@app/interfaces'; import { useTeamTasks } from './useTeamTasks'; import { IKanban } from '@app/interfaces/IKanban'; -import { useUserProfilePage } from './useUserProfilePage'; -import { useTaskFilter } from 'lib/features'; - export function useKanban() { - const profile = useUserProfilePage(); - const hook = useTaskFilter(profile); const [loading, setLoading] = useState(true); const [searchTasks, setSearchTasks] = useState(''); const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); const taskStatusHook = useTaskStatus(); const { tasks: newTask, tasksFetching, updateTask } = useTeamTasks(); - + const [priority, setPriority] = useState([]); + const [sizes, setSizes] = useState([]); useEffect(() => { if (!taskStatusHook.loading && !tasksFetching) { let kanban = {}; setLoading(true); - // const priority = hook.statusFilter.priority; - // const status = hook.statusFilter.status; const tasks = newTask .filter((task: ITeamTask) => { return task.title.toLowerCase().includes(searchTasks.toLowerCase()); }) + .filter((task: ITeamTask) => { + return priority.length ? priority.includes(task.priority) : true; + }) + .filter((task: ITeamTask) => { + return sizes.length ? sizes.includes(task.size) : true; + }); const getTasksByStatus = (status: string | undefined) => { return tasks.filter((task: ITeamTask) => { return task.status === status; @@ -43,7 +43,7 @@ export function useKanban() { setLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [taskStatusHook.loading, tasksFetching, newTask, searchTasks, hook.statusFilter]); + }, [taskStatusHook.loading, tasksFetching, newTask, searchTasks, priority, sizes]); /** * collapse or show kanban column @@ -90,9 +90,10 @@ export function useKanban() { return { data: kanbanBoard as IKanban, isLoading: loading, - hook, columns: taskStatusHook.taskStatus, searchTasks, + setPriority, + setSizes, updateKanbanBoard: setKanbanBoard, updateTaskStatus: updateTask, toggleColumn, diff --git a/apps/web/lib/components/kanban-card.tsx b/apps/web/lib/components/kanban-card.tsx index 0247f3dc8..cdbe018bc 100644 --- a/apps/web/lib/components/kanban-card.tsx +++ b/apps/web/lib/components/kanban-card.tsx @@ -1,6 +1,6 @@ import { DraggableProvided } from 'react-beautiful-dnd'; import PriorityIcon from '@components/ui/svgs/priority-icon'; -import { ITeamTask, Tag } from '@app/interfaces'; +import { ITaskPriority, ITeamTask, Tag } from '@app/interfaces'; import { useAuthenticateUser, useCollaborative, @@ -81,14 +81,17 @@ function TagList({ tags }: { tags: Tag[] }) { ); } -function Priority({ level }: { level: number }) { - const numberArray = Array.from({ length: level }, (_, index) => index + 1); +function Priority({ level }: { level: ITaskPriority }) { + const levelSmallCase = level.toString().toLowerCase(); + const levelIntoNumber = + levelSmallCase === 'low' ? 1 : levelSmallCase === 'medium' ? 2 : levelSmallCase === 'high' ? 3 : 4; + const numberArray = Array.from({ length: levelIntoNumber }, (_, index) => index + 1); return ( <>
    @@ -153,12 +156,7 @@ export default function Item(props: ItemProps) { const { collaborativeSelect } = useCollaborative(memberInfo.memberUser); const menu = <>{!collaborativeSelect && }; - const progress = getEstimation( - null, - item, - totalWorkedTasksTimer || 1, - item.estimate || 0 - ); + const progress = getEstimation(null, item, totalWorkedTasksTimer || 1, item.estimate || 0); const currentMember = activeTeam?.members.find((member) => member.id === memberInfo.member?.id || item?.id); const { h, m, s } = secondsToTime( @@ -202,17 +200,7 @@ export default function Item(props: ItemProps) { #{item.number} {item.title} - + {item.priority && }
    From 8cfb40d94d447705cde58e7135f77274ac88362e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:52:42 +0000 Subject: [PATCH 12/17] chore(deps): bump follow-redirects from 1.15.4 to 1.15.6 in /apps/mobile Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- apps/mobile/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/mobile/yarn.lock b/apps/mobile/yarn.lock index 64bcc9ff4..099182d3a 100644 --- a/apps/mobile/yarn.lock +++ b/apps/mobile/yarn.lock @@ -7163,9 +7163,9 @@ flush-write-stream@^1.0.0: readable-stream "^2.3.6" follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.4.1: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== fontfaceobserver@^2.1.0: version "2.3.0" From a677e4ce7787c3506b3f2e9a25fc5a5488bab9b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:56:31 +0000 Subject: [PATCH 13/17] chore(deps): bump follow-redirects from 1.15.4 to 1.15.6 Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- apps/mobile/yarn.lock | 6 +++--- yarn.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/mobile/yarn.lock b/apps/mobile/yarn.lock index 64bcc9ff4..099182d3a 100644 --- a/apps/mobile/yarn.lock +++ b/apps/mobile/yarn.lock @@ -7163,9 +7163,9 @@ flush-write-stream@^1.0.0: readable-stream "^2.3.6" follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.4.1: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== fontfaceobserver@^2.1.0: version "2.3.0" diff --git a/yarn.lock b/yarn.lock index db436f401..1167c21a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12413,9 +12413,9 @@ flatted@^3.2.9: integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== follow-redirects@^1.0.0, follow-redirects@^1.15.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-each@^0.3.3: version "0.3.3" From 46e2e7782eddc98af90023de6868e45917f46783 Mon Sep 17 00:00:00 2001 From: Anish Date: Sun, 17 Mar 2024 18:47:32 +0500 Subject: [PATCH 14/17] [Fix]: fix the header tabs of home-pages (#2302) * [Fix]: fix the header tabs of home-pages * [Fix]: fix the header tabs of home-pages * Update team-members.tsx * fix(Table): added table border and name changed * fix(Table): added table border and name changed --------- Co-authored-by: Ruslan K --- apps/web/app/[locale]/page-component.tsx | 30 +-- .../web/components/pages/main/header-tabs.tsx | 6 +- apps/web/components/ui/data-table.tsx | 53 +++-- apps/web/lib/components/kanban-card.tsx | 2 +- .../features/task/task-all-status-type.tsx | 2 +- apps/web/lib/features/team-member-cell.tsx | 13 +- .../lib/features/team-members-block-view.tsx | 116 +++++----- .../lib/features/team-members-card-view.tsx | 202 +++++++++--------- .../lib/features/team-members-table-view.tsx | 11 +- .../team/user-team-card/task-info.tsx | 4 +- .../user-team-card/user-team-card-menu.tsx | 2 +- 11 files changed, 237 insertions(+), 204 deletions(-) diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 2a2e1a857..0841dfee7 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -8,20 +8,11 @@ import { clsxm } from '@app/utils'; import NoTeam from '@components/pages/main/no-team'; import { withAuthentication } from 'lib/app/authenticator'; import { Breadcrumb, Card, Container } from 'lib/components'; -import { - AuthUserTaskInput, - TeamInvitations, - TeamMembers, - Timer, - UnverifiedEmail, - UserTeamCardHeader, - UserTeamBlockHeader -} from 'lib/features'; +import { AuthUserTaskInput, TeamInvitations, TeamMembers, Timer, UnverifiedEmail } from 'lib/features'; import { MainHeader, MainLayout } from 'lib/layout'; import { IssuesView } from '@app/constants'; import { useNetworkState } from '@uidotdev/usehooks'; import Offline from '@components/pages/offline'; -import UserTeamTableHeader from 'lib/features/team/user-team-table/user-team-table-header'; import { useTranslations } from 'next-intl'; import { Analytics } from '@vercel/analytics/react'; @@ -84,25 +75,12 @@ function MainPage() { -
    + {/*
    */} - {isTeamMember ? : null} - {view === IssuesView.CARDS && isTeamMember ? ( - - ) : view === IssuesView.BLOCKS ? ( - - ) : view === IssuesView.TABLE ? ( - - ) : null} + {isTeamMember ? : null} + {isTeamMember ? : } - {/* Divider */} -
    -
    - - - {isTeamMember ? : } - diff --git a/apps/web/components/pages/main/header-tabs.tsx b/apps/web/components/pages/main/header-tabs.tsx index 9a85e06ba..942b8783b 100644 --- a/apps/web/components/pages/main/header-tabs.tsx +++ b/apps/web/components/pages/main/header-tabs.tsx @@ -29,7 +29,11 @@ const HeaderTabs = ({ linkAll, kanban = false }: { linkAll: boolean; kanban?: bo activeView === optionView && 'bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-100' )} - onClick={() => setView(optionView)} + onClick={() => { + if (links[index] !== '/kanban') { + setView(optionView); + } + }} > { columns: ColumnDef[]; @@ -43,6 +44,10 @@ function DataTable({ columns, data, footerRows, isHeader }: DataT rowSelection, columnFilters }, + defaultColumn: { + // Let's set up our default column filter UI + size: 20 + }, enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, @@ -55,19 +60,28 @@ function DataTable({ columns, data, footerRows, isHeader }: DataT getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues() }); - return ( - +
    {isHeader && ( - + {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { + + {headerGroup.headers.map((header, index) => { + const tooltip: any = header.column.columnDef; + const isTooltip: any = flexRender(tooltip.tooltip, header.getContext()); return ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} + + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + ); })} @@ -75,13 +89,24 @@ function DataTable({ columns, data, footerRows, isHeader }: DataT ))} )} - - +
    + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - + + {row.getVisibleCells().map((cell, index) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} diff --git a/apps/web/lib/components/kanban-card.tsx b/apps/web/lib/components/kanban-card.tsx index cdbe018bc..0dfe7c38c 100644 --- a/apps/web/lib/components/kanban-card.tsx +++ b/apps/web/lib/components/kanban-card.tsx @@ -180,7 +180,7 @@ export default function Item(props: ItemProps) {
    {} - {menu} + {menu}
    diff --git a/apps/web/lib/features/task/task-all-status-type.tsx b/apps/web/lib/features/task/task-all-status-type.tsx index f1c88d696..bd5b790ee 100644 --- a/apps/web/lib/features/task/task-all-status-type.tsx +++ b/apps/web/lib/features/task/task-all-status-type.tsx @@ -53,7 +53,7 @@ export function TaskAllStatusTypes({ return (
    -
    +
    {showStatus && task?.status && taskStatus[task?.status] && ( + ); } @@ -27,7 +32,7 @@ export function UserInfoCell({ cell }: { cell: any }) { const publicTeam = get(cell, 'column.columnDef.meta.publicTeam', false); const memberInfo = useTeamMemberCard(member); - return ; + return ; } export function WorkedOnTaskCell({ row }: { row: any }) { @@ -40,7 +45,7 @@ export function WorkedOnTaskCell({ row }: { row: any }) { memberInfo={memberInfo} task={memberInfo.memberTask} isAuthUser={memberInfo.isAuthUser} - className="2xl:w-48 3xl:w-[12rem] w-1/5 lg:px-4 flex flex-col gap-y-[1.125rem] justify-center" + className="items-center lg:px-4 flex flex-col gap-y-[1.125rem] justify-center" /> ); } @@ -55,7 +60,7 @@ export function TaskEstimateInfoCell({ row }: { row: any }) { memberInfo={memberInfo} edition={taskEdition} activeAuthTask={true} - className="w-1/5 lg:px-3 2xl:w-52 3xl:w-64" + className="flex flex-col justify-center " /> ); } diff --git a/apps/web/lib/features/team-members-block-view.tsx b/apps/web/lib/features/team-members-block-view.tsx index 94b623e09..bb2ba61dd 100644 --- a/apps/web/lib/features/team-members-block-view.tsx +++ b/apps/web/lib/features/team-members-block-view.tsx @@ -5,6 +5,7 @@ import { UserTeamBlock } from './team/user-team-block'; import { useRecoilValue } from 'recoil'; import { taskBlockFilterState } from '@app/stores/task-filter'; import { UserTeamCardSkeleton } from './team/user-team-card/task-skeleton'; +import { UserTeamBlockHeader } from './team/user-team-block/user-team-block-header'; interface Props { teamMembers: OT_Member[]; @@ -39,62 +40,67 @@ const TeamMembersBlockView: React.FC = ({ } return ( -
    - {/* Current authenticated user members */} - - {/* */} - -
    - {members.map((member) => { - return ( -
    - - - -
    - ); - })} -
    - - {[1, 2].map((_, i) => { - return ( -
  • - -
  • - ); - })} -
    - {members.length < 1 && !teamsFetching && ( -
    -

    {emptyMessage}

    + <> + +
    + +
    + {/* Current authenticated user members */} + + {/* */} + +
    + {members.map((member) => { + return ( +
    + + + +
    + ); + })}
    - )} -
    + + {[1, 2].map((_, i) => { + return ( +
  • + +
  • + ); + })} +
    + {members.length < 1 && !teamsFetching && ( +
    +

    {emptyMessage}

    +
    + )} +
    + ); }; diff --git a/apps/web/lib/features/team-members-card-view.tsx b/apps/web/lib/features/team-members-card-view.tsx index 3a848ecb3..e5c210291 100644 --- a/apps/web/lib/features/team-members-card-view.tsx +++ b/apps/web/lib/features/team-members-card-view.tsx @@ -2,7 +2,7 @@ import { useAuthenticateUser, useModal, useOrganizationEmployeeTeams, useTeamInv import { Transition } from '@headlessui/react'; import { InviteFormModal } from './team/invite/invite-form-modal'; import { InvitedCard, InviteUserTeamCard } from './team/invite/user-invite-card'; -import { InviteUserTeamSkeleton, UserTeamCard, UserTeamCardSkeleton } from '.'; +import { InviteUserTeamSkeleton, UserTeamCard, UserTeamCardHeader, UserTeamCardSkeleton } from '.'; import { OT_Member } from '@app/interfaces'; import React from 'react'; @@ -46,109 +46,115 @@ const TeamMembersCardView: React.FC = ({ }; return ( -
      - {/* Current authenticated user members */} - -
    • - (dragTeamMember.current = 0)} - onDragEnter={() => (draggedOverTeamMember.current = 0)} - onDragEnd={handleSort} - onDragOver={(e) => e.preventDefault()} - /> -
    • -
      - - {/* Team members list */} - {memberOrdereds.map((member, i) => { - return ( - -
    • - { - dragTeamMember.current = i; - }} - onDragEnter={() => { - draggedOverTeamMember.current = i; - }} - onDragEnd={handleSort} - onDragOver={(e) => e.preventDefault()} - /> -
    • -
      - ); - })} - - {members.length > 0 && - teamInvitations.map((invitation) => ( -
    • - + <> +
      + +
      +
      +
        + {/* Current authenticated user members */} + +
      • + (dragTeamMember.current = 0)} + onDragEnter={() => (draggedOverTeamMember.current = 0)} + onDragEnd={handleSort} + onDragOver={(e) => e.preventDefault()} + />
      • - ))} +
        - {/* Loader skeleton */} - - {[0, 2].map((_, i) => { + {/* Team members list */} + {memberOrdereds.map((member, i) => { return ( -
      • - -
      • + +
      • + { + dragTeamMember.current = i; + }} + onDragEnter={() => { + draggedOverTeamMember.current = i; + }} + onDragEnd={handleSort} + onDragOver={(e) => e.preventDefault()} + /> +
      • +
        ); })} -
      • - -
      • -
        - {/* Invite button */} - -
      • - -
      • -
        -
      + {members.length > 0 && + teamInvitations.map((invitation) => ( +
    • + +
    • + ))} + + {/* Loader skeleton */} + + {[0, 2].map((_, i) => { + return ( +
    • + +
    • + ); + })} +
    • + +
    • +
      + + {/* Invite button */} + +
    • + +
    • +
      +
    + ); }; diff --git a/apps/web/lib/features/team-members-table-view.tsx b/apps/web/lib/features/team-members-table-view.tsx index 1ffe4937e..15d51aa1d 100644 --- a/apps/web/lib/features/team-members-table-view.tsx +++ b/apps/web/lib/features/team-members-table-view.tsx @@ -6,6 +6,7 @@ import { UserInfoCell, TaskCell, WorkedOnTaskCell, TaskEstimateInfoCell, ActionM import { useAuthenticateUser, useModal } from '@app/hooks'; import { InviteUserTeamCard } from './team/invite/user-invite-card'; import { InviteFormModal } from './team/invite/invite-form-modal'; +import { useTranslations } from 'next-intl'; const TeamMembersTableView = ({ teamMembers, @@ -18,11 +19,14 @@ const TeamMembersTableView = ({ publicTeam?: boolean; active?: boolean; }) => { + const t = useTranslations(); + const columns = React.useMemo[]>( () => [ { id: 'name', - header: 'Name', + header: 'Team Member', + tooltip: '', cell: UserInfoCell, meta: { publicTeam @@ -31,21 +35,25 @@ const TeamMembersTableView = ({ { id: 'task', header: 'Task', + tooltip: '', cell: TaskCell }, { id: 'workedOnTask', header: 'Worked on task', + tooltip: t('task.taskTableHead.TOTAL_WORKED_TODAY_HEADER_TOOLTIP'), cell: WorkedOnTaskCell }, { id: 'estimate', header: 'Estimate', + tooltip: '', cell: TaskEstimateInfoCell }, { id: 'action', header: 'Action', + tooltip: '', cell: ActionMenuCell, meta: { active @@ -65,6 +73,7 @@ const TeamMembersTableView = ({ return ( <> []} data={sortedTeamMembers} noResultsMessage={{ diff --git a/apps/web/lib/features/team/user-team-card/task-info.tsx b/apps/web/lib/features/team/user-team-card/task-info.tsx index 86b23f803..0c96e6194 100644 --- a/apps/web/lib/features/team/user-team-card/task-info.tsx +++ b/apps/web/lib/features/team/user-team-card/task-info.tsx @@ -14,12 +14,12 @@ export function TaskInfo({ className, memberInfo, edition, publicTeam }: Props) return (
    {/* task */} -
    +
    {edition.task && ( )} diff --git a/apps/web/lib/features/team/user-team-card/user-team-card-menu.tsx b/apps/web/lib/features/team/user-team-card/user-team-card-menu.tsx index 98564efe1..358c4ac20 100644 --- a/apps/web/lib/features/team/user-team-card/user-team-card-menu.tsx +++ b/apps/web/lib/features/team/user-team-card/user-team-card-menu.tsx @@ -77,7 +77,7 @@ function DropdownMenu({ edition, memberInfo }: Props) { return ( Date: Mon, 18 Mar 2024 22:26:05 +0500 Subject: [PATCH 15/17] [fix]: epic and labels filter added --- apps/web/app/[locale]/kanban/page.tsx | 20 ++++++++++++++++++-- apps/web/app/hooks/features/useKanban.ts | 17 +++++++++++++++-- apps/web/lib/features/task/task-status.tsx | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index b4b255dc7..b686f9b21 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -18,10 +18,10 @@ import { AddIcon, PeoplesIcon } from 'assets/svg'; import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; import { userTimezone } from '@app/helpers'; import KanbanSearch from '@components/pages/kanban/search-bar'; -import { TaskPropertiesDropdown, TaskSizesDropdown } from 'lib/features'; +import { EpicPropertiesDropdown, TaskLabelsDropdown, TaskPropertiesDropdown, TaskSizesDropdown } from 'lib/features'; const Kanban = () => { - const { data, setSearchTasks, searchTasks, isLoading, setPriority, setSizes } = useKanban(); + const { data, setSearchTasks, searchTasks, isLoading, setPriority, setSizes, setLabels, setEpics } = useKanban(); const { activeTeam } = useOrganizationTeams(); const t = useTranslations(); @@ -53,6 +53,7 @@ const Kanban = () => { const { user } = useAuthenticateUser(); const { openModal, isOpen, closeModal } = useModal(); const timezone = userTimezone(); + console.log('datadata', data); return ( <> @@ -114,6 +115,21 @@ const Kanban = () => { ))}
    +
    + setEpics(values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + /> +
    + +
    + setLabels(values || [])} + className="lg:min-w-[140px] pt-[3px] mt-4 mb-2 lg:mt-0" + multiple={true} + /> +
    setPriority(values || [])} diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index 44203b35d..679f39319 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -8,6 +8,8 @@ import { IKanban } from '@app/interfaces/IKanban'; export function useKanban() { const [loading, setLoading] = useState(true); const [searchTasks, setSearchTasks] = useState(''); + const [labels, setLabels] = useState([]); + const [epics, setEpics] = useState([]); const [kanbanBoard, setKanbanBoard] = useRecoilState(kanbanBoardState); const taskStatusHook = useTaskStatus(); const { tasks: newTask, tasksFetching, updateTask } = useTeamTasks(); @@ -26,7 +28,16 @@ export function useKanban() { }) .filter((task: ITeamTask) => { return sizes.length ? sizes.includes(task.size) : true; + }) + // filter labels in task.tags[{label}] + .filter((task: ITeamTask) => { + return labels.length ? labels.some((label) => task.tags.some((tag) => tag.name === label)) : true; + }) + // filter epics as id now filter data + .filter((task: ITeamTask) => { + return epics.length ? epics.includes(task.id) : true; }); + const getTasksByStatus = (status: string | undefined) => { return tasks.filter((task: ITeamTask) => { return task.status === status; @@ -43,7 +54,7 @@ export function useKanban() { setLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [taskStatusHook.loading, tasksFetching, newTask, searchTasks, priority, sizes]); + }, [taskStatusHook.loading, tasksFetching, newTask, searchTasks, priority, sizes, labels, epics]); /** * collapse or show kanban column @@ -79,7 +90,7 @@ export function useKanban() { }); }); }; - + console.log('kanbanBoard-l', epics); const addNewTask = (task: ITeamTask, status: string) => { const updatedBoard = { ...kanbanBoard, @@ -93,7 +104,9 @@ export function useKanban() { columns: taskStatusHook.taskStatus, searchTasks, setPriority, + setLabels, setSizes, + setEpics, updateKanbanBoard: setKanbanBoard, updateTaskStatus: updateTask, toggleColumn, diff --git a/apps/web/lib/features/task/task-status.tsx b/apps/web/lib/features/task/task-status.tsx index 1277ea673..58b0eae4e 100644 --- a/apps/web/lib/features/task/task-status.tsx +++ b/apps/web/lib/features/task/task-status.tsx @@ -433,7 +433,7 @@ export function EpicPropertiesDropdown({ value: task.id, icon: (
    - , +
    ) }; From b40498690c5d0a0b7c785480644ba0109c674072 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Mon, 18 Mar 2024 22:33:43 +0500 Subject: [PATCH 16/17] [fix]: epic and labels filter added --- apps/web/app/hooks/features/useKanban.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/web/app/hooks/features/useKanban.ts b/apps/web/app/hooks/features/useKanban.ts index 679f39319..dfa0b797f 100644 --- a/apps/web/app/hooks/features/useKanban.ts +++ b/apps/web/app/hooks/features/useKanban.ts @@ -29,11 +29,9 @@ export function useKanban() { .filter((task: ITeamTask) => { return sizes.length ? sizes.includes(task.size) : true; }) - // filter labels in task.tags[{label}] .filter((task: ITeamTask) => { return labels.length ? labels.some((label) => task.tags.some((tag) => tag.name === label)) : true; }) - // filter epics as id now filter data .filter((task: ITeamTask) => { return epics.length ? epics.includes(task.id) : true; }); @@ -90,7 +88,6 @@ export function useKanban() { }); }); }; - console.log('kanbanBoard-l', epics); const addNewTask = (task: ITeamTask, status: string) => { const updatedBoard = { ...kanbanBoard, From 0c4100fe4b07e25d8f4b660162b2e5800bf7ed58 Mon Sep 17 00:00:00 2001 From: anishali2 Date: Mon, 18 Mar 2024 22:34:39 +0500 Subject: [PATCH 17/17] [fix]: epic and labels filter added --- apps/web/app/[locale]/kanban/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index b686f9b21..f8bd790e6 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -53,7 +53,6 @@ const Kanban = () => { const { user } = useAuthenticateUser(); const { openModal, isOpen, closeModal } = useModal(); const timezone = userTimezone(); - console.log('datadata', data); return ( <>