From 3a8d75958d3f8ece83483608db7a5901a848d996 Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Mon, 2 Sep 2024 09:43:11 +0200 Subject: [PATCH 01/21] Fix warnings in compononents --- apps/web/app/[locale]/calendar/component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/[locale]/calendar/component.tsx b/apps/web/app/[locale]/calendar/component.tsx index 6656ec634..fdba66829 100644 --- a/apps/web/app/[locale]/calendar/component.tsx +++ b/apps/web/app/[locale]/calendar/component.tsx @@ -71,8 +71,8 @@ export function HeadTimeSheet({ timesheet, isOpen, openModal, closeModal }: { ti
{}} + isOpen={isOpen ?? false} />
{timesheet === 'TimeSheet' && ( From 1f3d2c5869080f879da37174fd0bdc79c642d5f7 Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Mon, 2 Sep 2024 09:43:50 +0200 Subject: [PATCH 02/21] Fix warnings in kanban --- apps/web/app/[locale]/kanban/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index c7dff39c2..4b2aab1d7 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -62,7 +62,7 @@ const Kanban = () => { { title: activeTeam?.name || '', href: '/' }, { title: t('common.KANBAN'), href: `/${currentLocale}/kanban` } ], - [activeTeam?.name, currentLocale] + [activeTeam?.name, currentLocale, t] ); const activeTeamMembers = activeTeam?.members ? activeTeam.members : []; From 2b0b4dbc481c3198b0a52c2602b80b13f293a5ae Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Mon, 2 Sep 2024 09:44:40 +0200 Subject: [PATCH 03/21] Update livekeet componet and fix warning --- apps/web/app/[locale]/meet/livekit/component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/[locale]/meet/livekit/component.tsx b/apps/web/app/[locale]/meet/livekit/component.tsx index f362623f0..2699e5890 100644 --- a/apps/web/app/[locale]/meet/livekit/component.tsx +++ b/apps/web/app/[locale]/meet/livekit/component.tsx @@ -41,7 +41,7 @@ function LiveKitPage() {
{token && roomName && Date: Mon, 2 Sep 2024 09:45:46 +0200 Subject: [PATCH 04/21] Update UserProfileDetail --- .../profile/[memberId]/components/UserProfileDetail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx b/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx index 527c004c4..6bc360bc1 100644 --- a/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx @@ -67,7 +67,7 @@ export function UserProfileDetail({ member }: { member?: OT_Member }) { {user?.firstName} {user?.lastName}
- +
{user?.email} From 6d1c6ea6d54026c9983b49c6f16a03190bc8b682 Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Mon, 2 Sep 2024 19:07:47 +0200 Subject: [PATCH 05/21] Fix warnings like unsed vars and non null assertion --- .../components/UserProfileDetail.tsx | 2 +- apps/web/app/helpers/daily-plan-estimated.ts | 5 +- apps/web/app/helpers/drag-and-drop.ts | 5 +- .../hooks/auth/useAuthenticationPasscode.ts | 129 +++++++++--------- apps/web/app/hooks/features/useEmployee.ts | 2 +- .../hooks/features/useGetTasksStatsData.ts | 2 +- apps/web/app/hooks/features/usePagination.ts | 4 +- apps/web/app/hooks/features/useTeamTasks.ts | 18 ++- apps/web/app/hooks/features/useTimeLogs.ts | 1 - apps/web/app/hooks/features/useTimer.ts | 2 +- apps/web/app/stores/employee.ts | 2 +- .../kanban/sort-tasks-status-settings.tsx | 5 +- .../blocks/task-secondary-info.tsx | 2 +- apps/web/components/ui/calendar.tsx | 2 +- .../components/custom-select/multi-select.tsx | 2 +- .../lib/components/inputs/auth-code-input.tsx | 16 +-- apps/web/lib/components/lazy-render.tsx | 2 +- apps/web/lib/components/time-picker/index.tsx | 2 +- .../daily-plan-compare-estimate-modal.tsx | 2 +- .../calendar/confirm-change-status.tsx | 4 +- .../calendar/setup-full-calendar.tsx | 4 +- .../calendar/table-time-sheet.tsx | 1 + .../manual-time/add-manual-time-modal.tsx | 22 +-- .../features/task/daily-plan/future-tasks.tsx | 2 +- .../task/daily-plan/outstanding-all.tsx | 2 +- .../features/task/daily-plan/past-tasks.tsx | 2 +- .../features/task/task-all-status-type.tsx | 2 +- .../lib/features/team-members-card-view.tsx | 2 +- apps/web/lib/features/user-profile-plans.tsx | 17 ++- apps/web/lib/settings/version-form.tsx | 2 +- 30 files changed, 147 insertions(+), 118 deletions(-) diff --git a/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx b/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx index 6bc360bc1..253fe89d1 100644 --- a/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/components/UserProfileDetail.tsx @@ -67,7 +67,7 @@ export function UserProfileDetail({ member }: { member?: OT_Member }) { {user?.firstName} {user?.lastName}
- + {member ? : <>}
{user?.email} diff --git a/apps/web/app/helpers/daily-plan-estimated.ts b/apps/web/app/helpers/daily-plan-estimated.ts index 1c18fb348..6d81d429d 100644 --- a/apps/web/app/helpers/daily-plan-estimated.ts +++ b/apps/web/app/helpers/daily-plan-estimated.ts @@ -20,9 +20,10 @@ export const dailyPlanCompareEstimated = (plans: IDailyPlan[]): IDailyPlanCompar }; } - const workTimePlanned = convertHourToSeconds(plan.workTimePlanned!); + const workTimePlanned = plan.workTimePlanned ? convertHourToSeconds(plan.workTimePlanned) : 0; const times = plan.tasks?.map((task) => task.estimate).filter((time): time is number => typeof time === 'number') ?? []; - const estimated = plan.tasks?.map((task) => task.estimate! > 0); + const estimated = plan.tasks?.map((task) => (task.estimate ?? 0) > 0); + let estimatedTime = 0; if (times.length > 0) { diff --git a/apps/web/app/helpers/drag-and-drop.ts b/apps/web/app/helpers/drag-and-drop.ts index c1d3af554..245c46dc3 100644 --- a/apps/web/app/helpers/drag-and-drop.ts +++ b/apps/web/app/helpers/drag-and-drop.ts @@ -11,11 +11,12 @@ export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setP const planSourceIndex = newPlans.findIndex(plan => plan.id === source.droppableId); const planDestinationIndex = newPlans.findIndex(plan => plan.id === destination.droppableId); - const newSourceTasks = [...newPlans[planSourceIndex].tasks!]; + const newSourceTasks = newPlans[planSourceIndex].tasks ? [...newPlans[planSourceIndex].tasks] : []; const newDestinationTasks = source.droppableId !== destination.droppableId - ? [...newPlans[planDestinationIndex].tasks!] + ? newPlans[planDestinationIndex].tasks ? [...newPlans[planDestinationIndex].tasks] : [] : newSourceTasks; + const [deletedTask] = newSourceTasks.splice(source.index, 1); newDestinationTasks.splice(destination.index, 0, deletedTask); diff --git a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts index 724f309e1..376ab9f27 100644 --- a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts +++ b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts @@ -75,80 +75,81 @@ export function useAuthenticationPasscode() { /** * Verify auth request */ - const verifySignInEmailConfirmRequest = async ({ - email, - code, - lastTeamId - }: { - email: string; - code: string; - lastTeamId?: string; - }) => { - signInEmailConfirmQueryCall(email, code) - .then((res) => { - if ('team' in res.data) { - router.replace('/'); - return; - } + const verifySignInEmailConfirmRequest = useCallback( + async ({ + email, + code, + lastTeamId + }: { + email: string; + code: string; + lastTeamId?: string; + }) => { + signInEmailConfirmQueryCall(email, code) + .then((res) => { + if ('team' in res.data) { + router.replace('/'); + return; + } - const checkError: { - message: string; - } = res.data as any; + const checkError: { + message: string; + } = res.data as any; - const isError = checkError.message === 'Unauthorized'; + const isError = checkError.message === 'Unauthorized'; - if (isError) { - setErrors({ - code: 'Invalid code. Please try again.' - }); - } else { - setErrors({}); - } + if (isError) { + setErrors({ + code: 'Invalid code. Please try again.' + }); + } else { + setErrors({}); + } - const data = res.data as ISigninEmailConfirmResponse; - if (!data.workspaces) { - return; - } + const data = res.data as ISigninEmailConfirmResponse; + if (!data.workspaces) { + return; + } - if (data && Array.isArray(data.workspaces) && data.workspaces.length > 0) { - setWorkspaces(data.workspaces); - setDefaultTeamId(data.defaultTeamId); + if (data && Array.isArray(data.workspaces) && data.workspaces.length > 0) { + setWorkspaces(data.workspaces); + setDefaultTeamId(data.defaultTeamId); - setScreen('workspace'); - } + setScreen('workspace'); + } - // If user tries to login from public Team Page as an Already a Member - // Redirect to the current team automatically - if (pathname === '/team/[teamId]/[profileLink]' && data.workspaces.length) { - if (queryTeamId) { - const currentWorkspace = data.workspaces.find((workspace) => - workspace.current_teams.map((item) => item.team_id).includes(queryTeamId as string) - ); - - signInToWorkspaceRequest({ - email: email, - code: code, - token: currentWorkspace?.token as string, - selectedTeam: queryTeamId as string, - lastTeamId - }); + // If user tries to login from public Team Page as an Already a Member + // Redirect to the current team automatically + if (pathname === '/team/[teamId]/[profileLink]' && data.workspaces.length) { + if (queryTeamId) { + const currentWorkspace = data.workspaces.find((workspace) => + workspace.current_teams.map((item) => item.team_id).includes(queryTeamId as string) + ); + + signInToWorkspaceRequest({ + email: email, + code: code, + token: currentWorkspace?.token as string, + selectedTeam: queryTeamId as string, + lastTeamId + }); + } } - } - // if (res.data?.status !== 200 && res.data?.status !== 201) { - // setErrors({ code: t('pages.auth.INVALID_INVITE_CODE_MESSAGE') }); - // } - }) - .catch((err: AxiosError<{ errors: Record }, any> | { errors: Record }) => { - if (isAxiosError(err)) { - if (err.response?.status === 400) { - setErrors(err.response.data?.errors || {}); + // if (res.data?.status !== 200 && res.data?.status !== 201) { + // setErrors({ code: t('pages.auth.INVALID_INVITE_CODE_MESSAGE') }); + // } + }) + .catch((err: AxiosError<{ errors: Record }, any> | { errors: Record }) => { + if (isAxiosError(err)) { + if (err.response?.status === 400) { + setErrors(err.response.data?.errors || {}); + } + } else { + setErrors(err.errors || {}); } - } else { - setErrors(err.errors || {}); - } - }); - }; + }); + }, [signInEmailConfirmQueryCall, router, pathname, queryTeamId]); const verifyPasscodeRequest = useCallback( ({ email, code }: { email: string; code: string }) => { diff --git a/apps/web/app/hooks/features/useEmployee.ts b/apps/web/app/hooks/features/useEmployee.ts index 916877137..c366a426e 100644 --- a/apps/web/app/hooks/features/useEmployee.ts +++ b/apps/web/app/hooks/features/useEmployee.ts @@ -52,7 +52,7 @@ export const useEmployeeUpdate = () => { .catch((error) => { console.log(error); }); - }, []); + }, [employeeUpdateQuery]); return { updateEmployee, isLoading } } diff --git a/apps/web/app/hooks/features/useGetTasksStatsData.ts b/apps/web/app/hooks/features/useGetTasksStatsData.ts index 37e8548d9..f4d8a67ff 100644 --- a/apps/web/app/hooks/features/useGetTasksStatsData.ts +++ b/apps/web/app/hooks/features/useGetTasksStatsData.ts @@ -32,7 +32,7 @@ export function useGetTasksStatsData(employeeId: string | undefined, triggerWith if (entry?.isIntersecting && supported) { loadTaskStats(); } - }, [employeeId, triggerWithIObserver, entry]); + }, [employeeId, triggerWithIObserver, entry, getTasksStatsData]); return IObserverRef; } diff --git a/apps/web/app/hooks/features/usePagination.ts b/apps/web/app/hooks/features/usePagination.ts index 3b4630b7b..5f0967ef9 100644 --- a/apps/web/app/hooks/features/usePagination.ts +++ b/apps/web/app/hooks/features/usePagination.ts @@ -48,7 +48,7 @@ export function useScrollPagination({ setPage(1); setSlicedItems(items.slice(0, defaultItemsPerPage)); } - }, [enabled, items]); + }, [enabled, items, defaultItemsPerPage]); useEffect(() => { const container = $scrollableElement.current; @@ -68,7 +68,7 @@ export function useScrollPagination({ return () => { container.removeEventListener('scroll', handleScroll); }; - }, [$scrollableElement.current, enabled]); + }, [enabled]); useEffect(() => { const newItems = items.slice(0, defaultItemsPerPage * page); diff --git a/apps/web/app/hooks/features/useTeamTasks.ts b/apps/web/app/hooks/features/useTeamTasks.ts index a60f7d6c4..f317fb5fd 100644 --- a/apps/web/app/hooks/features/useTeamTasks.ts +++ b/apps/web/app/hooks/features/useTeamTasks.ts @@ -115,7 +115,7 @@ export function useTeamTasks() { return res; }); }, - [getTasksByIdQueryCall, setDetailedTask] + [getTasksByIdQueryCall, setDetailedTask, tasksRef] ); const getTasksByEmployeeId = useCallback( @@ -285,8 +285,8 @@ export function useTeamTasks() { // TODO: Make it dynamic when we add Dropdown in Navbar ...(activeTeam?.projects && activeTeam?.projects.length > 0 ? { - projectId: activeTeam.projects[0].id - } + projectId: activeTeam.projects[0].id + } : {}), ...(description ? { description: `

${description}

` } : {}), ...(members ? { members } : {}), @@ -451,7 +451,17 @@ export function useTeamTasks() { } } }, - [setActiveTeamTask, setActiveUserTaskCookieCb, updateOrganizationTeamEmployeeActiveTask, activeTeam, authUser] + [ + setActiveTeamTask, + setActiveUserTaskCookieCb, + updateOrganizationTeamEmployeeActiveTask, + activeTeam, + authUser, + $memberActiveTaskId, + $user, + tasksRef, + updateTask + ] ); const deleteEmployeeFromTasks = useCallback( diff --git a/apps/web/app/hooks/features/useTimeLogs.ts b/apps/web/app/hooks/features/useTimeLogs.ts index bd18fa220..49e9b1f96 100644 --- a/apps/web/app/hooks/features/useTimeLogs.ts +++ b/apps/web/app/hooks/features/useTimeLogs.ts @@ -43,7 +43,6 @@ export function useTimeLogs() { profile.member?.employeeId, queryTimerLogsDailyReport, setTimerLogsDailyReport, - user?.employee.id, user?.employee.organizationId, user?.tenantId, ] diff --git a/apps/web/app/hooks/features/useTimer.ts b/apps/web/app/hooks/features/useTimer.ts index 0712b14e9..62cafea4d 100644 --- a/apps/web/app/hooks/features/useTimer.ts +++ b/apps/web/app/hooks/features/useTimer.ts @@ -112,7 +112,7 @@ function useLocalTimeCounter(timerStatus: ITimerStatus | null, activeTeamTask: I return 0; } return timerSecondsRef.current; - }, [seconds, firstLoad, timerStatusRef]); + }, [seconds, timerStatusRef]); useEffect(() => { if (firstLoad) { diff --git a/apps/web/app/stores/employee.ts b/apps/web/app/stores/employee.ts index ecd867c97..7fbeca90b 100644 --- a/apps/web/app/stores/employee.ts +++ b/apps/web/app/stores/employee.ts @@ -14,5 +14,5 @@ export const workingEmployeesEmailState = atom({ export const employeeUpdateState = atom({ key: 'employeeUpdateState', - default: null!, + default: undefined, }) diff --git a/apps/web/components/pages/kanban/sort-tasks-status-settings.tsx b/apps/web/components/pages/kanban/sort-tasks-status-settings.tsx index d6410efe6..57fc6376b 100644 --- a/apps/web/components/pages/kanban/sort-tasks-status-settings.tsx +++ b/apps/web/components/pages/kanban/sort-tasks-status-settings.tsx @@ -9,14 +9,15 @@ import { SixSquareGridIcon } from 'assets/svg'; import { useTranslations } from 'next-intl'; import React, { useState } from 'react'; import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; -import { useRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; const SortTasksStatusSettings = ({ arr, onClose }: { arr: ITaskStatusItemList[]; onClose: () => void }) => { const [items, setItems] = useState(arr); const [saveLoader, setSaveLoader] = useState(false); const [saveCheck, setSaveCheck] = useState(false); const organizationId = getOrganizationIdCookie(); - const [_, setState] = useRecoilState(taskStatusListState); + const setState = useSetRecoilState(taskStatusListState); + const t = useTranslations(); const { reOrderQueryCall } = useTaskStatus(); const onDragEnd = (result: DropResult) => { diff --git a/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx b/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx index ffdff3dc9..37abeb8e8 100644 --- a/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx +++ b/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx @@ -52,7 +52,7 @@ const TaskSecondaryInfo = () => { (version: ITaskVersionCreate) => { handleStatusUpdate(version.value || version.name, 'version', task?.taskStatusId, task); }, - [$taskVersion, task, handleStatusUpdate] + [task, handleStatusUpdate] ); const onTaskSelect = useCallback( diff --git a/apps/web/components/ui/calendar.tsx b/apps/web/components/ui/calendar.tsx index ac069e2b3..160f09422 100644 --- a/apps/web/components/ui/calendar.tsx +++ b/apps/web/components/ui/calendar.tsx @@ -44,7 +44,7 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C ...classNames }} components={{ - Dropdown: ({ value, onChange, children, ...props }: DropdownProps) => { + Dropdown: ({ value, onChange, children }: DropdownProps) => { const options = React.Children.toArray(children) as React.ReactElement< React.HTMLProps >[]; diff --git a/apps/web/lib/components/custom-select/multi-select.tsx b/apps/web/lib/components/custom-select/multi-select.tsx index 6b87b8084..a88cb2e65 100644 --- a/apps/web/lib/components/custom-select/multi-select.tsx +++ b/apps/web/lib/components/custom-select/multi-select.tsx @@ -71,7 +71,7 @@ export function MultiSelect({ if (triggerRef.current) { setPopoverWidth(triggerRef.current.offsetWidth); } - }, [triggerRef.current]); + }, []); return (
diff --git a/apps/web/lib/components/inputs/auth-code-input.tsx b/apps/web/lib/components/inputs/auth-code-input.tsx index 7a8a2b4e2..6aca5b03b 100644 --- a/apps/web/lib/components/inputs/auth-code-input.tsx +++ b/apps/web/lib/components/inputs/auth-code-input.tsx @@ -125,16 +125,9 @@ export const AuthCodeInputField = forwardRef( } }, [autoFocus, inputsRef]); - useEffect(() => { - if (autoComplete && autoComplete.length > 0) { - handleAutoComplete(autoComplete); - setCanSubmit(true); - } - }, [autoComplete, canSubmit]); - useEffect(() => { canSubmit && submitCode && submitCode(); - }, []); + }, [canSubmit, submitCode]); const sendResult = () => { const res = inputsRef.current.map((input) => input.value).join(''); @@ -225,6 +218,13 @@ export const AuthCodeInputField = forwardRef( sendResult(); }; + useEffect(() => { + if (autoComplete && autoComplete.length > 0) { + handleAutoComplete(autoComplete); + setCanSubmit(true); + } + }, [autoComplete, canSubmit, handleAutoComplete]); + const hintColor = { success: '#4BB543', error: '#FF9494', diff --git a/apps/web/lib/components/lazy-render.tsx b/apps/web/lib/components/lazy-render.tsx index e11e30427..2261578c0 100644 --- a/apps/web/lib/components/lazy-render.tsx +++ b/apps/web/lib/components/lazy-render.tsx @@ -53,7 +53,7 @@ export function LazyRender({ items, children, itemsPerPage = 1 return () => { window.cancelIdleCallback(cancelableIdlCallback); }; - }, [page, items, itemsRef]); + }, [page, items, itemsRef, itemsPerPage]); return ( <> diff --git a/apps/web/lib/components/time-picker/index.tsx b/apps/web/lib/components/time-picker/index.tsx index 210c1281d..17daf3663 100644 --- a/apps/web/lib/components/time-picker/index.tsx +++ b/apps/web/lib/components/time-picker/index.tsx @@ -31,7 +31,7 @@ export function TimePicker({ onChange, defaultValue }: IPopoverTimePicker) { const handleTimeChange = (newTime: any) => { setTime(newTime); - onChange!(newTime) + onChange && onChange(newTime); }; return ( diff --git a/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx b/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx index 7d4f56c7c..e762df8cf 100644 --- a/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx +++ b/apps/web/lib/features/daily-plan/daily-plan-compare-estimate-modal.tsx @@ -38,7 +38,7 @@ export function DailyPlanCompareEstimatedModal({ const hour = dh.toString()?.padStart(2, '0'); const minute = dm.toString()?.padStart(2, '0'); const [times, setTimes] = useState({ - hours: (workTimePlanned! / 3600).toString(), + hours: workTimePlanned ? (workTimePlanned / 3600).toString() : '--', meridiem: 'PM', minute: '--' }); diff --git a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx index 4f7b6bbc1..d42f2fe24 100644 --- a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx +++ b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx @@ -25,8 +25,8 @@ export function ConfirmStatusChange({ closeModal, isOpen, newStatus, oldStatus } return ( {}} title={"Confirm Change"} className="bg-light--theme-light text-xl0 dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[24rem] h-[auto] justify-start !shadow-2xl" titleClass="font-bold" diff --git a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx index 53029d319..d5ff98662 100644 --- a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx +++ b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx @@ -191,7 +191,7 @@ export const CardItemsMember = ({ imageUrl, name, time }: { imageUrl?: string, n return (
- +
{name}
@@ -208,7 +208,7 @@ export const CardItemsProjects = ({ logo, title, totalHours }: { logo?: string, return (
- logos + logos
{title} {totalHours} diff --git a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx index e6c533f9d..f18ae4c5e 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -417,6 +417,7 @@ const TaskDetails = ({ description, name }: { description: string; name: string {name} +
{description}
); }; diff --git a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx index c2b4ee861..dc9ac4e3b 100644 --- a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx +++ b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx @@ -241,14 +241,20 @@ export function AddManualTimeModal(props: IAddManualTimeModalProps) { - setTeam(team)} - itemId={(team) => (team ? team.id : '')} - itemToString={(team) => (team ? team.name : '')} - triggerClassName="border-gray-300 dark:border-slate-600" - /> + { + activeTeam ? + setTeam(team)} + itemId={(team) => (team ? team.id : '')} + itemToString={(team) => (team ? team.name : '')} + triggerClassName="border-gray-300 dark:border-slate-600" + /> + : + <> + } +
{ diff --git a/apps/web/lib/features/task/daily-plan/future-tasks.tsx b/apps/web/lib/features/task/daily-plan/future-tasks.tsx index 7260ea075..08dbe89c0 100644 --- a/apps/web/lib/features/task/daily-plan/future-tasks.tsx +++ b/apps/web/lib/features/task/daily-plan/future-tasks.tsx @@ -27,7 +27,7 @@ export function FutureTasks({ profile }: { profile: any }) { const [futureDailyPlanTasks, setFutureDailyPlanTasks] = useState(futurePlans); useEffect(() => { setFutureDailyPlanTasks(filterDailyPlan(date as any, futurePlans)); - }, [date, setDate]); + }, [date, setDate, futurePlans]); const view = useRecoilValue(dailyPlanViewHeaderTabs); return ( diff --git a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx index f576333d4..e476d3a66 100644 --- a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx +++ b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx @@ -37,7 +37,7 @@ export function OutstandingAll({ profile }: OutstandingAll) { type="COLUMN" direction={view === 'CARDS' ? 'vertical' : 'horizontal'} > - {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( + {(provided: DroppableProvided) => (
    { setPastTasks(filterDailyPlan(date as any, pastPlans)); - }, [date, setDate]); + }, [date, setDate, pastPlans]); return (
    {pastTasks?.length > 0 ? ( 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 65e8df85a..f4cc77544 100644 --- a/apps/web/lib/features/task/task-all-status-type.tsx +++ b/apps/web/lib/features/task/task-all-status-type.tsx @@ -66,7 +66,7 @@ export function TaskAllStatusTypes({ ); }, [taskLabels, task?.tags]); - const taskId = planBadgeContPast(dailyPlan.items, task!.id); + const taskId = task? planBadgeContPast(dailyPlan.items, task.id) : ''; return (
    diff --git a/apps/web/lib/features/team-members-card-view.tsx b/apps/web/lib/features/team-members-card-view.tsx index 1f91528f3..9f246c688 100644 --- a/apps/web/lib/features/team-members-card-view.tsx +++ b/apps/web/lib/features/team-members-card-view.tsx @@ -47,7 +47,7 @@ const TeamMembersCardView: React.FC = ({ // TODO: update teamMembers index handleChangeOrder(peopleClone[dragTeamMember.current], draggedOverTeamMember.current); handleChangeOrder(peopleClone[draggedOverTeamMember.current], dragTeamMember.current); - }, [memberOrdereds, dragTeamMember, draggedOverTeamMember]); + }, [memberOrdereds, dragTeamMember, draggedOverTeamMember, handleChangeOrder]); return ( <> diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx index a42f37f0b..92a147e9c 100644 --- a/apps/web/lib/features/user-profile-plans.tsx +++ b/apps/web/lib/features/user-profile-plans.tsx @@ -91,7 +91,16 @@ export function UserProfilePlans() { setCurrentDataDailyPlan(futurePlans); setFilterFuturePlanData(filterDailyPlan(date as any, futurePlans)); } - }, [currentTab, setCurrentDataDailyPlan, setDate, date]); + }, [currentTab, + setCurrentDataDailyPlan, + setDate, + date, + currentDataDailyPlan, + futurePlans, + pastPlans, + sortedPlans + ] + ); return (
    @@ -196,7 +205,7 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current const [plans, setPlans] = useState(filteredPlans); useEffect(() => { setPlans(filterDailyPlan(date as any, filteredPlans)); - }, [date, setDate]); + }, [date, setDate, filteredPlans]); return (
    @@ -374,8 +383,8 @@ export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: Fil .reduce((acc, cur) => acc + cur, 0) ?? 0; // Get all tasks' estimation and worked times - const estimatedTime = sumTimes(plan.tasks!, 'estimate'); - const totalWorkTime = sumTimes(plan.tasks!, 'totalWorkedTime'); + const estimatedTime = plan.tasks ? sumTimes(plan.tasks, 'estimate') : 0; + const totalWorkTime = plan.tasks ? sumTimes(plan.tasks, 'totalWorkedTime') : 0; // Get completed and ready tasks from a plan const completedTasks = plan.tasks?.filter((task) => task.status === 'completed').length ?? 0; diff --git a/apps/web/lib/settings/version-form.tsx b/apps/web/lib/settings/version-form.tsx index feaf8b2f0..342c7a3bc 100644 --- a/apps/web/lib/settings/version-form.tsx +++ b/apps/web/lib/settings/version-form.tsx @@ -85,7 +85,7 @@ export const VersionForm = ({ formOnly = false, onCreated, onVersionCreated }: S }); } }, - [edit, createNew, formOnly, onCreated, editTaskVersion, user, reset, createTaskVersion, refetch] + [edit, createNew, formOnly, onCreated, editTaskVersion, user, reset, createTaskVersion, refetch, $onVersionCreated] ); return ( From ee1036b481fcf55e30ead82369b694ae257c522d Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Mon, 2 Sep 2024 23:30:49 +0200 Subject: [PATCH 06/21] Fix deepscan error --- apps/web/app/[locale]/meet/livekit/component.tsx | 2 +- .../task/details-section/blocks/task-secondary-info.tsx | 6 ++---- apps/web/lib/components/inputs/auth-code-input.tsx | 2 +- apps/web/lib/features/task/daily-plan/outstanding-all.tsx | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/web/app/[locale]/meet/livekit/component.tsx b/apps/web/app/[locale]/meet/livekit/component.tsx index 2699e5890..ad382fc81 100644 --- a/apps/web/app/[locale]/meet/livekit/component.tsx +++ b/apps/web/app/[locale]/meet/livekit/component.tsx @@ -41,7 +41,7 @@ function LiveKitPage() {
    {token && roomName && { const task = useRecoilValue(detailedTaskState); - const taskVersion = useRecoilValue(taskVersionListState); - const $taskVersion = useSyncRef(taskVersion); const { updateTask } = useTeamTasks(); const { handleStatusUpdate } = useTeamTasks(); diff --git a/apps/web/lib/components/inputs/auth-code-input.tsx b/apps/web/lib/components/inputs/auth-code-input.tsx index 6aca5b03b..a1c9b0988 100644 --- a/apps/web/lib/components/inputs/auth-code-input.tsx +++ b/apps/web/lib/components/inputs/auth-code-input.tsx @@ -223,7 +223,7 @@ export const AuthCodeInputField = forwardRef( handleAutoComplete(autoComplete); setCanSubmit(true); } - }, [autoComplete, canSubmit, handleAutoComplete]); + }, [autoComplete, canSubmit]); const hintColor = { success: '#4BB543', diff --git a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx index e476d3a66..c50447ccc 100644 --- a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx +++ b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx @@ -6,7 +6,7 @@ import { useRecoilValue } from 'recoil'; import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs'; import TaskBlockCard from '../task-block-card'; import { clsxm } from '@app/utils'; -import { DragDropContext, Draggable, Droppable, DroppableProvided, DroppableStateSnapshot } from 'react-beautiful-dnd'; +import { DragDropContext, Draggable, Droppable, DroppableProvided } from 'react-beautiful-dnd'; import { useState } from 'react'; import { ITeamTask } from '@app/interfaces'; import { handleDragAndDropDailyOutstandingAll } from '@app/helpers'; From 3e88e5e5ae2b9ad5663d11879c194b29800fdeef Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Mon, 2 Sep 2024 23:57:22 +0200 Subject: [PATCH 07/21] Remove empty arrow function --- .../app/[locale]/auth/password/component.tsx | 1 + apps/web/app/[locale]/calendar/component.tsx | 14 ++++-- .../calendar/confirm-change-status.tsx | 47 +++++++++++-------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/apps/web/app/[locale]/auth/password/component.tsx b/apps/web/app/[locale]/auth/password/component.tsx index dec387ab3..bccc4f0b7 100644 --- a/apps/web/app/[locale]/auth/password/component.tsx +++ b/apps/web/app/[locale]/auth/password/component.tsx @@ -123,6 +123,7 @@ function WorkSpaceScreen({ form, className }: { form: TAuthenticationPassword } [form.workspaces] ); + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { if (form.workspaces.length === 1 && !hasMultipleTeams) { setTimeout(() => { diff --git a/apps/web/app/[locale]/calendar/component.tsx b/apps/web/app/[locale]/calendar/component.tsx index fdba66829..f000fa1d8 100644 --- a/apps/web/app/[locale]/calendar/component.tsx +++ b/apps/web/app/[locale]/calendar/component.tsx @@ -70,10 +70,16 @@ export function HeadTimeSheet({ timesheet, isOpen, openModal, closeModal }: { ti return (
    - {}} - isOpen={isOpen ?? false} - /> + { + closeModal ? ( + + ) : + <> + } +
    {timesheet === 'TimeSheet' && (
    diff --git a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx index d42f2fe24..2fa91a4ce 100644 --- a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx +++ b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx @@ -24,30 +24,37 @@ export function ConfirmStatusChange({ closeModal, isOpen, newStatus, oldStatus } const oldStatusClass = getStatusClasses(oldStatus); return ( - {}} - title={"Confirm Change"} - className="bg-light--theme-light text-xl0 dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[24rem] h-[auto] justify-start !shadow-2xl" - titleClass="font-bold" - > -
    - Time entry will be changed from - + <> + { + closeModal ? ( + +
    + Time entry will be changed from + - - -
    -
    + + +
    +
    + ) + : + <> + } + ) } - const StatusTransition = ({ previousStatus, currentStatus, currentStatusClass, previousStatusClass }: { previousStatus: string; currentStatus: string; currentStatusClass: string; previousStatusClass: string }) => (
    {previousStatus} From 81ceefdd5b125c7277464cd15cdb37f451cefb5b Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Tue, 3 Sep 2024 08:37:40 +0200 Subject: [PATCH 08/21] FIx null check error --- apps/web/app/helpers/drag-and-drop.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/helpers/drag-and-drop.ts b/apps/web/app/helpers/drag-and-drop.ts index 245c46dc3..d96dab2b9 100644 --- a/apps/web/app/helpers/drag-and-drop.ts +++ b/apps/web/app/helpers/drag-and-drop.ts @@ -11,7 +11,7 @@ export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setP const planSourceIndex = newPlans.findIndex(plan => plan.id === source.droppableId); const planDestinationIndex = newPlans.findIndex(plan => plan.id === destination.droppableId); - const newSourceTasks = newPlans[planSourceIndex].tasks ? [...newPlans[planSourceIndex].tasks] : []; + const newSourceTasks = [...(newPlans[planSourceIndex].tasks ?? [])]; const newDestinationTasks = source.droppableId !== destination.droppableId ? newPlans[planDestinationIndex].tasks ? [...newPlans[planDestinationIndex].tasks] : [] : newSourceTasks; From bbedcdcec8cd27d2df08bca0f599860a80c09f28 Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Tue, 3 Sep 2024 09:20:30 +0200 Subject: [PATCH 09/21] Fix handling null assertion --- apps/web/app/helpers/drag-and-drop.ts | 3 +-- apps/web/lib/features/task/daily-plan/outstanding-all.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/web/app/helpers/drag-and-drop.ts b/apps/web/app/helpers/drag-and-drop.ts index d96dab2b9..8c51396b4 100644 --- a/apps/web/app/helpers/drag-and-drop.ts +++ b/apps/web/app/helpers/drag-and-drop.ts @@ -13,10 +13,9 @@ export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setP const newSourceTasks = [...(newPlans[planSourceIndex].tasks ?? [])]; const newDestinationTasks = source.droppableId !== destination.droppableId - ? newPlans[planDestinationIndex].tasks ? [...newPlans[planDestinationIndex].tasks] : [] + ? [...(newPlans[planDestinationIndex].tasks ?? [])] : newSourceTasks; - const [deletedTask] = newSourceTasks.splice(source.index, 1); newDestinationTasks.splice(destination.index, 0, deletedTask); diff --git a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx index c50447ccc..6c4c950a5 100644 --- a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx +++ b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx @@ -20,7 +20,7 @@ export function OutstandingAll({ profile }: OutstandingAll) { const displayedTaskId = new Set(); const tasks = outstandingPlans.map((plan) => plan.tasks).reduce((red, curr) => red?.concat(curr || []), []); - const [task, setTask] = useState(tasks!); + const [task, setTask] = useState(() => tasks ?? []); return (
    From 2e65ddc2b50cfb1c84471fffca503e0e0ad50e74 Mon Sep 17 00:00:00 2001 From: Ushindi Gedeon Date: Tue, 3 Sep 2024 09:40:39 +0200 Subject: [PATCH 10/21] Fix react-hooks exhaustive-deps warning --- apps/web/app/[locale]/auth/password/component.tsx | 3 ++- apps/web/app/hooks/auth/useAuthenticationPasscode.ts | 1 + apps/web/lib/components/inputs/auth-code-input.tsx | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/app/[locale]/auth/password/component.tsx b/apps/web/app/[locale]/auth/password/component.tsx index bccc4f0b7..ad22c6d7c 100644 --- a/apps/web/app/[locale]/auth/password/component.tsx +++ b/apps/web/app/[locale]/auth/password/component.tsx @@ -123,7 +123,7 @@ function WorkSpaceScreen({ form, className }: { form: TAuthenticationPassword } [form.workspaces] ); - // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => { if (form.workspaces.length === 1 && !hasMultipleTeams) { setTimeout(() => { @@ -150,6 +150,7 @@ function WorkSpaceScreen({ form, className }: { form: TAuthenticationPassword } ); setSelectedTeam(lastSelectedTeamId); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [form.workspaces]); useEffect(() => { diff --git a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts index 376ab9f27..22c083cc2 100644 --- a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts +++ b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts @@ -149,6 +149,7 @@ export function useAuthenticationPasscode() { setErrors(err.errors || {}); } }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [signInEmailConfirmQueryCall, router, pathname, queryTeamId]); const verifyPasscodeRequest = useCallback( diff --git a/apps/web/lib/components/inputs/auth-code-input.tsx b/apps/web/lib/components/inputs/auth-code-input.tsx index a1c9b0988..180db30ae 100644 --- a/apps/web/lib/components/inputs/auth-code-input.tsx +++ b/apps/web/lib/components/inputs/auth-code-input.tsx @@ -223,6 +223,7 @@ export const AuthCodeInputField = forwardRef( handleAutoComplete(autoComplete); setCanSubmit(true); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [autoComplete, canSubmit]); const hintColor = { From 74f5b0b227c1fd8e9ed2baf1acb43edbb0a6f4cb Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 06:49:34 +0000 Subject: [PATCH 11/21] Simplify integration types retrieval logic --- apps/web/lib/settings/integration-setting.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/web/lib/settings/integration-setting.tsx b/apps/web/lib/settings/integration-setting.tsx index 926e55207..abf9ed0fa 100644 --- a/apps/web/lib/settings/integration-setting.tsx +++ b/apps/web/lib/settings/integration-setting.tsx @@ -77,7 +77,7 @@ export const IntegrationSetting = () => { } }, [metaData, integrationTenant]); - const { loading: loadingIntegrationTypes, integrationTypes, getIntegrationTypes } = useIntegrationTypes(); + const { getIntegrationTypes } = useIntegrationTypes(); useEffect(() => { if (!integrationTenantLoading && integrationTenant && integrationTenant.length) { @@ -86,15 +86,13 @@ export const IntegrationSetting = () => { }, [integrationTenantLoading, integrationTenant, getRepositories]); useEffect(() => { - if (!loadingIntegrationTypes && integrationTypes.length === 0) { - getIntegrationTypes().then((types) => { - const allIntegrations = types.find((item: any) => item.name === 'All Integrations'); - if (allIntegrations && allIntegrations?.id) { - getIntegrationTenant('Github'); - } - }); - } - }, [loadingIntegrationTypes, integrationTypes, getIntegrationTypes, getIntegrationTenant]); + getIntegrationTypes().then((types) => { + const allIntegrations = types.find((item: any) => item.name === 'All Integrations'); + if (allIntegrations && allIntegrations?.id) { + getIntegrationTenant('Github'); + } + }); + }, [getIntegrationTypes, getIntegrationTenant]); const handleSelectRepo = useCallback( (value: string) => { From b92321e34555e4d8b15f838dd3e73b22d216e295 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 07:10:36 +0000 Subject: [PATCH 12/21] Add localization support for invalid auth code error --- .../hooks/auth/useAuthenticationPasscode.ts | 20 ++++++++----------- apps/web/locales/ar.json | 3 ++- apps/web/locales/bg.json | 3 ++- apps/web/locales/de.json | 3 ++- apps/web/locales/en.json | 3 ++- apps/web/locales/es.json | 3 ++- apps/web/locales/fr.json | 3 ++- apps/web/locales/he.json | 3 ++- apps/web/locales/it.json | 3 ++- apps/web/locales/nl.json | 3 ++- apps/web/locales/pl.json | 3 ++- apps/web/locales/pt.json | 3 ++- apps/web/locales/ru.json | 3 ++- apps/web/locales/zh.json | 3 ++- 14 files changed, 34 insertions(+), 25 deletions(-) diff --git a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts index 22c083cc2..da8249ec5 100644 --- a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts +++ b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts @@ -14,6 +14,7 @@ import { usePathname, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useQuery } from '../useQuery'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; type AuthCodeRef = { focus: () => void; @@ -24,6 +25,7 @@ export function useAuthenticationPasscode() { const router = useRouter(); const pathname = usePathname(); const query = useSearchParams(); + const t = useTranslations(); const queryTeamId = query?.get('teamId'); @@ -76,15 +78,7 @@ export function useAuthenticationPasscode() { * Verify auth request */ const verifySignInEmailConfirmRequest = useCallback( - async ({ - email, - code, - lastTeamId - }: { - email: string; - code: string; - lastTeamId?: string; - }) => { + async ({ email, code, lastTeamId }: { email: string; code: string; lastTeamId?: string }) => { signInEmailConfirmQueryCall(email, code) .then((res) => { if ('team' in res.data) { @@ -100,7 +94,7 @@ export function useAuthenticationPasscode() { if (isError) { setErrors({ - code: 'Invalid code. Please try again.' + code: t('pages.auth.INVALID_CODE_TRY_AGAIN') }); } else { setErrors({}); @@ -149,8 +143,10 @@ export function useAuthenticationPasscode() { setErrors(err.errors || {}); } }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [signInEmailConfirmQueryCall, router, pathname, queryTeamId]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [signInEmailConfirmQueryCall, router, pathname, queryTeamId] + ); const verifyPasscodeRequest = useCallback( ({ email, code }: { email: string; code: string }) => { diff --git a/apps/web/locales/ar.json b/apps/web/locales/ar.json index ea1e2f1f6..064bd8d4e 100644 --- a/apps/web/locales/ar.json +++ b/apps/web/locales/ar.json @@ -294,7 +294,8 @@ "LOGIN": "تسجيل الدخول", "SELECT_WORKSPACE": "تحديد مساحة العمل", "ENTER_EMAIL": "أدخل البريد الإلكتروني", - "WORKSPACES_NOT_FOUND": "لم يتم العثور على مساحات عمل" + "WORKSPACES_NOT_FOUND": "لم يتم العثور على مساحات عمل", + "INVALID_CODE_TRY_AGAIN": "رمز غير صالح. الرجاء المحاولة مرة أخرى." }, "authPasscode": { "HEADING_TITLE": "الانضمام إلى فريق موجود", diff --git a/apps/web/locales/bg.json b/apps/web/locales/bg.json index 83c58cb12..36390559b 100644 --- a/apps/web/locales/bg.json +++ b/apps/web/locales/bg.json @@ -294,7 +294,8 @@ "LOGIN": "Вход", "SELECT_WORKSPACE": "Избери работно пространство", "ENTER_EMAIL": "Въведи имейл", - "WORKSPACES_NOT_FOUND": "Работни пространства не са намерени" + "WORKSPACES_NOT_FOUND": "Работни пространства не са намерени", + "INVALID_CODE_TRY_AGAIN": "Невалиден код. Моля, опитайте отново." }, "authPasscode": { "HEADING_TITLE": "Присъединяване към съществуващ отбор", diff --git a/apps/web/locales/de.json b/apps/web/locales/de.json index 8fa776bd1..ed9c0c4ff 100644 --- a/apps/web/locales/de.json +++ b/apps/web/locales/de.json @@ -294,7 +294,8 @@ "LOGIN": "Anmelden", "SELECT_WORKSPACE": "Arbeitsbereich auswählen", "ENTER_EMAIL": "E-Mail-Adresse eingeben", - "WORKSPACES_NOT_FOUND": "Keine Arbeitsbereiche gefunden" + "WORKSPACES_NOT_FOUND": "Keine Arbeitsbereiche gefunden", + "INVALID_CODE_TRY_AGAIN": "Ungültiger Code. Bitte versuchen Sie es erneut." }, "authPasscode": { "HEADING_TITLE": "Bestehendem Team beitreten", diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json index 794633c52..6f320a632 100644 --- a/apps/web/locales/en.json +++ b/apps/web/locales/en.json @@ -294,7 +294,8 @@ "LOGIN": "Login", "SELECT_WORKSPACE": "Select Workspace", "ENTER_EMAIL": "Enter Email", - "WORKSPACES_NOT_FOUND": "Workspaces Not Found" + "WORKSPACES_NOT_FOUND": "Workspaces Not Found", + "INVALID_CODE_TRY_AGAIN": "Invalid code. Please try again." }, "authPasscode": { "HEADING_TITLE": "Join existing Team", diff --git a/apps/web/locales/es.json b/apps/web/locales/es.json index 0fdfbd08d..c4f8e1358 100644 --- a/apps/web/locales/es.json +++ b/apps/web/locales/es.json @@ -294,7 +294,8 @@ "LOGIN": "Iniciar sesión", "SELECT_WORKSPACE": "Seleccionar espacio de trabajo", "ENTER_EMAIL": "Ingresar correo electrónico", - "WORKSPACES_NOT_FOUND": "Espacios de trabajo no encontrados" + "WORKSPACES_NOT_FOUND": "Espacios de trabajo no encontrados", + "INVALID_CODE_TRY_AGAIN": "Código inválido. Por favor, inténtelo de nuevo." }, "authPasscode": { "HEADING_TITLE": "Unirse a equipo existente", diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json index ef2a181c8..fcef530a5 100644 --- a/apps/web/locales/fr.json +++ b/apps/web/locales/fr.json @@ -294,7 +294,8 @@ "LOGIN": "Connexion", "SELECT_WORKSPACE": "Sélectionner un espace de travail", "ENTER_EMAIL": "Saisissez votre adresse e-mail", - "WORKSPACES_NOT_FOUND": "Aucun espace de travail trouvé" + "WORKSPACES_NOT_FOUND": "Aucun espace de travail trouvé", + "INVALID_CODE_TRY_AGAIN": "Code invalide. Veuillez réessayer." }, "authPasscode": { "HEADING_TITLE": "Rejoindre une équipe existante", diff --git a/apps/web/locales/he.json b/apps/web/locales/he.json index 12b4219c8..49d34e8c2 100644 --- a/apps/web/locales/he.json +++ b/apps/web/locales/he.json @@ -294,7 +294,8 @@ "LOGIN": "התחברות", "SELECT_WORKSPACE": "בחר סביבת עבודה", "ENTER_EMAIL": "הכנס אימייל", - "WORKSPACES_NOT_FOUND": "סביבות עבודה לא נמצאו" + "WORKSPACES_NOT_FOUND": "סביבות עבודה לא נמצאו", + "INVALID_CODE_TRY_AGAIN": "קוד לא תקין. אנא נסה שוב." }, "authPasscode": { "HEADING_TITLE": "הצטרף לצוות קיים", diff --git a/apps/web/locales/it.json b/apps/web/locales/it.json index 6cc1b27e5..e70a7f627 100644 --- a/apps/web/locales/it.json +++ b/apps/web/locales/it.json @@ -294,7 +294,8 @@ "LOGIN": "Accesso", "SELECT_WORKSPACE": "Seleziona Spazio di Lavoro", "ENTER_EMAIL": "Inserisci l'Email", - "WORKSPACES_NOT_FOUND": "Spazi di Lavoro Non Trovati" + "WORKSPACES_NOT_FOUND": "Spazi di Lavoro Non Trovati", + "INVALID_CODE_TRY_AGAIN": "Codice non valido. Riprova." }, "authPasscode": { "HEADING_TITLE": "Unisciti a un Team esistente", diff --git a/apps/web/locales/nl.json b/apps/web/locales/nl.json index 534cc37df..f80b541c7 100644 --- a/apps/web/locales/nl.json +++ b/apps/web/locales/nl.json @@ -294,7 +294,8 @@ "LOGIN": "Inloggen", "SELECT_WORKSPACE": "Selecteer werkruimte", "ENTER_EMAIL": "Voer e-mail in", - "WORKSPACES_NOT_FOUND": "Werkruimtes niet gevonden" + "WORKSPACES_NOT_FOUND": "Werkruimtes niet gevonden", + "INVALID_CODE_TRY_AGAIN": "Ongeldige code. Probeer opnieuw." }, "authPasscode": { "HEADING_TITLE": "Bestaand team joinen", diff --git a/apps/web/locales/pl.json b/apps/web/locales/pl.json index f24b50e96..c0f90fc22 100644 --- a/apps/web/locales/pl.json +++ b/apps/web/locales/pl.json @@ -294,7 +294,8 @@ "LOGIN": "Zaloguj się", "SELECT_WORKSPACE": "Wybierz Przestrzeń Roboczą", "ENTER_EMAIL": "Wprowadź E-mail", - "WORKSPACES_NOT_FOUND": "Nie znaleziono Przestrzeni Roboczych" + "WORKSPACES_NOT_FOUND": "Nie znaleziono Przestrzeni Roboczych", + "INVALID_CODE_TRY_AGAIN": "Nieprawidłowy Kod. Proszę spróbować ponownie." }, "authPasscode": { "HEADING_TITLE": "Dołącz do istniejącego zespołu", diff --git a/apps/web/locales/pt.json b/apps/web/locales/pt.json index e802ad3c6..3b87fef51 100644 --- a/apps/web/locales/pt.json +++ b/apps/web/locales/pt.json @@ -294,7 +294,8 @@ "LOGIN": "Login", "SELECT_WORKSPACE": "Selecionar Espaço de Trabalho", "ENTER_EMAIL": "Inserir E-mail", - "WORKSPACES_NOT_FOUND": "Espaços de trabalho não encontrados" + "WORKSPACES_NOT_FOUND": "Espaços de trabalho não encontrados", + "INVALID_CODE_TRY_AGAIN": "Código inválido. Por favor, tente novamente." }, "authPasscode": { "HEADING_TITLE": "Participar de uma Equipe Existente", diff --git a/apps/web/locales/ru.json b/apps/web/locales/ru.json index 48cfc5d47..435e60703 100644 --- a/apps/web/locales/ru.json +++ b/apps/web/locales/ru.json @@ -294,7 +294,8 @@ "LOGIN": "Войти", "SELECT_WORKSPACE": "Выберите рабочее пространство", "ENTER_EMAIL": "Введите электронную почту", - "WORKSPACES_NOT_FOUND": "Рабочие пространства не найдены" + "WORKSPACES_NOT_FOUND": "Рабочие пространства не найдены", + "INVALID_CODE_TRY_AGAIN": "Неверный код. Пожалуйста, попробуйте еще раз." }, "authPasscode": { "HEADING_TITLE": "Присоединиться к существующей команде", diff --git a/apps/web/locales/zh.json b/apps/web/locales/zh.json index a18c9d0b9..abd251327 100644 --- a/apps/web/locales/zh.json +++ b/apps/web/locales/zh.json @@ -294,7 +294,8 @@ "LOGIN": "登录", "SELECT_WORKSPACE": "选择工作区", "ENTER_EMAIL": "输入电子邮箱", - "WORKSPACES_NOT_FOUND": "工作区未找到" + "WORKSPACES_NOT_FOUND": "工作区未找到", + "INVALID_CODE_TRY_AGAIN": "无效的代码。请重试。" }, "authPasscode": { "HEADING_TITLE": "加入已有团队", From 252492316fc47acf9c6e40f9b91af46799fdfbdc Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 07:15:37 +0000 Subject: [PATCH 13/21] Fix scroll event listener dependency in pagination hook --- apps/web/app/hooks/features/usePagination.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/hooks/features/usePagination.ts b/apps/web/app/hooks/features/usePagination.ts index 5f0967ef9..a43ba2ca4 100644 --- a/apps/web/app/hooks/features/usePagination.ts +++ b/apps/web/app/hooks/features/usePagination.ts @@ -68,7 +68,7 @@ export function useScrollPagination({ return () => { container.removeEventListener('scroll', handleScroll); }; - }, [enabled]); + }, [$scrollableElement.current, enabled]); useEffect(() => { const newItems = items.slice(0, defaultItemsPerPage * page); From 08cbb22a2cad854f49b752292613986de38d16b9 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 07:16:55 +0000 Subject: [PATCH 14/21] Fix timer reset issue on initial load --- apps/web/app/hooks/features/useTimer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/hooks/features/useTimer.ts b/apps/web/app/hooks/features/useTimer.ts index 62cafea4d..a8c0a22e4 100644 --- a/apps/web/app/hooks/features/useTimer.ts +++ b/apps/web/app/hooks/features/useTimer.ts @@ -104,7 +104,7 @@ function useLocalTimeCounter(timerStatus: ITimerStatus | null, activeTeamTask: I // THis is form constant update of the progress line timerSecondsRef.current = useMemo(() => { - // if (!firstLoad) return 0; + if (!firstLoad) return 0; if (seconds > timerSecondsRef.current) { return seconds; } @@ -112,7 +112,7 @@ function useLocalTimeCounter(timerStatus: ITimerStatus | null, activeTeamTask: I return 0; } return timerSecondsRef.current; - }, [seconds, timerStatusRef]); + }, [seconds, firstLoad, timerStatusRef]); useEffect(() => { if (firstLoad) { From f16eef596f8bafe4880896ca5251965a3a42569c Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 07:24:09 +0000 Subject: [PATCH 15/21] Optimize callback handling in AuthCodeInputField --- apps/web/lib/components/inputs/auth-code-input.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/web/lib/components/inputs/auth-code-input.tsx b/apps/web/lib/components/inputs/auth-code-input.tsx index 180db30ae..1df14a9a2 100644 --- a/apps/web/lib/components/inputs/auth-code-input.tsx +++ b/apps/web/lib/components/inputs/auth-code-input.tsx @@ -4,6 +4,7 @@ import { clsxm } from '@app/utils'; import React, { MutableRefObject, forwardRef, useState, useEffect, useImperativeHandle, useRef } from 'react'; import { InputField } from './input'; import { useTranslations } from 'next-intl'; +import { useCallbackRef } from '@app/hooks'; const allowedCharactersValues = ['alpha', 'numeric', 'alphanumeric'] as const; @@ -95,6 +96,8 @@ export const AuthCodeInputField = forwardRef( } const [canSubmit, setCanSubmit] = useState(false); const reference = useRef([]); + const $submitCode = useCallbackRef(submitCode); + const inputsRef = inputReference || reference; const inputProps = propsMap[allowedCharacters]; const validDefaultValue = @@ -126,8 +129,8 @@ export const AuthCodeInputField = forwardRef( }, [autoFocus, inputsRef]); useEffect(() => { - canSubmit && submitCode && submitCode(); - }, [canSubmit, submitCode]); + canSubmit && $submitCode.current?.(); + }, [canSubmit, $submitCode]); const sendResult = () => { const res = inputsRef.current.map((input) => input.value).join(''); From a90e9660e279b51c0278d27b096fb8b08f98ef65 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 09:41:54 +0000 Subject: [PATCH 16/21] Refactor and cleanup hooks and component logic --- apps/web/app/hooks/useDateRange.ts | 38 +++--- apps/web/app/stores/daily-plan.ts | 118 ++++++++++--------- apps/web/lib/features/user-profile-plans.tsx | 42 +++---- 3 files changed, 103 insertions(+), 95 deletions(-) diff --git a/apps/web/app/hooks/useDateRange.ts b/apps/web/app/hooks/useDateRange.ts index 4c8cfd08e..146737a74 100644 --- a/apps/web/app/hooks/useDateRange.ts +++ b/apps/web/app/hooks/useDateRange.ts @@ -1,18 +1,24 @@ -import { dateRangeAllPlanState, dateRangeFuturePlanState, dateRangePastPlanState, getFirstAndLastDateState } from "@app/stores"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { + dateRangeAllPlanState, + dateRangeFuturePlanState, + dateRangePastPlanState, + getFirstAndLastDateState +} from '@app/stores'; +import { useRecoilState, useRecoilValue } from 'recoil'; export const useDateRange = (tab: string | any) => { - const itemsDate = useRecoilValue(getFirstAndLastDateState); - const [dateFuture, setDateFuture] = useRecoilState(dateRangeFuturePlanState); - const [dateAllPlan, setDateAllPlan] = useRecoilState(dateRangeAllPlanState); - const [datePastPlan, setDatePastPlan] = useRecoilState(dateRangePastPlanState); - switch (tab) { - case 'Future Tasks': - return { date: dateFuture, setDate: setDateFuture, data: itemsDate }; - case 'Past Tasks': - return { date: datePastPlan, setDate: setDatePastPlan, data: itemsDate }; - case 'All Tasks': - default: - return { date: dateAllPlan, setDate: setDateAllPlan, data: itemsDate }; - } -} + const itemsDate = useRecoilValue(getFirstAndLastDateState); + const [dateFuture, setDateFuture] = useRecoilState(dateRangeFuturePlanState); + const [dateAllPlan, setDateAllPlan] = useRecoilState(dateRangeAllPlanState); + const [datePastPlan, setDatePastPlan] = useRecoilState(dateRangePastPlanState); + + switch (tab) { + case 'Future Tasks': + return { date: dateFuture, setDate: setDateFuture, data: itemsDate }; + case 'Past Tasks': + return { date: datePastPlan, setDate: setDatePastPlan, data: itemsDate }; + case 'All Tasks': + default: + return { date: dateAllPlan, setDate: setDateAllPlan, data: itemsDate }; + } +}; diff --git a/apps/web/app/stores/daily-plan.ts b/apps/web/app/stores/daily-plan.ts index af57cd9da..95f5c0e59 100644 --- a/apps/web/app/stores/daily-plan.ts +++ b/apps/web/app/stores/daily-plan.ts @@ -3,7 +3,6 @@ import { IDailyPlan, PaginationResponse } from '@app/interfaces'; import { DateRange } from 'react-day-picker'; import { isTestDateRange } from '@app/helpers'; - export const dailyPlanListState = atom>({ key: 'dailyPlanListState', default: { items: [], total: 0 } @@ -47,70 +46,81 @@ export const activeDailyPlanState = selector({ return dailyPlans.items.find((plan) => plan.id === activeId) || dailyPlans.items[0] || null; } }); -const createDailyPlanCountFilterAtom = (key: string | any) => atom( - { + +const createDailyPlanCountFilterAtom = (key: string | any) => + atom({ key, default: 0 - } -) + }); -const createDailyPlanAtom = (key: string | any) => atom({ - key, - default: [], -}); +const createDailyPlanAtom = (key: string | any) => + atom({ + key, + default: [] + }); -const createDateRangeAtom = (key: string | any) => atom({ - key, - default: { from: undefined, to: undefined } -}); +const createDateRangeAtom = (key: string | any) => + atom({ + key, + default: { from: undefined, to: undefined } + }); export const dataDailyPlanState = createDailyPlanAtom('originalPlanState'); -const getFirstAndLastDateSelector = (key: string | any) => selector({ - key, - get: ({ get }) => { - const itemsData = get(dataDailyPlanState); - if (!itemsData?.length) return { from: null, to: null }; - const sortedData = itemsData?.slice().sort((a, b) => new Date(a.date)?.getTime() - new Date(b?.date).getTime()); - return { from: new Date(sortedData[0]?.date), to: new Date(sortedData[sortedData.length - 1]?.date) }; - } -}); -export const dateRangeDailyPlanState = createDateRangeAtom('dateRangeDailyPlanState') - -const createFilteredDailyPlanDataSelector = (key: string | any, dateRangeState: RecoilState, originalDataState: RecoilState) => selector({ - key, - get: ({ get }) => { - const dateRange = get(dateRangeState); - const data = get(originalDataState); - if (!dateRange || !data.length) return data; - const { from, to } = dateRange; - if (!from && !to) { - return data +const getFirstAndLastDateSelector = (key: string | any) => + selector({ + key, + get: ({ get }) => { + const itemsData = get(dataDailyPlanState); + if (!itemsData?.length) return { from: null, to: null }; + const sortedData = itemsData + ?.slice() + .sort((a, b) => new Date(a.date)?.getTime() - new Date(b?.date).getTime()); + return { from: new Date(sortedData[0]?.date), to: new Date(sortedData[sortedData.length - 1]?.date) }; } - return data.filter((plan) => { - const itemDate = new Date(plan.date); - return isTestDateRange(itemDate, from, to); - }); - }, -}); + }); +export const dateRangeDailyPlanState = createDateRangeAtom('dateRangeDailyPlanState'); + +const createFilteredDailyPlanDataSelector = ( + key: string | any, + dateRangeState: RecoilState, + originalDataState: RecoilState +) => + selector({ + key, + get: ({ get }) => { + const dateRange = get(dateRangeState); + const data = get(originalDataState); + if (!dateRange || !data.length) return data; + const { from, to } = dateRange; + if (!from && !to) { + return data; + } + return data.filter((plan) => { + const itemDate = new Date(plan.date); + return isTestDateRange(itemDate, from, to); + }); + } + }); export const dataDailyPlanAllFilterState = createDailyPlanAtom('dataDailyPlanAllFilterState'); export const dateRangeAllPlanState = createDateRangeAtom('dateRangeAllPlanState'); -export const setCreateFilteredDailyPlanDataSelector = () => selector({ - key: 'dataDailyPlanAllFilter', - get: ({ get }) => { - const dateRange = get(dateRangeAllPlanState); - const data = get(dataDailyPlanAllFilterState); - if (!dateRange || !data.length) return data; - const { from, to } = dateRange; - if (!from && !to) { - return data +export const setCreateFilteredDailyPlanDataSelector = () => + selector({ + key: 'dataDailyPlanAllFilter', + get: ({ get }) => { + const dateRange = get(dateRangeAllPlanState); + const data = get(dataDailyPlanAllFilterState); + if (!dateRange || !data.length) return data; + const { from, to } = dateRange; + if (!from && !to) { + return data; + } + return data.filter((plan) => { + const itemDate = new Date(plan.date); + return isTestDateRange(itemDate, from, to); + }); } - return data.filter((plan) => { - const itemDate = new Date(plan.date); - return isTestDateRange(itemDate, from, to); - }); - }, -}); + }); export const dataDailyPlanCountFilterState = createDailyPlanCountFilterAtom('dataDailyPlanCountFilterState'); export const dateRangePastPlanState = createDateRangeAtom('dateRangePastPlanState'); diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx index 92a147e9c..311f3bac7 100644 --- a/apps/web/lib/features/user-profile-plans.tsx +++ b/apps/web/lib/features/user-profile-plans.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { useAuthenticateUser, useCanSeeActivityScreen, useDailyPlan, useUserProfilePage } from '@app/hooks'; import { TaskCard } from './task/task-card'; @@ -49,7 +49,6 @@ export function UserProfilePlans() { const [currentTab, setCurrentTab] = useLocalStorageState('daily-plan-tab', 'Today Tasks'); - const [currentDataDailyPlan, setCurrentDataDailyPlan] = useRecoilState(dataDailyPlanState); const { setDate, date } = useDateRange(currentTab); @@ -91,16 +90,7 @@ export function UserProfilePlans() { setCurrentDataDailyPlan(futurePlans); setFilterFuturePlanData(filterDailyPlan(date as any, futurePlans)); } - }, [currentTab, - setCurrentDataDailyPlan, - setDate, - date, - currentDataDailyPlan, - futurePlans, - pastPlans, - sortedPlans - ] - ); + }, [currentTab, setCurrentDataDailyPlan, setDate, date, currentDataDailyPlan, futurePlans, pastPlans, sortedPlans]); return (
    @@ -190,22 +180,26 @@ export function UserProfilePlans() { */ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; currentTab?: FilterTabs }) { // Filter plans - let filteredPlans: IDailyPlan[] = []; + const filteredPlans = useRef([]); const { deleteDailyPlan, deleteDailyPlanLoading, sortedPlans, todayPlan } = useDailyPlan(); const [popupOpen, setPopupOpen] = useState(false); const [currentDeleteIndex, setCurrentDeleteIndex] = useState(0); - const { setDate, date } = useDateRange(currentTab); + const { date } = useDateRange(currentTab); - filteredPlans = sortedPlans; - if (currentTab === 'Today Tasks') filteredPlans = todayPlan; + if (currentTab === 'Today Tasks') { + filteredPlans.current = todayPlan; + } else { + filteredPlans.current = sortedPlans; + } const canSeeActivity = useCanSeeActivityScreen(); const view = useRecoilValue(dailyPlanViewHeaderTabs); - const [plans, setPlans] = useState(filteredPlans); + const [plans, setPlans] = useState(filteredPlans.current); + useEffect(() => { - setPlans(filterDailyPlan(date as any, filteredPlans)); - }, [date, setDate, filteredPlans]); + setPlans(filterDailyPlan(date as any, filteredPlans.current)); + }, [date, filteredPlans.current]); return (
    @@ -373,7 +367,7 @@ export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: Fil const [time, setTime] = useState(0); const { updateDailyPlan, updateDailyPlanLoading } = useDailyPlan(); const { isTeamManager } = useAuthenticateUser(); - const t = useTranslations() + const t = useTranslations(); // Get all tasks's estimations time // Helper function to sum times const sumTimes = (tasks: ITeamTask[], key: any) => @@ -384,7 +378,7 @@ export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: Fil // Get all tasks' estimation and worked times const estimatedTime = plan.tasks ? sumTimes(plan.tasks, 'estimate') : 0; - const totalWorkTime = plan.tasks ? sumTimes(plan.tasks, 'totalWorkedTime') : 0; + const totalWorkTime = plan.tasks ? sumTimes(plan.tasks, 'totalWorkedTime') : 0; // Get completed and ready tasks from a plan const completedTasks = plan.tasks?.filter((task) => task.status === 'completed').length ?? 0; @@ -508,14 +502,12 @@ export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: Fil } export function EmptyPlans({ planMode }: { planMode?: FilterTabs }) { - const t = useTranslations() + const t = useTranslations(); return (
    } />
    From 0e1317887f8aeeae7548b37492de005a165faf49 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 09:51:37 +0000 Subject: [PATCH 17/21] Set default prop values directly in function signatures --- apps/web/components/layout/Meta.tsx | 8 +------- apps/web/components/ui/inputs/input.tsx | 6 +----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/web/components/layout/Meta.tsx b/apps/web/components/layout/Meta.tsx index c61bb0d65..7a2429008 100644 --- a/apps/web/components/layout/Meta.tsx +++ b/apps/web/components/layout/Meta.tsx @@ -1,7 +1,7 @@ import Head from 'next/head'; import { MetaProps } from '../../app/interfaces/hooks'; -const Meta = ({ title, keywords, description }: MetaProps) => { +const Meta = ({ title = 'Gauzy Teams', keywords = '', description = '' }: MetaProps) => { return ( @@ -11,10 +11,4 @@ const Meta = ({ title, keywords, description }: MetaProps) => { ); }; -Meta.defaultProps = { - title: 'Gauzy Teams', - keywords: '', - description: '' -}; - export default Meta; diff --git a/apps/web/components/ui/inputs/input.tsx b/apps/web/components/ui/inputs/input.tsx index b7428d03e..803496863 100644 --- a/apps/web/components/ui/inputs/input.tsx +++ b/apps/web/components/ui/inputs/input.tsx @@ -3,7 +3,7 @@ import { IInputProps } from '@app/interfaces/hooks'; const Input = ({ label, name, - type, + type = 'text', placeholder, required, onChange, @@ -40,8 +40,4 @@ const Input = ({ ); }; -Input.defaultProps = { - type: 'text' -}; - export default Input; From 11427a0143c1fca1a8a9227c90e7ad3c43f17b97 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 16:24:20 +0000 Subject: [PATCH 18/21] Optimize performance with useMemo & cleanup hooks Enhanced performance by wrapping heavy computations and sort/filter operations with `useMemo` in `useDailyPlan` and `useUserProfilePage` to avoid unnecessary recalculations on re-renders. Refined hook dependencies and cleaned up redundant `useState` and `useEffect` usages to streamline state management logic. Adjusted drag-and-drop handlers for simpler plan updates. --- apps/web/app/helpers/drag-and-drop.ts | 70 ++++----- apps/web/app/hooks/features/useDailyPlan.ts | 136 ++++++++++-------- .../app/hooks/features/useUserProfilePage.ts | 10 +- .../features/task/daily-plan/past-tasks.tsx | 5 +- apps/web/lib/features/user-profile-plans.tsx | 10 +- 5 files changed, 122 insertions(+), 109 deletions(-) diff --git a/apps/web/app/helpers/drag-and-drop.ts b/apps/web/app/helpers/drag-and-drop.ts index 8c51396b4..639766aca 100644 --- a/apps/web/app/helpers/drag-and-drop.ts +++ b/apps/web/app/helpers/drag-and-drop.ts @@ -1,48 +1,52 @@ -import { IDailyPlan, ITeamTask } from "@app/interfaces"; -import { DropResult } from "react-beautiful-dnd"; +import { IDailyPlan, ITeamTask } from '@app/interfaces'; +import { DropResult } from 'react-beautiful-dnd'; -export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setPlans: React.Dispatch>) => { - const { source, destination } = results; +export const handleDragAndDrop = ( + results: DropResult, + plans: IDailyPlan[], + setPlans: React.Dispatch> +) => { + const { source, destination } = results; - if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; + if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; - const newPlans = [...plans]; + const newPlans = [...plans]; - const planSourceIndex = newPlans.findIndex(plan => plan.id === source.droppableId); - const planDestinationIndex = newPlans.findIndex(plan => plan.id === destination.droppableId); + const planSourceIndex = newPlans.findIndex((plan) => plan.id === source.droppableId); + const planDestinationIndex = newPlans.findIndex((plan) => plan.id === destination.droppableId); - const newSourceTasks = [...(newPlans[planSourceIndex].tasks ?? [])]; - const newDestinationTasks = source.droppableId !== destination.droppableId - ? [...(newPlans[planDestinationIndex].tasks ?? [])] - : newSourceTasks; + const newSourceTasks = [...(newPlans[planSourceIndex].tasks ?? [])]; + const newDestinationTasks = + source.droppableId !== destination.droppableId + ? [...(newPlans[planDestinationIndex].tasks ?? [])] + : newSourceTasks; - const [deletedTask] = newSourceTasks.splice(source.index, 1); - newDestinationTasks.splice(destination.index, 0, deletedTask); + const [deletedTask] = newSourceTasks.splice(source.index, 1); + newDestinationTasks.splice(destination.index, 0, deletedTask); - newPlans[planSourceIndex] = { - ...newPlans[planSourceIndex], - tasks: newSourceTasks, - }; - newPlans[planDestinationIndex] = { - ...newPlans[planDestinationIndex], - tasks: newDestinationTasks, - }; - setPlans(newPlans); + newPlans[planSourceIndex] = { + ...newPlans[planSourceIndex], + tasks: newSourceTasks + }; + newPlans[planDestinationIndex] = { + ...newPlans[planDestinationIndex], + tasks: newDestinationTasks + }; + setPlans(newPlans); }; - export const handleDragAndDropDailyOutstandingAll = ( - results: DropResult, - tasks: ITeamTask[], - setTasks: React.Dispatch> + results: DropResult, + tasks: ITeamTask[], + setTasks: React.Dispatch> ) => { - const { source, destination } = results; + const { source, destination } = results; - if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; + if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return; - const newTasks = [...tasks]; - const [movedTask] = newTasks.splice(source.index, 1); - newTasks.splice(destination.index, 0, movedTask); + const newTasks = [...tasks]; + const [movedTask] = newTasks.splice(source.index, 1); + newTasks.splice(destination.index, 0, movedTask); - setTasks(newTasks); + setTasks(newTasks); }; diff --git a/apps/web/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts index c8a561547..a8be0524c 100644 --- a/apps/web/app/hooks/features/useDailyPlan.ts +++ b/apps/web/app/hooks/features/useDailyPlan.ts @@ -1,7 +1,7 @@ 'use client'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useQuery } from '../useQuery'; import { activeTeamState, @@ -269,77 +269,87 @@ export function useDailyPlan() { ] ); - const ascSortedPlans = - profileDailyPlans.items && - [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); - const futurePlans = ascSortedPlans?.filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization - return planDate.getTime() >= today.getTime(); - }); - - const descSortedPlans = - profileDailyPlans.items && - [...profileDailyPlans.items].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - const pastPlans = descSortedPlans?.filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(0, 0, 0, 0); // Set today time to exclude timestamps in comparization - return planDate.getTime() < today.getTime(); - }); - - const todayPlan = - profileDailyPlans.items && - [...profileDailyPlans.items].filter((plan) => + const ascSortedPlans = useMemo(() => { + return [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + }, [profileDailyPlans]); + + const futurePlans = useMemo(() => { + return ascSortedPlans?.filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization + return planDate.getTime() >= today.getTime(); + }); + }, [ascSortedPlans]); + + const descSortedPlans = useMemo(() => { + return [...profileDailyPlans.items].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + }, [profileDailyPlans]); + + const pastPlans = useMemo(() => { + return descSortedPlans?.filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(0, 0, 0, 0); // Set today time to exclude timestamps in comparization + return planDate.getTime() < today.getTime(); + }); + }, [descSortedPlans]); + + const todayPlan = useMemo(() => { + return [...profileDailyPlans.items].filter((plan) => plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0]) ); + }, [profileDailyPlans]); - const todayTasks = todayPlan - .map((plan) => { - return plan.tasks ? plan.tasks : []; - }) - .flat(); - - const futureTasks = - futurePlans && - futurePlans + const todayTasks = useMemo(() => { + return todayPlan .map((plan) => { return plan.tasks ? plan.tasks : []; }) .flat(); + }, [todayPlan]); - const outstandingPlans = - profileDailyPlans.items && - [...profileDailyPlans.items] - // Exclude today plans - .filter((plan) => !plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0])) - - // Exclude future plans - .filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization - return planDate.getTime() <= today.getTime(); + const futureTasks = useMemo(() => { + return futurePlans + .map((plan) => { + return plan.tasks ? plan.tasks : []; }) - .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) - .map((plan) => ({ - ...plan, - // Include only no completed tasks - tasks: plan.tasks?.filter((task) => task.status !== 'completed') - })) - .map((plan) => ({ - ...plan, - // Include only tasks that are not added yet to the today plan or future plans - tasks: plan.tasks?.filter( - (_task) => ![...todayTasks, ...futureTasks].find((task) => task.id === _task.id) - ) - })) - .filter((plan) => plan.tasks?.length && plan.tasks.length > 0); - - const sortedPlans = - profileDailyPlans.items && - [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + .flat(); + }, [futurePlans]); + + const outstandingPlans = useMemo(() => { + return ( + [...profileDailyPlans.items] + // Exclude today plans + .filter((plan) => !plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0])) + + // Exclude future plans + .filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(23, 59, 59, 0); // Set today time to exclude timestamps in comparization + return planDate.getTime() <= today.getTime(); + }) + .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) + .map((plan) => ({ + ...plan, + // Include only no completed tasks + tasks: plan.tasks?.filter((task) => task.status !== 'completed') + })) + .map((plan) => ({ + ...plan, + // Include only tasks that are not added yet to the today plan or future plans + tasks: plan.tasks?.filter( + (_task) => ![...todayTasks, ...futureTasks].find((task) => task.id === _task.id) + ) + })) + .filter((plan) => plan.tasks?.length && plan.tasks.length > 0) + ); + }, [profileDailyPlans, todayTasks, futureTasks]); + + const sortedPlans = useMemo(() => { + return [...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + }, [profileDailyPlans]); useEffect(() => { if (firstLoad) { diff --git a/apps/web/app/hooks/features/useUserProfilePage.ts b/apps/web/app/hooks/features/useUserProfilePage.ts index 90303976a..74d5cf3fe 100644 --- a/apps/web/app/hooks/features/useUserProfilePage.ts +++ b/apps/web/app/hooks/features/useUserProfilePage.ts @@ -23,11 +23,13 @@ export function useUserProfilePage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [params, userMemberId]); - const members = activeTeam?.members || []; + const members = useMemo(() => activeTeam?.members || [], [activeTeam]); - const matchUser = members.find((m) => { - return m.employee.userId === memberId; - }); + const matchUser = useMemo(() => { + return members.find((m) => { + return m.employee.userId === memberId; + }); + }, [members, memberId]); const isAuthUser = auth?.employee?.userId === memberId; diff --git a/apps/web/lib/features/task/daily-plan/past-tasks.tsx b/apps/web/lib/features/task/daily-plan/past-tasks.tsx index 7bb21a5b6..f977a0c60 100644 --- a/apps/web/lib/features/task/daily-plan/past-tasks.tsx +++ b/apps/web/lib/features/task/daily-plan/past-tasks.tsx @@ -19,11 +19,12 @@ export function PastTasks({ profile, currentTab = 'Past Tasks' }: { profile: any const view = useRecoilValue(dailyPlanViewHeaderTabs); const [pastTasks, setPastTasks] = useState(pastPlans); - const { setDate, date } = useDateRange(window.localStorage.getItem('daily-plan-tab')); + const { date } = useDateRange(window.localStorage.getItem('daily-plan-tab')); useEffect(() => { setPastTasks(filterDailyPlan(date as any, pastPlans)); - }, [date, setDate, pastPlans]); + }, [date, pastPlans]); + return (
    {pastTasks?.length > 0 ? ( diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx index 311f3bac7..655f3040e 100644 --- a/apps/web/lib/features/user-profile-plans.tsx +++ b/apps/web/lib/features/user-profile-plans.tsx @@ -90,7 +90,7 @@ export function UserProfilePlans() { setCurrentDataDailyPlan(futurePlans); setFilterFuturePlanData(filterDailyPlan(date as any, futurePlans)); } - }, [currentTab, setCurrentDataDailyPlan, setDate, date, currentDataDailyPlan, futurePlans, pastPlans, sortedPlans]); + }, [currentTab, setCurrentDataDailyPlan, date, currentDataDailyPlan, futurePlans, pastPlans, sortedPlans]); return (
    @@ -195,16 +195,12 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current const canSeeActivity = useCanSeeActivityScreen(); const view = useRecoilValue(dailyPlanViewHeaderTabs); - const [plans, setPlans] = useState(filteredPlans.current); - - useEffect(() => { - setPlans(filterDailyPlan(date as any, filteredPlans.current)); - }, [date, filteredPlans.current]); + const plans = filterDailyPlan(date as any, filteredPlans.current); return (
    {Array.isArray(plans) && plans?.length > 0 ? ( - handleDragAndDrop(result, plans, setPlans)}> + handleDragAndDrop(result, plans, () => {})}> Date: Mon, 9 Sep 2024 17:11:29 +0000 Subject: [PATCH 19/21] Optimize state and memoize data in hooks --- apps/web/app/helpers/plan-day-badge.ts | 31 ++++++------ .../features/useOrganizationTeamManagers.ts | 14 ++++-- apps/web/app/hooks/features/useTaskInput.ts | 18 ++++--- .../features/task/task-all-status-type.tsx | 2 +- .../lib/features/task/task-input-kanban.tsx | 50 ++++++++++--------- apps/web/lib/features/task/task-input.tsx | 50 ++++++++++--------- apps/web/lib/features/task/task-issue.tsx | 49 ++++++++++-------- apps/web/lib/features/team-members.tsx | 24 +++++---- apps/web/lib/features/user-profile-plans.tsx | 35 ++++++++----- apps/web/lib/settings/member-setting.tsx | 17 ++++--- 10 files changed, 167 insertions(+), 123 deletions(-) diff --git a/apps/web/app/helpers/plan-day-badge.ts b/apps/web/app/helpers/plan-day-badge.ts index 1bd23d610..409e350e7 100644 --- a/apps/web/app/helpers/plan-day-badge.ts +++ b/apps/web/app/helpers/plan-day-badge.ts @@ -29,26 +29,25 @@ export const planBadgeContent = ( } }; - -export const planBadgeContPast = ( - dailyPlan: IDailyPlan[], - taskId: ITeamTask['id'] -): string | null => { +export const planBadgeContPast = (dailyPlan: IDailyPlan[], taskId: ITeamTask['id']): string | null => { const today = new Date().toISOString().split('T')[0]; - const dailyPlanDataPast = dailyPlan.filter(plan => new Date(plan.date) < new Date(today)); - const allTasks = dailyPlanDataPast.flatMap(plan => plan.tasks); - const taskCount: { [key: string]: number } = allTasks?.reduce((acc, task) => { - if (task && task.id) { acc[task.id] = (acc[task.id] || 0) + 1; } - return acc; - }, {} as { [key: string]: number }); - - const dailyPlanPast = allTasks?.filter(task => task && taskCount[task.id] === 1); + const dailyPlanDataPast = dailyPlan.filter((plan) => new Date(plan.date) < new Date(today)); + const allTasks = dailyPlanDataPast.flatMap((plan) => plan.tasks); + const taskCount: { [key: string]: number } = allTasks?.reduce( + (acc, task) => { + if (task && task.id) { + acc[task.id] = (acc[task.id] || 0) + 1; + } + return acc; + }, + {} as { [key: string]: number } + ); + + const dailyPlanPast = allTasks?.filter((task) => task && taskCount[task.id] === 1); const filterDailyPlan = dailyPlanPast.filter((plan) => plan?.id === taskId); if (filterDailyPlan.length > 0) { return 'Planned'; } else { return null; } - - -} +}; diff --git a/apps/web/app/hooks/features/useOrganizationTeamManagers.ts b/apps/web/app/hooks/features/useOrganizationTeamManagers.ts index edaab309e..b379bd566 100644 --- a/apps/web/app/hooks/features/useOrganizationTeamManagers.ts +++ b/apps/web/app/hooks/features/useOrganizationTeamManagers.ts @@ -2,18 +2,21 @@ import { useRecoilValue } from 'recoil'; import { useAuthenticateUser } from './useAuthenticateUser'; import { useOrganizationTeams } from './useOrganizationTeams'; import { filterValue } from '@app/stores/all-teams'; +import { useMemo } from 'react'; export function useOrganizationAndTeamManagers() { const { user } = useAuthenticateUser(); const { teams } = useOrganizationTeams(); const { value: filtered } = useRecoilValue(filterValue); - const userManagedTeams = teams.filter((team) => - team.members.some((member) => member.employee?.user?.id === user?.id && member.role?.name === 'MANAGER') - ); + const userManagedTeams = useMemo(() => { + return teams.filter((team) => + team.members.some((member) => member.employee?.user?.id === user?.id && member.role?.name === 'MANAGER') + ); + }, [teams, user]); - const filteredTeams = - filtered === 'all' + const filteredTeams = useMemo(() => { + return filtered === 'all' ? userManagedTeams : filtered === 'pause' ? userManagedTeams.map((team) => ({ @@ -36,6 +39,7 @@ export function useOrganizationAndTeamManagers() { members: team.members.filter((member) => member.employee.acceptDate) })) : userManagedTeams; + }, [filtered, userManagedTeams]); return { userManagedTeams, diff --git a/apps/web/app/hooks/features/useTaskInput.ts b/apps/web/app/hooks/features/useTaskInput.ts index 241ca8f18..bb204f8f6 100644 --- a/apps/web/app/hooks/features/useTaskInput.ts +++ b/apps/web/app/hooks/features/useTaskInput.ts @@ -170,13 +170,17 @@ export function useTaskInput({ [updateTask, userRef] ); - const closedTaskCount = filteredTasks2.filter((f_task) => { - return f_task.status === 'closed'; - }).length; - - const openTaskCount = filteredTasks2.filter((f_task) => { - return f_task.status !== 'closed'; - }).length; + const closedTaskCount = useMemo(() => { + return filteredTasks2.filter((f_task) => { + return f_task.status === 'closed'; + }).length; + }, [filteredTasks2]); + + const openTaskCount = useMemo(() => { + return filteredTasks2.filter((f_task) => { + return f_task.status !== 'closed'; + }).length; + }, [filteredTasks2]); useEffect(() => { setTaskIssue(''); 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 f4cc77544..979871820 100644 --- a/apps/web/lib/features/task/task-all-status-type.tsx +++ b/apps/web/lib/features/task/task-all-status-type.tsx @@ -66,7 +66,7 @@ export function TaskAllStatusTypes({ ); }, [taskLabels, task?.tags]); - const taskId = task? planBadgeContPast(dailyPlan.items, task.id) : ''; + const taskId = task ? planBadgeContPast(dailyPlan.items, task.id) : ''; return (
    diff --git a/apps/web/lib/features/task/task-input-kanban.tsx b/apps/web/lib/features/task/task-input-kanban.tsx index f87d3fd1f..3b16487d0 100644 --- a/apps/web/lib/features/task/task-input-kanban.tsx +++ b/apps/web/lib/features/task/task-input-kanban.tsx @@ -171,31 +171,35 @@ export function TaskInputKanban(props: Props) { }); }, [datas, props, 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; - } + const updatedTaskList = useMemo(() => { + 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)); + 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)); + } } - } + + return updatedTaskList; + }, [props.task, datas.filteredTasks]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { diff --git a/apps/web/lib/features/task/task-input.tsx b/apps/web/lib/features/task/task-input.tsx index 96f2786bb..78aa0118e 100644 --- a/apps/web/lib/features/task/task-input.tsx +++ b/apps/web/lib/features/task/task-input.tsx @@ -226,31 +226,35 @@ export function TaskInput(props: Props) { }); }, [datas, props, autoActiveTask, onTaskCreated, viewType]); - 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; - } + const updatedTaskList = useMemo(() => { + 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)); + 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)); + } } - } + + return updatedTaskList; + }, [props.task, datas.filteredTasks]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { diff --git a/apps/web/lib/features/task/task-issue.tsx b/apps/web/lib/features/task/task-issue.tsx index c68719934..2f98bcae1 100644 --- a/apps/web/lib/features/task/task-issue.tsx +++ b/apps/web/lib/features/task/task-issue.tsx @@ -14,6 +14,7 @@ import { useStatusValue } from './task-status'; import { useTranslations } from 'next-intl'; +import { useMemo } from 'react'; export const taskIssues: TStatus = { Bug: { @@ -99,30 +100,38 @@ export function ActiveTaskIssuesDropdown({ ...props }: IActiveTaskStatuses<'issu const t = useTranslations(); const { item, items, onChange, field } = useActiveTaskStatus(props, taskIssues, 'issueType'); - const validTransitions: Record = { - [IssueType.EPIC]: [], - [IssueType.STORY]: items.filter((it) => [IssueType.TASK, IssueType.BUG].includes(it.value as IssueType)), + const validTransitions: Record = useMemo( + () => ({ + [IssueType.EPIC]: [], + [IssueType.STORY]: items.filter((it) => [IssueType.TASK, IssueType.BUG].includes(it.value as IssueType)), - [IssueType.TASK]: items.filter((it) => [IssueType.STORY, IssueType.BUG].includes(it.value as IssueType)), + [IssueType.TASK]: items.filter((it) => [IssueType.STORY, IssueType.BUG].includes(it.value as IssueType)), - [IssueType.BUG]: items.filter((it) => [IssueType.STORY, IssueType.TASK].includes(it.value as IssueType)) - }; - - let updatedItemsBasedOnTaskIssueType: TStatusItem[] = []; - - if (props.task && props.task?.issueType && props.task.parent) { - updatedItemsBasedOnTaskIssueType = validTransitions[props.task?.issueType]; + [IssueType.BUG]: items.filter((it) => [IssueType.STORY, IssueType.TASK].includes(it.value as IssueType)) + }), + [items] + ); - // If parent task is already Story then user can not assign current task as a Story - if (props.task.parent.issueType === 'Story') { - updatedItemsBasedOnTaskIssueType = updatedItemsBasedOnTaskIssueType.filter((it) => it.value !== 'Story'); + const updatedItemsBasedOnTaskIssueType = useMemo(() => { + let updatedItemsBasedOnTaskIssueType: TStatusItem[] = []; + if (props.task && props.task?.issueType && props.task.parent) { + updatedItemsBasedOnTaskIssueType = validTransitions[props.task?.issueType]; + + // If parent task is already Story then user can not assign current task as a Story + if (props.task.parent.issueType === 'Story') { + updatedItemsBasedOnTaskIssueType = updatedItemsBasedOnTaskIssueType.filter( + (it) => it.value !== 'Story' + ); + } + } else if (props.task && props.task?.issueType) { + updatedItemsBasedOnTaskIssueType = validTransitions[props.task?.issueType]; + } else { + // Default show types in Dropdown + updatedItemsBasedOnTaskIssueType = items; } - } else if (props.task && props.task?.issueType) { - updatedItemsBasedOnTaskIssueType = validTransitions[props.task?.issueType]; - } else { - // Default show types in Dropdown - updatedItemsBasedOnTaskIssueType = items; - } + + return updatedItemsBasedOnTaskIssueType; + }, [props.task, items, validTransitions]); return ( member.employee !== null); + const members = (activeTeam?.members || []).filter((member) => member.employee !== null); const orderedMembers = [...members].sort((a, b) => (sortByWorkStatus(a, b) ? -1 : 1)); - const blockViewMembers = - activeFilter == 'all' + const blockViewMembers = useMemo(() => { + return activeFilter == 'all' ? orderedMembers : activeFilter == 'idle' ? orderedMembers.filter((m: OT_Member) => m.timerStatus == undefined || m.timerStatus == 'idle') : orderedMembers.filter((m) => m.timerStatus === activeFilter); + }, [activeFilter, orderedMembers]); const currentUser = members.find((m) => m.employee.userId === user?.id); - const $members = members - .filter((member) => member.id !== currentUser?.id) - .sort((a, b) => { - if (a.order && b.order) return a.order > b.order ? -1 : 1; - else return -1; - }); + + const $members = useMemo(() => { + return members + .filter((member) => member.id !== currentUser?.id) + .sort((a, b) => { + if (a.order && b.order) return a.order > b.order ? -1 : 1; + else return -1; + }); + }, [members, currentUser]); + const $teamsFetching = teamsFetching && members.length === 0; let teamMembersView; diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx index 655f3040e..bfd754d6a 100644 --- a/apps/web/lib/features/user-profile-plans.tsx +++ b/apps/web/lib/features/user-profile-plans.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { useAuthenticateUser, useCanSeeActivityScreen, useDailyPlan, useUserProfilePage } from '@app/hooks'; import { TaskCard } from './task/task-card'; @@ -195,12 +195,16 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current const canSeeActivity = useCanSeeActivityScreen(); const view = useRecoilValue(dailyPlanViewHeaderTabs); - const plans = filterDailyPlan(date as any, filteredPlans.current); + const [plans, setPlans] = useState(filteredPlans.current); + + useEffect(() => { + setPlans(filterDailyPlan(date as any, filteredPlans.current)); + }, [date, filteredPlans.current]); return (
    {Array.isArray(plans) && plans?.length > 0 ? ( - handleDragAndDrop(result, plans, () => {})}> + handleDragAndDrop(result, plans, setPlans)}> - tasks - ?.map((task: any) => task[key]) - .filter((time): time is number => typeof time === 'number') - .reduce((acc, cur) => acc + cur, 0) ?? 0; + const sumTimes = useCallback((tasks: ITeamTask[], key: any) => { + return ( + tasks + ?.map((task: any) => task[key]) + .filter((time): time is number => typeof time === 'number') + .reduce((acc, cur) => acc + cur, 0) ?? 0 + ); + }, []); // Get all tasks' estimation and worked times - const estimatedTime = plan.tasks ? sumTimes(plan.tasks, 'estimate') : 0; - const totalWorkTime = plan.tasks ? sumTimes(plan.tasks, 'totalWorkedTime') : 0; + const estimatedTime = useMemo(() => (plan.tasks ? sumTimes(plan.tasks, 'estimate') : 0), [plan.tasks]); + const totalWorkTime = useMemo(() => (plan.tasks ? sumTimes(plan.tasks, 'totalWorkedTime') : 0), [plan.tasks]); // Get completed and ready tasks from a plan - const completedTasks = plan.tasks?.filter((task) => task.status === 'completed').length ?? 0; - const readyTasks = plan.tasks?.filter((task) => task.status === 'ready').length ?? 0; + const completedTasks = useMemo( + () => plan.tasks?.filter((task) => task.status === 'completed').length ?? 0, + [plan.tasks] + ); + + const readyTasks = useMemo(() => plan.tasks?.filter((task) => task.status === 'ready').length ?? 0, [plan.tasks]); // Total tasks for the plan const totalTasks = plan.tasks?.length ?? 0; diff --git a/apps/web/lib/settings/member-setting.tsx b/apps/web/lib/settings/member-setting.tsx index 247edc5c8..d270f32fb 100644 --- a/apps/web/lib/settings/member-setting.tsx +++ b/apps/web/lib/settings/member-setting.tsx @@ -2,7 +2,7 @@ import { useAuthenticateUser, useModal, useOrganizationTeams } from '@app/hooks' import { Button, InputField, NoData, Text } from 'lib/components'; import { SearchNormalIcon } from 'assets/svg'; import { InviteFormModal } from 'lib/features/team/invite/invite-form-modal'; -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, useMemo, useState } from 'react'; import { useTranslations } from 'next-intl'; import { MemberTable } from './member-table'; @@ -15,12 +15,15 @@ export const MemberSetting = () => { const { user } = useAuthenticateUser(); const { isOpen, closeModal } = useModal(); - const members = - activeTeam?.members.filter( - (member) => - member.employee.fullName.toLowerCase().includes(filterString) || - member.employee.user?.email.toLowerCase().includes(filterString) - ) || []; + const members = useMemo(() => { + return ( + activeTeam?.members.filter( + (member) => + member.employee.fullName.toLowerCase().includes(filterString) || + member.employee.user?.email.toLowerCase().includes(filterString) + ) || [] + ); + }, [activeTeam, filterString]); return (
    From 60e89161aaa87bf63f854f569f9c30f79e98c5f0 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 17:21:46 +0000 Subject: [PATCH 20/21] Refactor auth hooks and clean up invite modals --- .../hooks/auth/useAuthenticationPasscode.ts | 49 ++++++++++--------- apps/web/app/hooks/features/usePagination.ts | 2 + apps/web/components/layout/Meta.tsx | 2 +- .../components/shared/invite/invite-modal.tsx | 2 +- .../team/invite/invite-form-modal.tsx | 2 +- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts index da8249ec5..9f2ecb175 100644 --- a/apps/web/app/hooks/auth/useAuthenticationPasscode.ts +++ b/apps/web/app/hooks/auth/useAuthenticationPasscode.ts @@ -74,6 +74,31 @@ export function useAuthenticationPasscode() { setFormValues((prevState) => ({ ...prevState, [name]: value })); }; + const signInToWorkspaceRequest = useCallback( + (params: { + email: string; + token: string; + selectedTeam: string; + code?: string; + defaultTeamId?: string; + lastTeamId?: string; + }) => { + signInWorkspaceQueryCall(params) + .then(() => { + setAuthenticated(true); + router.push('/'); + }) + .catch((err: AxiosError) => { + if (err.response?.status === 400) { + setErrors((err.response?.data as any)?.errors || {}); + } + + inputCodeRef.current?.clear(); + }); + }, + [signInWorkspaceQueryCall, router] + ); + /** * Verify auth request */ @@ -145,7 +170,7 @@ export function useAuthenticationPasscode() { }); // eslint-disable-next-line react-hooks/exhaustive-deps }, - [signInEmailConfirmQueryCall, router, pathname, queryTeamId] + [signInEmailConfirmQueryCall, t, signInToWorkspaceRequest, router, pathname, queryTeamId] ); const verifyPasscodeRequest = useCallback( @@ -173,28 +198,6 @@ export function useAuthenticationPasscode() { [queryCall] ); - const signInToWorkspaceRequest = (params: { - email: string; - token: string; - selectedTeam: string; - code?: string; - defaultTeamId?: string; - lastTeamId?: string; - }) => { - signInWorkspaceQueryCall(params) - .then(() => { - setAuthenticated(true); - router.push('/'); - }) - .catch((err: AxiosError) => { - if (err.response?.status === 400) { - setErrors((err.response?.data as any)?.errors || {}); - } - - inputCodeRef.current?.clear(); - }); - }; - const handleCodeSubmit = (e: React.FormEvent) => { e.preventDefault(); setErrors({}); diff --git a/apps/web/app/hooks/features/usePagination.ts b/apps/web/app/hooks/features/usePagination.ts index a43ba2ca4..f1a19a96a 100644 --- a/apps/web/app/hooks/features/usePagination.ts +++ b/apps/web/app/hooks/features/usePagination.ts @@ -68,6 +68,8 @@ export function useScrollPagination({ return () => { container.removeEventListener('scroll', handleScroll); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [$scrollableElement.current, enabled]); useEffect(() => { diff --git a/apps/web/components/layout/Meta.tsx b/apps/web/components/layout/Meta.tsx index 7a2429008..5f3ecccfd 100644 --- a/apps/web/components/layout/Meta.tsx +++ b/apps/web/components/layout/Meta.tsx @@ -1,7 +1,7 @@ import Head from 'next/head'; import { MetaProps } from '../../app/interfaces/hooks'; -const Meta = ({ title = 'Gauzy Teams', keywords = '', description = '' }: MetaProps) => { +const Meta = ({ title = 'Gauzy Teams', keywords = '', description = '' }: Partial) => { return ( diff --git a/apps/web/components/shared/invite/invite-modal.tsx b/apps/web/components/shared/invite/invite-modal.tsx index 21dc0c7b6..ec74bd0a7 100644 --- a/apps/web/components/shared/invite/invite-modal.tsx +++ b/apps/web/components/shared/invite/invite-modal.tsx @@ -54,7 +54,7 @@ const InviteModal = ({ isOpen, Fragment, closeModal }: IInviteProps) => { } inviteUser(formData.email, formData.name) - .then((data) => { + .then(() => { setFormData(initalValues); closeModal(); toast({ diff --git a/apps/web/lib/features/team/invite/invite-form-modal.tsx b/apps/web/lib/features/team/invite/invite-form-modal.tsx index 634a9763d..04fa0d459 100644 --- a/apps/web/lib/features/team/invite/invite-form-modal.tsx +++ b/apps/web/lib/features/team/invite/invite-form-modal.tsx @@ -92,7 +92,7 @@ export function InviteFormModal({ open, closeModal }: { open: boolean; closeModa } inviteUser(selectedEmail.title, form.get('name')?.toString() || selectedEmail.name || '') - .then(({ data }) => { + .then(() => { closeModal(); e.currentTarget.reset(); From 92e74ad04c9e8debf78d84c19c183546e421feb7 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Mon, 9 Sep 2024 17:52:18 +0000 Subject: [PATCH 21/21] Simplify conditional rendering in TeamOutstandingNotifications --- apps/web/lib/features/team/team-outstanding-notifications.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/lib/features/team/team-outstanding-notifications.tsx b/apps/web/lib/features/team/team-outstanding-notifications.tsx index fab2b7fa7..7dc1cd181 100644 --- a/apps/web/lib/features/team/team-outstanding-notifications.tsx +++ b/apps/web/lib/features/team/team-outstanding-notifications.tsx @@ -25,7 +25,7 @@ export function TeamOutstandingNotifications() { return (
    - {outstandingPlans && outstandingPlans.length > 0 && ( + {outstandingPlans.length > 0 && ( )}