From 0df70cea7b73896afd0c6e9f6ea1c0ccd5a9506d Mon Sep 17 00:00:00 2001 From: land-bit Date: Sun, 22 Sep 2024 19:22:01 +0200 Subject: [PATCH 01/10] [Improvement] 'See Plan' | ability to add task to Empty plan --- .../add-task-estimation-hours-modal.tsx | 2 +- .../features/daily-plan/all-plans-modal.tsx | 75 +++++++++++++++++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx index 8d57812f8..669c5c7b9 100644 --- a/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx +++ b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx @@ -462,7 +462,7 @@ interface ISearchTaskInputProps { * * @returns The Search input component */ -function SearchTaskInput(props: ISearchTaskInputProps) { +export function SearchTaskInput(props: ISearchTaskInputProps) { const { selectedPlan, setShowSearchInput, defaultTask, setDefaultTask } = props; const { tasks: teamTasks, createTask } = useTeamTasks(); const { taskStatus } = useTaskStatus(); diff --git a/apps/web/lib/features/daily-plan/all-plans-modal.tsx b/apps/web/lib/features/daily-plan/all-plans-modal.tsx index 1744d094b..5f851fe19 100644 --- a/apps/web/lib/features/daily-plan/all-plans-modal.tsx +++ b/apps/web/lib/features/daily-plan/all-plans-modal.tsx @@ -1,15 +1,16 @@ -import { Card, Modal, NoData, Tooltip, VerticalSeparator } from 'lib/components'; +import { Card, InputField, Modal, NoData, Tooltip, VerticalSeparator } from 'lib/components'; import { Dispatch, memo, SetStateAction, useCallback, useMemo, useState } from 'react'; import { clsxm } from '@app/utils'; import { Text } from 'lib/components'; -import { ChevronRightIcon } from 'assets/svg'; -import { AddTasksEstimationHoursModal } from './add-task-estimation-hours-modal'; +import { AddIcon, ChevronRightIcon } from 'assets/svg'; +import { AddTasksEstimationHoursModal, SearchTaskInput } from './add-task-estimation-hours-modal'; import { useDailyPlan } from '@app/hooks'; import { Button } from '@components/ui/button'; import { Calendar } from '@components/ui/calendar'; -import { IDailyPlan } from '@app/interfaces'; +import { IDailyPlan, ITeamTask } from '@app/interfaces'; import moment from 'moment'; import { ValueNoneIcon } from '@radix-ui/react-icons'; +import { useTranslations } from 'next-intl'; interface IAllPlansModal { closeModal: () => void; @@ -90,6 +91,12 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) } }, [selectedTab, todayPlan, tomorrowPlan, selectedFuturePlan]); + const [showSearchInput, setShowSearchInput] = useState(false); + const [defaultTask, setDefaultTask] = useState(null); + const [workTimePlanned, setWorkTimePlanned] = useState(0); + const [isWorkingTimeInputFocused, setWorkingTimeInputFocused] = useState(false); + const t = useTranslations(); + // Set the related tab for today and tomorrow dates const handleCalendarSelect = useCallback(() => { if (customDate) { @@ -154,7 +161,7 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) -
+
{selectedTab === 'Calendar' && showCalendar ? (
@@ -202,7 +209,63 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal) closeModal={handleCloseModal} /> ) : ( - } text="Plan not found " /> + <> + {showSearchInput ? ( + + ) : ( +
+ + {t('timer.todayPlanSettings.WORK_TIME_PLANNED')}{' '} + * + +
+ { + !isNaN(parseInt(e.target.value)) + ? setWorkTimePlanned(parseInt(e.target.value)) + : setWorkTimePlanned(0); + }} + required + noWrapper + min={0} + value={ + !isNaN(workTimePlanned) && + workTimePlanned.toString() !== '0' + ? workTimePlanned.toString().replace(/^0+/, '') + : isWorkingTimeInputFocused + ? '' + : 0 + } + onFocus={() => setWorkingTimeInputFocused(true)} + onBlur={() => setWorkingTimeInputFocused(false)} + defaultValue={0} + /> + +
+
+ )} +
+ } text="Plan not found " /> +
+ )} )} From d8d80089d2164542ffd32beeccd45c0ff18329fb Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 25 Sep 2024 02:11:19 +0200 Subject: [PATCH 02/10] optimise paddings and spaces in the home page --- apps/web/app/[locale]/page-component.tsx | 275 ++++++++---------- .../notifications-dropdown/index.tsx | 63 ++++ apps/web/lib/features/team-member-header.tsx | 42 +-- .../lib/features/team/team-invitations.tsx | 2 +- .../team/user-team-card/task-skeleton.tsx | 12 +- .../user-team-table-header.tsx | 4 +- apps/web/lib/layout/navbar.tsx | 169 +++++------ 7 files changed, 290 insertions(+), 277 deletions(-) create mode 100644 apps/web/lib/components/notifications-dropdown/index.tsx diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 8de63bd88..40b430352 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -8,13 +8,7 @@ import { clsxm } from '@app/utils'; import NoTeam from '@components/pages/main/no-team'; import { withAuthentication } from 'lib/app/authenticator'; import { Breadcrumb, Card } from 'lib/components'; -import { - AuthUserTaskInput, - TeamInvitations, - TeamMembers, - Timer, - UnverifiedEmail -} from 'lib/features'; +import { AuthUserTaskInput, TeamInvitations, TeamMembers, Timer, UnverifiedEmail } from 'lib/features'; import { MainLayout } from 'lib/layout'; import { IssuesView } from '@app/constants'; import { useNetworkState } from '@uidotdev/usehooks'; @@ -35,161 +29,132 @@ import { headerTabs } from '@app/stores/header-tabs'; import { usePathname } from 'next/navigation'; import { PeoplesIcon } from 'assets/svg'; import TeamMemberHeader from 'lib/features/team-member-header'; -import { - ResizableHandle, - ResizablePanel, - ResizablePanelGroup -} from '@components/ui/resizable'; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable'; import { TeamOutstandingNotifications } from 'lib/features/team/team-outstanding-notifications'; function MainPage() { - const t = useTranslations(); - const [headerSize, setHeaderSize] = useState(10); - const { - isTeamMember, - isTrackingEnabled, - activeTeam - } = useOrganizationTeams(); - const [fullWidth, setFullWidth] = useAtom(fullWidthState); - const [view, setView] = useAtom(headerTabs); - const path = usePathname(); - const breadcrumb = [ - { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, - { title: activeTeam?.name || '', href: '/' }, - { title: t(`common.${view}`), href: `/` } - ]; - const { online } = useNetworkState(); - useEffect(() => { - if (view == IssuesView.KANBAN && path == '/') { - setView(IssuesView.CARDS); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [path, setView]); + const t = useTranslations(); + const [headerSize, setHeaderSize] = useState(10); + const { isTeamMember, isTrackingEnabled, activeTeam } = useOrganizationTeams(); + const [fullWidth, setFullWidth] = useAtom(fullWidthState); + const [view, setView] = useAtom(headerTabs); + const path = usePathname(); + const breadcrumb = [ + { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, + { title: activeTeam?.name || '', href: '/' }, + { title: t(`common.${view}`), href: `/` } + ]; + const { online } = useNetworkState(); + useEffect(() => { + if (view == IssuesView.KANBAN && path == '/') { + setView(IssuesView.CARDS); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [path, setView]); - React.useEffect(() => { - window && window?.localStorage.getItem('conf-fullWidth-mode'); - setFullWidth( - JSON.parse(window?.localStorage.getItem('conf-fullWidth-mode') || 'true') - ); - }, [setFullWidth]); + React.useEffect(() => { + window && window?.localStorage.getItem('conf-fullWidth-mode'); + setFullWidth(JSON.parse(window?.localStorage.getItem('conf-fullWidth-mode') || 'true')); + }, [setFullWidth]); - if (!online) { - return ; - } - return ( - <> -
- {/*
*/} - - -
- - {/* */} - setHeaderSize(size)} - > -
-
-
-
- - -
-
- -
-
-
- - - - {isTeamMember ? ( - - ) : null} -
- -
-
-
- - {/*
*/} - -
- {isTeamMember ? ( - - ) : ( - - )} -
-
-
-
-
-
- - - ); + if (!online) { + return ; + } + return ( + <> +
+ {/*
*/} + + +
+ + {/* */} + setHeaderSize(size)} + > +
+
+
+
+ + +
+
+ +
+
+
+ + + + {isTeamMember ? ( + + ) : null} +
+ +
+
+
+ + {/*
*/} + +
{isTeamMember ? : }
+
+
+
+
+
+ + + ); } -function TaskTimerSection({ - isTrackingEnabled -}: { - isTrackingEnabled: boolean; -}) { - const [showInput, setShowInput] = React.useState(false); - return ( - - -
setShowInput((p) => !p)} - className="border dark:border-[#26272C] w-full rounded p-2 md:hidden flex justify-center mt-2" - > - - {showInput ? 'hide the issue input' : 'show the issue input'} - -
- {isTrackingEnabled ? ( -
- -
- ) : null} -
- ); +function TaskTimerSection({ isTrackingEnabled }: { isTrackingEnabled: boolean }) { + const [showInput, setShowInput] = React.useState(false); + return ( + + +
setShowInput((p) => !p)} + className="border dark:border-[#26272C] w-full rounded p-2 md:hidden flex justify-center mt-2" + > + + {showInput ? 'hide the issue input' : 'show the issue input'} + +
+ {isTrackingEnabled ? ( +
+ +
+ ) : null} +
+ ); } export default withAuthentication(MainPage, { displayName: 'MainPage' }); diff --git a/apps/web/lib/components/notifications-dropdown/index.tsx b/apps/web/lib/components/notifications-dropdown/index.tsx new file mode 100644 index 000000000..83fdb3e67 --- /dev/null +++ b/apps/web/lib/components/notifications-dropdown/index.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Disclosure } from '@headlessui/react'; +import { ChevronUpIcon } from '@heroicons/react/20/solid'; +import { ScrollArea, ScrollBar } from '@components/ui/scroll-bar'; + +function CheckRender({ children }: IProps) { + return children ? true : false; +} + +interface IProps { + children: React.ReactNode; +} + +/** + * A dropdown component that displays user notiications. + * + * @param {object} props - The props object + * + * @returns {JSX.Element} The Notification dropdown component + */ +export default function NotificationsDropdown(props: IProps) { + const { children } = props; + + const childrenArray = React.Children.toArray(children).map((child) => { + console.log(child); + + return React.isValidElement(child); + }); + + if (childrenArray[0]) { + return ( +
+ {childrenArray.length > 1 ? ( + + {({ open }) => ( + <> +
+
{childrenArray[0]}
+ + + +
+ + + + {childrenArray.slice(1)} + + + + + )} +
+ ) : ( + childrenArray[0] + )} +
+ ); + } else { + return
no data
; + } +} diff --git a/apps/web/lib/features/team-member-header.tsx b/apps/web/lib/features/team-member-header.tsx index db213e94d..913a5a4a9 100644 --- a/apps/web/lib/features/team-member-header.tsx +++ b/apps/web/lib/features/team-member-header.tsx @@ -7,27 +7,27 @@ import { fullWidthState } from '@app/stores/fullWidth'; import UserTeamTableHeader from './team/user-team-table/user-team-table-header'; function TeamMemberHeader({ view }: { view: IssuesView }) { - const fullWidth = useAtomValue(fullWidthState); - let header; - switch (true) { - case view == IssuesView.CARDS: - header = ; - break; - case view == IssuesView.TABLE: - header = ; - break; - case view == IssuesView.BLOCKS: - header = ; - break; - default: - header = ; - break; - } - return ( - - {header} - - ); + const fullWidth = useAtomValue(fullWidthState); + let header; + switch (true) { + case view == IssuesView.CARDS: + header = ; + break; + case view == IssuesView.TABLE: + header = ; + break; + case view == IssuesView.BLOCKS: + header = ; + break; + default: + header = ; + break; + } + return ( + + {header} + + ); } export default TeamMemberHeader; diff --git a/apps/web/lib/features/team/team-invitations.tsx b/apps/web/lib/features/team/team-invitations.tsx index 520e2875a..bfe408bc8 100644 --- a/apps/web/lib/features/team/team-invitations.tsx +++ b/apps/web/lib/features/team/team-invitations.tsx @@ -106,7 +106,7 @@ export function TeamInvitations() { handleCloseInvitation(invitation.id); }} > - + ))} diff --git a/apps/web/lib/features/team/user-team-card/task-skeleton.tsx b/apps/web/lib/features/team/user-team-card/task-skeleton.tsx index d568d956e..4a06d0fe1 100644 --- a/apps/web/lib/features/team/user-team-card/task-skeleton.tsx +++ b/apps/web/lib/features/team/user-team-card/task-skeleton.tsx @@ -43,11 +43,11 @@ export function InviteUserTeamSkeleton() { export function UserTeamCardHeader() { const t = useTranslations(); return ( -
-
-
+
+
+
-
+
{t('common.TEAM')} {t('common.MEMBER')}
@@ -61,7 +61,7 @@ export function UserTeamCardHeader() {
- {t('task.taskTableHead.TASK_WORK.TITLE')}
{t('common.TASK')} + {t('common.TASK')} Time
@@ -69,7 +69,7 @@ export function UserTeamCardHeader() {
- {t('task.taskTableHead.TOTAL_WORK.TITLE')}
{t('common.TODAY')} + Total {t('common.TODAY')}
diff --git a/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx b/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx index f84b5e44f..50ab5244f 100644 --- a/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx +++ b/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx @@ -5,7 +5,7 @@ import React from 'react'; function UserTeamTableHeader() { const t = useTranslations(); return ( - + {t('common.TEAM')} {t('common.MEMBER')} @@ -13,7 +13,7 @@ function UserTeamTableHeader() { {t('common.TASK')} - {t('task.taskTableHead.TASK_WORK.TITLE')}
{t('common.TASK')} + {t('common.TASK')} Time
{t('common.ESTIMATE')} diff --git a/apps/web/lib/layout/navbar.tsx b/apps/web/lib/layout/navbar.tsx index d5b34d670..4f49a920a 100644 --- a/apps/web/lib/layout/navbar.tsx +++ b/apps/web/lib/layout/navbar.tsx @@ -18,108 +18,93 @@ import { useAtom, useAtomValue } from 'jotai'; import { fullWidthState } from '@app/stores/fullWidth'; const HeaderSkeleton = () => { - return ( - + ); }; export function Navbar({ - className, - showTimer, - publicTeam, - notFound + className, + showTimer, + publicTeam, + notFound }: IClassName & { - showTimer?: boolean; - publicTeam?: boolean; - notFound?: boolean; + showTimer?: boolean; + publicTeam?: boolean; + notFound?: boolean; }) { - const t = useTranslations(); - const { isTeamMember } = useOrganizationTeams(); - const [user] = useAtom(userState); - const { isOpen, closeModal, openModal } = useModal(); - const fullWidth = useAtomValue(fullWidthState); + const t = useTranslations(); + const { isTeamMember } = useOrganizationTeams(); + const [user] = useAtom(userState); + const { isOpen, closeModal, openModal } = useModal(); + const fullWidth = useAtomValue(fullWidthState); - const pathname = usePathname(); + const pathname = usePathname(); - const isTeamDropdownAllowed = useMemo(() => { - if (!pathname) { - return false; - } - const notAllowedList = ['/task/[id]', '/profile/[memberId]']; - return !notAllowedList.includes(pathname); - }, [pathname]); + const isTeamDropdownAllowed = useMemo(() => { + if (!pathname) { + return false; + } + const notAllowedList = ['/task/[id]', '/profile/[memberId]']; + return !notAllowedList.includes(pathname); + }, [pathname]); - return ( -
- {!user && !notFound && !publicTeam ? ( - - ) : ( -
+ + + + )} +
+ ); } From ada6c08ffb0a22cdae35acc93354a1fb2d550a89 Mon Sep 17 00:00:00 2001 From: "Thierry CH." Date: Wed, 25 Sep 2024 07:26:48 +0200 Subject: [PATCH 03/10] 3022 improvement see plan the timer is running and user addsremove a task to the plan (#3059) * feat: add 'unplan-active-task-modal' * feat: add 'Add the Save changes' * feat: The timer is running and user can add/remove a task to the plan * feat: The timer is running and user can add/remove a task to the plan --- apps/web/app/hooks/features/useDailyPlan.ts | 36 ++-- .../add-task-estimation-hours-modal.tsx | 169 +++++++++++++----- .../daily-plan/unplan-active-task-modal.tsx | 83 +++++++++ 3 files changed, 234 insertions(+), 54 deletions(-) create mode 100644 apps/web/lib/features/daily-plan/unplan-active-task-modal.tsx diff --git a/apps/web/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts index de33fb21a..7f46646fa 100644 --- a/apps/web/app/hooks/features/useDailyPlan.ts +++ b/apps/web/app/hooks/features/useDailyPlan.ts @@ -108,11 +108,11 @@ export function useDailyPlan() { user?.tenantId || '' ); //Check if there is an existing plan - const isPlanExist = profileDailyPlans.items.find((plan) => + const isPlanExist = [...(profileDailyPlans.items ? profileDailyPlans.items : [])].find((plan) => plan.date?.toString()?.startsWith(new Date(data.date)?.toISOString().split('T')[0]) ); if (isPlanExist) { - const updatedPlans = profileDailyPlans.items.map((plan) => { + const updatedPlans = [...(profileDailyPlans.items ? profileDailyPlans.items : [])].map((plan) => { if (plan.date?.toString()?.startsWith(new Date(data.date)?.toISOString().split('T')[0])) { return res.data; } @@ -127,11 +127,11 @@ export function useDailyPlan() { } else { setProfileDailyPlans({ total: profileDailyPlans.total + 1, - items: [...profileDailyPlans.items, res.data] + items: [...(profileDailyPlans.items ? profileDailyPlans.items : []), res.data] }); } - setEmployeePlans([...employeePlans, res.data]); + setEmployeePlans([...(employeePlans ? employeePlans : []), res.data]); getMyDailyPlans(); return res; } @@ -151,8 +151,10 @@ export function useDailyPlan() { const updateDailyPlan = useCallback( async (data: IUpdateDailyPlan, planId: string) => { const res = await updateQueryCall(data, planId); - const updated = profileDailyPlans.items.filter((plan) => plan.id != planId); - const updatedEmployee = employeePlans.filter((plan) => plan.id != planId); + const updated = [...(profileDailyPlans.items ? profileDailyPlans.items : [])].filter( + (plan) => plan.id != planId + ); + const updatedEmployee = [...(employeePlans ? employeePlans : [])].filter((plan) => plan.id != planId); setProfileDailyPlans({ total: profileDailyPlans.total, items: [...updated, res.data] @@ -178,8 +180,10 @@ export function useDailyPlan() { const addTaskToPlan = useCallback( async (data: IDailyPlanTasksUpdate, planId: string) => { const res = await addTaskToPlanQueryCall(data, planId); - const updated = profileDailyPlans.items.filter((plan) => plan.id != planId); - const updatedEmployee = employeePlans.filter((plan) => plan.id != planId); + const updated = [...(profileDailyPlans.items ? profileDailyPlans.items : [])].filter( + (plan) => plan.id != planId + ); + const updatedEmployee = [...(employeePlans ? employeePlans : [])].filter((plan) => plan.id != planId); setProfileDailyPlans({ total: profileDailyPlans.total, items: [...updated, res.data] @@ -201,8 +205,10 @@ export function useDailyPlan() { const removeTaskFromPlan = useCallback( async (data: IDailyPlanTasksUpdate, planId: string) => { const res = await removeTAskFromPlanQueryCall(data, planId); - const updated = profileDailyPlans.items.filter((plan) => plan.id != planId); - const updatedEmployee = employeePlans.filter((plan) => plan.id != planId); + const updated = [...(profileDailyPlans.items ? profileDailyPlans.items : [])].filter( + (plan) => plan.id != planId + ); + const updatedEmployee = [...(employeePlans ? employeePlans : [])].filter((plan) => plan.id != planId); setProfileDailyPlans({ total: profileDailyPlans.total, items: [...updated, res.data] @@ -224,14 +230,14 @@ export function useDailyPlan() { const removeManyTaskPlans = useCallback( async (data: IRemoveTaskFromManyPlans, taskId: string) => { const res = await removeManyTaskPlanQueryCall({ taskId, data }); - const updatedProfileDailyPlans = profileDailyPlans.items + const updatedProfileDailyPlans = [...(profileDailyPlans.items ? profileDailyPlans.items : [])] .map((plan) => { const updatedTasks = plan.tasks ? plan.tasks.filter((task) => task.id !== taskId) : []; return { ...plan, tasks: updatedTasks }; }) .filter((plan) => plan.tasks && plan.tasks.length > 0); // Delete plans without tasks - const updatedEmployeePlans = employeePlans + const updatedEmployeePlans = [...(employeePlans ? employeePlans : [])] .map((plan) => { const updatedTasks = plan.tasks ? plan.tasks.filter((task) => task.id !== taskId) : []; return { ...plan, tasks: updatedTasks }; @@ -259,8 +265,10 @@ export function useDailyPlan() { const deleteDailyPlan = useCallback( async (planId: string) => { const res = await deleteDailyPlanQueryCall(planId); - const updated = profileDailyPlans.items.filter((plan) => plan.id != planId); - const updatedEmployee = employeePlans.filter((plan) => plan.id != planId); + const updated = [...(profileDailyPlans.items ? profileDailyPlans.items : [])].filter( + (plan) => plan.id != planId + ); + const updatedEmployee = [...(employeePlans ? employeePlans : [])].filter((plan) => plan.id != planId); setProfileDailyPlans({ total: updated.length, items: [...updated] }); setEmployeePlans([...updatedEmployee]); diff --git a/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx index 669c5c7b9..1413d496d 100644 --- a/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx +++ b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx @@ -21,6 +21,7 @@ import { Popover, Transition } from '@headlessui/react'; import { ScrollArea, ScrollBar } from '@components/ui/scroll-bar'; import { Cross2Icon } from '@radix-ui/react-icons'; import { checkPastDate } from 'lib/utils'; +import { UnplanActiveTaskModal } from './unplan-active-task-modal'; /** * A modal that allows user to add task estimation / planned work time, etc. @@ -76,6 +77,10 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa [activeTeamTask?.id, plan.tasks] ); const [isWorkingTimeInputFocused, setWorkingTimeInputFocused] = useState(false); + const [planEditState, setPlanEditState] = useState<{ draft: boolean; saved: boolean }>({ + draft: false, + saved: false + }); const canStartWorking = useMemo(() => { const isTodayPlan = @@ -139,7 +144,9 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa // Update the plan work time only if the user changed it plan.workTimePlanned !== workTimePlanned && (await updateDailyPlan({ workTimePlanned }, plan.id ?? '')); - if (canStartWorking) { + setPlanEditState({ draft: false, saved: true }); + + if (canStartWorking && !timerStatus?.running) { handleChangeActiveTask(); if (isRenderedInSoftFlow) { @@ -152,14 +159,15 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa setLoading(false); } }, [ + plan.workTimePlanned, + plan.id, + workTimePlanned, + updateDailyPlan, canStartWorking, + timerStatus?.running, handleChangeActiveTask, - handleCloseModal, - plan.id, - plan.workTimePlanned, isRenderedInSoftFlow, - updateDailyPlan, - workTimePlanned + handleCloseModal ]); /** @@ -235,7 +243,7 @@ export function AddTasksEstimationHoursModal(props: IAddTasksEstimationHoursModa const StartWorkingButton = (
+ {activeTeamTask && ( + + )} ); } @@ -712,6 +744,7 @@ interface ITaskCardActionsProps { task: ITeamTask; selectedPlan: IDailyPlan; openTaskDetailsModal: () => void; + openUnplanActiveTaskModal: () => void; } /** @@ -721,15 +754,17 @@ interface ITaskCardActionsProps { * @param {ITeamTask} props.task - The task on which actions will be performed * @param {IDailyPlan} props.selectedPlan - The currently selected plan * @param {() => void} props.openTaskDetailsModal - A function that opens a task details modal + * @param {() => void} props.openUnplanActiveTaskModal - A function to open the unplanActiveTask modal * * @returns {JSX.Element} The Popover component. */ function TaskCardActions(props: ITaskCardActionsProps) { - const { task, selectedPlan, openTaskDetailsModal } = props; + const { task, selectedPlan, openTaskDetailsModal, openUnplanActiveTaskModal } = props; const { user } = useAuthenticateUser(); const { futurePlans, todayPlan, removeTaskFromPlan, removeTaskFromPlanLoading } = useDailyPlan(); - + const { activeTeamTask } = useTeamTasks(); + const { timerStatus } = useTimerView(); const otherPlanIds = useMemo( () => [...futurePlans, ...todayPlan] @@ -739,6 +774,12 @@ function TaskCardActions(props: ITaskCardActionsProps) { .map((plan) => plan.id!), [futurePlans, selectedPlan.id, task.id, todayPlan] ); + const isTodayPLan = useMemo( + () => + new Date(selectedPlan?.date).toLocaleDateString('en') == + new Date(todayPlan[0]?.date).toLocaleDateString('en'), + [selectedPlan.date, todayPlan] + ); /** * Unplan selected task @@ -746,15 +787,39 @@ function TaskCardActions(props: ITaskCardActionsProps) { const unplanSelectedDate = useCallback( async (closePopover: () => void) => { try { - selectedPlan?.id && - (await removeTaskFromPlan({ taskId: task.id, employeeId: user?.employee.id }, selectedPlan?.id)); + if (task.id == activeTeamTask?.id) { + if (timerStatus?.running && isTodayPLan) { + openUnplanActiveTaskModal(); + } else { + selectedPlan?.id && + (await removeTaskFromPlan( + { taskId: task.id, employeeId: user?.employee.id }, + selectedPlan?.id + )); + } + } else { + selectedPlan?.id && + (await removeTaskFromPlan( + { taskId: task.id, employeeId: user?.employee.id }, + selectedPlan?.id + )); + } closePopover(); } catch (error) { console.log(error); } }, - [removeTaskFromPlan, selectedPlan.id, task.id, user?.employee.id] + [ + activeTeamTask?.id, + isTodayPLan, + openUnplanActiveTaskModal, + removeTaskFromPlan, + selectedPlan.id, + task.id, + timerStatus?.running, + user?.employee.id + ] ); return ( @@ -795,6 +860,9 @@ function TaskCardActions(props: ITaskCardActionsProps) { selectedPlanId={selectedPlan.id} planIds={[selectedPlan.id, ...otherPlanIds]} closeActionPopover={close} + openUnplanActiveTaskModal={openUnplanActiveTaskModal} + unplanSelectedDate={unplanSelectedDate} + unPlanSelectedDateLoading={removeTaskFromPlanLoading} /> ) : ( void; + unplanSelectedDate: (closePopover: () => void) => Promise; + unPlanSelectedDateLoading: boolean; + openUnplanActiveTaskModal: () => void; } /** @@ -841,32 +912,30 @@ interface IUnplanTaskProps { * @param {string} props.selectedPlanId - The currently selected plan id * @param {string[]} [props.planIds] - Others plans's ids * @param {() => void} props.closeActionPopover - The function to close the task card actions popover + * @param {(taskIds: string[]) => void} props.wantsToUnplanActiveTask - Will be called when the user wants to unplan the activeTas + * @param {() => void} props.openUnplanActiveTaskModal - A function to open the unplanActiveTask modal + * + * * * @returns {JSX.Element} The Popover component. */ function UnplanTask(props: IUnplanTaskProps) { - const { taskId, selectedPlanId, planIds, closeActionPopover } = props; + const { + taskId, + planIds, + closeActionPopover, + unplanSelectedDate, + openUnplanActiveTaskModal, + unPlanSelectedDateLoading + } = props; const { user } = useAuthenticateUser(); - const { removeTaskFromPlan, removeTaskFromPlanLoading, removeManyTaskPlans, removeManyTaskFromPlanLoading } = - useDailyPlan(); - - /** - * Unplan selected task - */ - const unplanSelectedDate = useCallback( - async (closePopover: () => void) => { - try { - await removeTaskFromPlan({ taskId: taskId, employeeId: user?.employee.id }, selectedPlanId); - - closePopover(); - // Close the task card actions popover as well - closeActionPopover(); - } catch (error) { - console.log(error); - } - }, - [closeActionPopover, removeTaskFromPlan, selectedPlanId, taskId, user?.employee.id] + const { removeManyTaskPlans, removeManyTaskFromPlanLoading, todayPlan } = useDailyPlan(); + const { activeTeamTask } = useTeamTasks(); + const { timerStatus } = useTimerView(); + const isActiveTaskPlannedToday = useMemo( + () => todayPlan[0].tasks?.find((task) => task.id === activeTeamTask?.id), + [activeTeamTask?.id, todayPlan] ); /** @@ -875,7 +944,17 @@ function UnplanTask(props: IUnplanTaskProps) { const unplanAll = useCallback( async (closePopover: () => void) => { try { - await removeManyTaskPlans({ plansIds: planIds, employeeId: user?.employee.id }, taskId); + // First, check if the user wants to unplan the active task + if (taskId == activeTeamTask?.id) { + if (timerStatus?.running && isActiveTaskPlannedToday) { + openUnplanActiveTaskModal(); + // TODO: Unplan from all plans after clicks 'YES' + } else { + await removeManyTaskPlans({ plansIds: planIds, employeeId: user?.employee.id }, taskId); + } + } else { + await removeManyTaskPlans({ plansIds: planIds, employeeId: user?.employee.id }, taskId); + } closePopover(); // Close the task card actions popover as well @@ -884,7 +963,17 @@ function UnplanTask(props: IUnplanTaskProps) { console.log(error); } }, - [closeActionPopover, planIds, removeManyTaskPlans, taskId, user?.employee.id] + [ + activeTeamTask?.id, + closeActionPopover, + isActiveTaskPlannedToday, + openUnplanActiveTaskModal, + planIds, + removeManyTaskPlans, + taskId, + timerStatus?.running, + user?.employee.id + ] ); return ( @@ -914,10 +1003,10 @@ function UnplanTask(props: IUnplanTaskProps) { onClick={() => unplanSelectedDate(close)} className={clsxm( 'shrink-0', - !removeTaskFromPlanLoading && 'hover:font-semibold hover:transition-all ' + !unPlanSelectedDateLoading && 'hover:font-semibold hover:transition-all ' )} > - {removeTaskFromPlanLoading ? ( + {unPlanSelectedDateLoading ? ( ) : ( 'Unplan selected date' diff --git a/apps/web/lib/features/daily-plan/unplan-active-task-modal.tsx b/apps/web/lib/features/daily-plan/unplan-active-task-modal.tsx new file mode 100644 index 000000000..4b95c1364 --- /dev/null +++ b/apps/web/lib/features/daily-plan/unplan-active-task-modal.tsx @@ -0,0 +1,83 @@ +import { useAuthenticateUser, useDailyPlan, useTimerView } from '@app/hooks'; +import { IDailyPlan, ITeamTask } from '@app/interfaces'; +import { Button, Card, Modal, Text } from 'lib/components'; +import { useCallback } from 'react'; + +interface UnplanActiveTaskModalProps { + open: boolean; + closeModal: () => void; + task: ITeamTask; + plan: IDailyPlan; +} + +/** + * A Modal that gives the possibility to unplan the active task. + * + * @param {Object} props - The props Object + * @param {boolean} props.open - If true open the modal otherwise close the modal + * @param {() => void} props.closeModal - A function to close the modal + * @param {ITeamTask} props.task - The task to unplan + * @param {IDailyPlan} props.plan - The today's plan + * + * @returns {JSX.Element} The modal element + */ +export function UnplanActiveTaskModal(props: UnplanActiveTaskModalProps) { + const { closeModal, task, open, plan } = props; + const { user } = useAuthenticateUser(); + const { removeTaskFromPlan, removeTaskFromPlanLoading } = useDailyPlan(); + const { stopTimer, timerStatus } = useTimerView(); + + const handleCloseModal = useCallback(() => { + closeModal(); + }, [closeModal]); + + const handleUnplanTask = useCallback(async () => { + try { + plan.id && (await removeTaskFromPlan({ taskId: task.id, employeeId: user?.employee.id }, plan.id)); + } catch (error) { + console.log(error); + } + }, [plan.id, removeTaskFromPlan, task.id, user?.employee.id]); + + // The function that will be called when the user clicks on 'YES' button + const onYes = useCallback(async () => { + if (timerStatus?.running) { + stopTimer(); + } + await handleUnplanTask(); + handleCloseModal(); + }, [handleCloseModal, handleUnplanTask, stopTimer, timerStatus?.running]); + + return ( + + +
+ + You are about to unplan the current active task, please confirm the action + +
+ + +
+
+
+
+ ); +} From e4651a7adc9a192389536dcec5eeb87f50cd79e7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 25 Sep 2024 11:03:22 +0200 Subject: [PATCH 04/10] optimise space in the home page --- apps/web/app/[locale]/page-component.tsx | 8 +++++--- apps/web/lib/features/team/team-invitations.tsx | 9 +++++++-- .../lib/features/team/user-team-card/task-skeleton.tsx | 2 +- .../team/user-team-table/user-team-table-header.tsx | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 40b430352..24706b353 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -94,9 +94,11 @@ function MainPage() {
- - - +
+ + + +
{isTeamMember ? ( ) : null} diff --git a/apps/web/lib/features/team/team-invitations.tsx b/apps/web/lib/features/team/team-invitations.tsx index bfe408bc8..10ccf9c56 100644 --- a/apps/web/lib/features/team/team-invitations.tsx +++ b/apps/web/lib/features/team/team-invitations.tsx @@ -10,7 +10,12 @@ import cloneDeep from 'lodash/cloneDeep'; import { useTranslations } from 'next-intl'; import { useCallback, useEffect, useState } from 'react'; -export function TeamInvitations() { +interface IProps { + className?: string; +} + +export function TeamInvitations(props: IProps) { + const { className } = props; const t = useTranslations(); const { myInvitationsList, @@ -61,7 +66,7 @@ export function TeamInvitations() { ); return ( -
+
{myInvitationsList .filter((invitation) => !removedInvitations.includes(invitation.id)) .map((invitation, index) => ( diff --git a/apps/web/lib/features/team/user-team-card/task-skeleton.tsx b/apps/web/lib/features/team/user-team-card/task-skeleton.tsx index 4a06d0fe1..1dd414659 100644 --- a/apps/web/lib/features/team/user-team-card/task-skeleton.tsx +++ b/apps/web/lib/features/team/user-team-card/task-skeleton.tsx @@ -43,7 +43,7 @@ export function InviteUserTeamSkeleton() { export function UserTeamCardHeader() { const t = useTranslations(); return ( -
+
diff --git a/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx b/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx index 50ab5244f..a0076f32a 100644 --- a/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx +++ b/apps/web/lib/features/team/user-team-table/user-team-table-header.tsx @@ -5,7 +5,7 @@ import React from 'react'; function UserTeamTableHeader() { const t = useTranslations(); return ( - + {t('common.TEAM')} {t('common.MEMBER')} From 04032dcac0399a3a2d45bf5f963dc472b069b9c6 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 25 Sep 2024 14:04:08 +0200 Subject: [PATCH 05/10] feat: optimize space in the profile page --- .../app/[locale]/profile/[memberId]/page.tsx | 387 +++--- .../notifications-dropdown/index.tsx | 12 +- apps/web/lib/features/task/task-filters.tsx | 8 +- apps/web/lib/features/team-member-header.tsx | 42 +- .../team/user-team-card/task-skeleton.tsx | 4 +- .../user-team-table-header.tsx | 2 +- apps/web/lib/features/user-profile-plans.tsx | 1134 +++++++---------- apps/web/lib/layout/navbar.tsx | 169 +-- apps/web/locales/ar.json | 2 + apps/web/locales/bg.json | 2 + apps/web/locales/de.json | 2 + apps/web/locales/en.json | 2 + apps/web/locales/es.json | 2 + apps/web/locales/fr.json | 2 + apps/web/locales/he.json | 2 + apps/web/locales/it.json | 2 + apps/web/locales/nl.json | 2 + apps/web/locales/pl.json | 2 + apps/web/locales/pt.json | 2 + apps/web/locales/ru.json | 2 + apps/web/locales/zh.json | 2 + 21 files changed, 802 insertions(+), 982 deletions(-) diff --git a/apps/web/app/[locale]/profile/[memberId]/page.tsx b/apps/web/app/[locale]/profile/[memberId]/page.tsx index cba0e9d9e..1eae2807f 100644 --- a/apps/web/app/[locale]/profile/[memberId]/page.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/page.tsx @@ -1,27 +1,11 @@ 'use client'; /* eslint-disable no-mixed-spaces-and-tabs */ -import { - useAuthenticateUser, - useDailyPlan, - useOrganizationTeams, - useUserProfilePage -} from '@app/hooks'; +import { useAuthenticateUser, useDailyPlan, useOrganizationTeams, useUserProfilePage } from '@app/hooks'; import { withAuthentication } from 'lib/app/authenticator'; -import { - Breadcrumb, - Button, - Container, - Text, - VerticalSeparator -} from 'lib/components'; +import { Breadcrumb, Button, Container, Text, VerticalSeparator } from 'lib/components'; import { ArrowLeftIcon } from 'assets/svg'; -import { - TaskFilter, - Timer, - UserProfileTask, - useTaskFilter -} from 'lib/features'; +import { TaskFilter, Timer, UserProfileTask, useTaskFilter } from 'lib/features'; import { MainHeader, MainLayout } from 'lib/layout'; import Link from 'next/link'; import React, { useCallback, useMemo, useState } from 'react'; @@ -33,214 +17,179 @@ import { ScreenshootTab } from 'lib/features/activity/screenshoots'; import { AppsTab } from 'lib/features/activity/apps'; import { VisitedSitesTab } from 'lib/features/activity/visited-sites'; import { activityTypeState } from '@app/stores/activity-type'; -import { - ResizableHandle, - ResizablePanel, - ResizablePanelGroup -} from '@components/ui/resizable'; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable'; import { UserProfileDetail } from './components/UserProfileDetail'; import { cn } from 'lib/utils'; // import { ActivityCalendar } from 'lib/features/activity/calendar'; export type FilterTab = 'Tasks' | 'Screenshots' | 'Apps' | 'Visited Sites'; -const Profile = React.memo(function ProfilePage({ - params -}: { - params: { memberId: string }; -}) { - const profile = useUserProfilePage(); - const [headerSize, setHeaderSize] = useState(10); - const { user } = useAuthenticateUser(); - const { - isTrackingEnabled, - activeTeam, - activeTeamManagers - } = useOrganizationTeams(); - const members = activeTeam?.members; - const { getEmployeeDayPlans } = useDailyPlan(); - const fullWidth = useAtomValue(fullWidthState); - const [activityFilter, setActivityFilter] = useState('Tasks'); - const setActivityTypeFilter = useSetAtom(activityTypeState); - const hook = useTaskFilter(profile); - - const isManagerConnectedUser = useMemo( - () => - activeTeamManagers.findIndex( - (member) => member.employee?.user?.id == user?.id - ), - [activeTeamManagers, user?.id] - ); - const canSeeActivity = useMemo( - () => profile.userProfile?.id === user?.id || isManagerConnectedUser != -1, - [isManagerConnectedUser, profile.userProfile?.id, user?.id] - ); - - const t = useTranslations(); - const breadcrumb = useMemo( - () => [ - { title: activeTeam?.name || '', href: '/' }, - { - title: JSON.parse(t('pages.profile.BREADCRUMB')) || '', - href: `/profile/${params.memberId}` - } - ], - [activeTeam?.name, params.memberId, t] - ); - - const activityScreens = useMemo( - () => ({ - Tasks: , - Screenshots: , - Apps: , - 'Visited Sites': - }), - [hook, profile] - ); - - const profileIsAuthUser = useMemo(() => profile.isAuthUser, [ - profile.isAuthUser - ]); - const hookFilterType = useMemo(() => hook.filterType, [hook.filterType]); - - const changeActivityFilter = useCallback( - (filter: FilterTab) => { - setActivityFilter(filter); - }, - [setActivityFilter] - ); - - React.useEffect(() => { - setActivityTypeFilter((prev) => ({ - ...prev, - member: profile.member ? profile.member : null - })); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [profile.member]); - - React.useEffect(() => { - getEmployeeDayPlans(profile.member?.employeeId ?? ''); - }, [getEmployeeDayPlans, profile.member?.employeeId]); - - if (Array.isArray(members) && members.length && !profile.member) { - return ( - -
-
- - {t('common.MEMBER')} {t('common.NOT_FOUND')}! - - - - {t('pages.profile.MEMBER_NOT_FOUND_MSG_1')} - - - -
-
-
- ); - } - - return ( - - - setHeaderSize(size)} - > - - {/* Breadcrumb */} -
- - - - - -
- - {/* User Profile Detail */} -
- - - {profileIsAuthUser && isTrackingEnabled && ( - - )} -
- {/* TaskFilter */} - -
- {/*
+const Profile = React.memo(function ProfilePage({ params }: { params: { memberId: string } }) { + const profile = useUserProfilePage(); + const [headerSize, setHeaderSize] = useState(10); + const { user } = useAuthenticateUser(); + const { isTrackingEnabled, activeTeam, activeTeamManagers } = useOrganizationTeams(); + const members = activeTeam?.members; + const { getEmployeeDayPlans } = useDailyPlan(); + const fullWidth = useAtomValue(fullWidthState); + const [activityFilter, setActivityFilter] = useState('Tasks'); + const setActivityTypeFilter = useSetAtom(activityTypeState); + const hook = useTaskFilter(profile); + + const isManagerConnectedUser = useMemo( + () => activeTeamManagers.findIndex((member) => member.employee?.user?.id == user?.id), + [activeTeamManagers, user?.id] + ); + const canSeeActivity = useMemo( + () => profile.userProfile?.id === user?.id || isManagerConnectedUser != -1, + [isManagerConnectedUser, profile.userProfile?.id, user?.id] + ); + + const t = useTranslations(); + const breadcrumb = useMemo( + () => [ + { title: activeTeam?.name || '', href: '/' }, + { + title: JSON.parse(t('pages.profile.BREADCRUMB')) || '', + href: `/profile/${params.memberId}` + } + ], + [activeTeam?.name, params.memberId, t] + ); + + const activityScreens = useMemo( + () => ({ + Tasks: , + Screenshots: , + Apps: , + 'Visited Sites': + }), + [hook, profile] + ); + + const profileIsAuthUser = useMemo(() => profile.isAuthUser, [profile.isAuthUser]); + const hookFilterType = useMemo(() => hook.filterType, [hook.filterType]); + + const changeActivityFilter = useCallback( + (filter: FilterTab) => { + setActivityFilter(filter); + }, + [setActivityFilter] + ); + + React.useEffect(() => { + setActivityTypeFilter((prev) => ({ + ...prev, + member: profile.member ? profile.member : null + })); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [profile.member]); + + React.useEffect(() => { + getEmployeeDayPlans(profile.member?.employeeId ?? ''); + }, [getEmployeeDayPlans, profile.member?.employeeId]); + + if (Array.isArray(members) && members.length && !profile.member) { + return ( + +
+
+ + {t('common.MEMBER')} {t('common.NOT_FOUND')}! + + + + {t('pages.profile.MEMBER_NOT_FOUND_MSG_1')} + + + +
+
+
+ ); + } + + return ( + + + setHeaderSize(size)} + > + +
+ {/* Breadcrumb */} +
+ + + + + +
+ + {/* User Profile Detail */} +
+ + + {profileIsAuthUser && isTrackingEnabled && ( + + )} +
+ {/* TaskFilter */} + +
+
+ {/*
*/} -
- - - {hook.tab == 'worked' && canSeeActivity && ( - -
- {Object.keys(activityScreens).map((filter, i) => ( -
- {i !== 0 && } -
changeActivityFilter(filter as FilterTab)} - > - {filter} -
-
- ))} -
-
- )} - - - {hook.tab !== 'worked' || activityFilter == 'Tasks' ? ( - - ) : ( - activityScreens[activityFilter] ?? null - )} - -
-
-
- ); + + + + {hook.tab == 'worked' && canSeeActivity && ( + +
+ {Object.keys(activityScreens).map((filter, i) => ( +
+ {i !== 0 && } +
changeActivityFilter(filter as FilterTab)} + > + {filter} +
+
+ ))} +
+
+ )} + + + {hook.tab !== 'worked' || activityFilter == 'Tasks' ? ( + + ) : ( + activityScreens[activityFilter] ?? null + )} + +
+ + + ); }); export default withAuthentication(Profile, { displayName: 'ProfilePage' }); diff --git a/apps/web/lib/components/notifications-dropdown/index.tsx b/apps/web/lib/components/notifications-dropdown/index.tsx index 83fdb3e67..af5fc8d1b 100644 --- a/apps/web/lib/components/notifications-dropdown/index.tsx +++ b/apps/web/lib/components/notifications-dropdown/index.tsx @@ -3,22 +3,14 @@ import { Disclosure } from '@headlessui/react'; import { ChevronUpIcon } from '@heroicons/react/20/solid'; import { ScrollArea, ScrollBar } from '@components/ui/scroll-bar'; -function CheckRender({ children }: IProps) { - return children ? true : false; -} - -interface IProps { - children: React.ReactNode; -} - /** - * A dropdown component that displays user notiications. + * A dropdown component that displays user notifications. * * @param {object} props - The props object * * @returns {JSX.Element} The Notification dropdown component */ -export default function NotificationsDropdown(props: IProps) { +export default function NotificationsDropdown(props: { children: React.ReactNode }) { const { children } = props; const childrenArray = React.Children.toArray(children).map((child) => { diff --git a/apps/web/lib/features/task/task-filters.tsx b/apps/web/lib/features/task/task-filters.tsx index 08fd99a55..ec9475b71 100644 --- a/apps/web/lib/features/task/task-filters.tsx +++ b/apps/web/lib/features/task/task-filters.tsx @@ -284,7 +284,7 @@ export function TaskFilter({ className, hook, profile }: IClassName & Props) { className="w-full" ref={hook.tab !== 'dailyplan' ? hook.outclickFilterCard.targetEl : null} > - {hook.filterType !== undefined && } + {hook.filterType !== undefined && } {hook.filterType === 'status' && ( )} @@ -389,7 +389,7 @@ function InputFilters({ hook, profile }: Props) { /* It's a function that returns a nav element. */ function TabsNav({ hook }: { hook: I_TaskFilter }) { return ( -
- ) : ( - <> - )} - - )} - - )} - - - - ))} - - - ) : ( - - )} -
- ); +function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; currentTab?: FilterTabs }) { + // Filter plans + const filteredPlans = useRef([]); + const { deleteDailyPlan, deleteDailyPlanLoading, sortedPlans, todayPlan } = useDailyPlan(); + const [popupOpen, setPopupOpen] = useState(false); + const [currentDeleteIndex, setCurrentDeleteIndex] = useState(0); + const { date } = useDateRange(currentTab); + + if (currentTab === 'Today Tasks') { + filteredPlans.current = todayPlan; + } else { + filteredPlans.current = sortedPlans; + } + + const canSeeActivity = useCanSeeActivityScreen(); + const view = useAtomValue(dailyPlanViewHeaderTabs); + + 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, setPlans)}> + new Date(plan.date).toISOString().split('T')[0])[0]] + } + > + {plans.map((plan, index) => ( + + +
+
+ {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) +
+ +
+
+ + + + {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( +
    + {plan.tasks?.map((task, index) => + view === 'CARDS' ? ( + + {(provided) => ( +
    + +
    + )} +
    + ) : ( + + {(provided) => ( +
    + +
    + )} +
    + ) + )} + <>{provided.placeholder} + {currentTab === 'Today Tasks' && ( + <> + {canSeeActivity ? ( +
    + { + setCurrentDeleteIndex(index); + setPopupOpen((prev) => !prev); + }} + variant="outline" + className="px-4 py-2 text-sm font-medium text-red-600 border border-red-600 rounded-md bg-light--theme-light dark:!bg-dark--theme-light" + > + Delete this plan + + } + > + {/*button confirm*/} + + {/*button cancel*/} + + +
    + ) : ( + <> + )} + + )} +
+ )} +
+
+
+ ))} +
+
+ ) : ( + + )} +
+ ); } -export function PlanHeader({ - plan, - planMode -}: { - plan: IDailyPlan; - planMode: FilterTabs; -}) { - const [editTime, setEditTime] = useState(false); - const [time, setTime] = useState(0); - const { updateDailyPlan, updateDailyPlanLoading } = useDailyPlan(); - const { isTeamManager } = useAuthenticateUser(); - const t = useTranslations(); - // Get all tasks's estimations time - // Helper function to sum times - 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 = 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 = 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; - - // Completion percent - const completionPercent = - totalTasks > 0 ? ((completedTasks * 100) / totalTasks).toFixed(0) : '0.0'; - - return ( -
- {/* Planned Time */} - -
- {!editTime && !updateDailyPlanLoading ? ( - <> -
- - {t('dailyPlan.PLANNED_TIME')} :{' '} - - - {formatIntegerToHour(plan.workTimePlanned)} - -
- {(!checkPastDate(plan.date) || isTeamManager) && ( - setEditTime(true)} - /> - )} - - ) : ( -
- setTime(parseFloat(e.target.value))} - /> - - {updateDailyPlanLoading ? ( - - ) : ( - { - updateDailyPlan({ workTimePlanned: time }, plan.id ?? ''); - setEditTime(false); - }} - /> - )} - -
- )} -
- - {/* Total estimated time based on tasks */} - - -
- {t('dailyPlan.ESTIMATED_TIME')} : - - {formatIntegerToHour(estimatedTime / 3600)} - -
- - {planMode !== 'Future Tasks' && } - - {/* Total worked time for the plan */} - {planMode !== 'Future Tasks' && ( -
- - {t('dailyPlan.TOTAL_TIME_WORKED')} :{' '} - - - {formatIntegerToHour(totalWorkTime / 3600)} - -
- )} - - {planMode !== 'Future Tasks' && } - - {/* Completed tasks */} - {planMode !== 'Future Tasks' && ( -
-
- - {t('dailyPlan.COMPLETED_TASKS')} :{' '} - - {`${completedTasks}/${totalTasks}`} -
-
- {t('dailyPlan.READY')}: - {readyTasks} -
-
- {t('dailyPlan.LEFT')}: - - {totalTasks - completedTasks - readyTasks} - -
-
- )} - - - - {/* Completion progress */} - {planMode !== 'Future Tasks' && ( -
-
- {t('dailyPlan.COMPLETION')}: - {completionPercent}% -
- -
- )} - - {/* Future tasks total plan */} - {planMode === 'Future Tasks' && ( -
-
- - {t('dailyPlan.PLANNED_TASKS')}:{' '} - - {totalTasks} -
-
- )} -
- ); +export function PlanHeader({ plan, planMode }: { plan: IDailyPlan; planMode: FilterTabs }) { + const [editTime, setEditTime] = useState(false); + const [time, setTime] = useState(0); + const { updateDailyPlan, updateDailyPlanLoading } = useDailyPlan(); + const { isTeamManager } = useAuthenticateUser(); + const t = useTranslations(); + // Get all tasks's estimations time + // Helper function to sum times + 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 = 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 = 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; + + // Completion percent + const completionPercent = totalTasks > 0 ? ((completedTasks * 100) / totalTasks).toFixed(0) : '0.0'; + + return ( +
+ {/* Planned Time */} + +
+ {!editTime && !updateDailyPlanLoading ? ( + <> +
+ {t('dailyPlan.PLANNED_TIME')} : + {formatIntegerToHour(plan.workTimePlanned)} +
+ {(!checkPastDate(plan.date) || isTeamManager) && ( + setEditTime(true)} + /> + )} + + ) : ( +
+ setTime(parseFloat(e.target.value))} + /> + + {updateDailyPlanLoading ? ( + + ) : ( + { + updateDailyPlan({ workTimePlanned: time }, plan.id ?? ''); + setEditTime(false); + }} + /> + )} + +
+ )} +
+ + {/* Total estimated time based on tasks */} + + +
+ {t('dailyPlan.ESTIMATED_TIME')} : + {formatIntegerToHour(estimatedTime / 3600)} +
+ + {planMode !== 'Future Tasks' && } + + {/* Total worked time for the plan */} + {planMode !== 'Future Tasks' && ( +
+ {t('dailyPlan.TOTAL_TIME_WORKED')} : + {formatIntegerToHour(totalWorkTime / 3600)} +
+ )} + + {planMode !== 'Future Tasks' && } + + {/* Completed tasks */} + {planMode !== 'Future Tasks' && ( +
+
+ {t('dailyPlan.COMPLETED_TASKS')} : + {`${completedTasks}/${totalTasks}`} +
+
+ {t('dailyPlan.READY')}: + {readyTasks} +
+
+ {t('dailyPlan.LEFT')}: + {totalTasks - completedTasks - readyTasks} +
+
+ )} + + + + {/* Completion progress */} + {planMode !== 'Future Tasks' && ( +
+
+ {t('dailyPlan.COMPLETION')}: + {completionPercent}% +
+ +
+ )} + + {/* Future tasks total plan */} + {planMode === 'Future Tasks' && ( +
+
+ {t('dailyPlan.PLANNED_TASKS')}: + {totalTasks} +
+
+ )} +
+ ); } export function EmptyPlans({ planMode }: Readonly<{ planMode?: FilterTabs }>) { - const t = useTranslations(); - - return ( -
- } - /> -
- ); + const t = useTranslations(); + + return ( +
+ } + /> +
+ ); } diff --git a/apps/web/lib/layout/navbar.tsx b/apps/web/lib/layout/navbar.tsx index 4f49a920a..d5b34d670 100644 --- a/apps/web/lib/layout/navbar.tsx +++ b/apps/web/lib/layout/navbar.tsx @@ -18,93 +18,108 @@ import { useAtom, useAtomValue } from 'jotai'; import { fullWidthState } from '@app/stores/fullWidth'; const HeaderSkeleton = () => { - return ( - + ); }; export function Navbar({ - className, - showTimer, - publicTeam, - notFound + className, + showTimer, + publicTeam, + notFound }: IClassName & { - showTimer?: boolean; - publicTeam?: boolean; - notFound?: boolean; + showTimer?: boolean; + publicTeam?: boolean; + notFound?: boolean; }) { - const t = useTranslations(); - const { isTeamMember } = useOrganizationTeams(); - const [user] = useAtom(userState); - const { isOpen, closeModal, openModal } = useModal(); - const fullWidth = useAtomValue(fullWidthState); + const t = useTranslations(); + const { isTeamMember } = useOrganizationTeams(); + const [user] = useAtom(userState); + const { isOpen, closeModal, openModal } = useModal(); + const fullWidth = useAtomValue(fullWidthState); - const pathname = usePathname(); + const pathname = usePathname(); - const isTeamDropdownAllowed = useMemo(() => { - if (!pathname) { - return false; - } - const notAllowedList = ['/task/[id]', '/profile/[memberId]']; - return !notAllowedList.includes(pathname); - }, [pathname]); + const isTeamDropdownAllowed = useMemo(() => { + if (!pathname) { + return false; + } + const notAllowedList = ['/task/[id]', '/profile/[memberId]']; + return !notAllowedList.includes(pathname); + }, [pathname]); - return ( -
- {!user && !notFound && !publicTeam ? ( - - ) : ( -
+ + + + )} +
+ ); } diff --git a/apps/web/locales/ar.json b/apps/web/locales/ar.json index 5f56fd779..366a8a383 100644 --- a/apps/web/locales/ar.json +++ b/apps/web/locales/ar.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "خطة اليوم", + "TASK_TIME": "وقت المهمة", + "TOTAL_TODAY": "الإجمالي اليوم", "PLAN_FOR_TOMORROW": "خطة الغد", "PLAN_FOR_SOME_DAY": "خطة ليوم ما", "ADD_TASK_TO_PLAN": "أضف هذه المهمة إلى الخطة", diff --git a/apps/web/locales/bg.json b/apps/web/locales/bg.json index 209e832c9..578d64818 100644 --- a/apps/web/locales/bg.json +++ b/apps/web/locales/bg.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "План за днес", + "TASK_TIME": "време за задача", + "TOTAL_TODAY": "Общо Днес", "PLAN_FOR_TOMORROW": "План за утре", "PLAN_FOR_SOME_DAY": "План за някой ден", "ADD_TASK_TO_PLAN": "Добави тази задача към плана", diff --git a/apps/web/locales/de.json b/apps/web/locales/de.json index f7c2b4f45..43e1088b2 100644 --- a/apps/web/locales/de.json +++ b/apps/web/locales/de.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plan für heute", + "TASK_TIME": "aufgabenzeit", + "TOTAL_TODAY": "Gesamt Heute", "PLAN_FOR_TOMORROW": "Plan für morgen", "PLAN_FOR_SOME_DAY": "Plan für irgendeinen Tag", "ADD_TASK_TO_PLAN": "Diese Aufgabe zum Plan hinzufügen", diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json index b065e7ef2..2febfb13e 100644 --- a/apps/web/locales/en.json +++ b/apps/web/locales/en.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plan for today", + "TASK_TIME": "task time", + "TOTAL_TODAY": "Total Today", "PLAN_FOR_TOMORROW": "Plan for tomorrow", "PLAN_FOR_SOME_DAY": "Plan for selected day", "ADD_TASK_TO_PLAN": "Add this task to a plan", diff --git a/apps/web/locales/es.json b/apps/web/locales/es.json index eb6061601..fa97289f2 100644 --- a/apps/web/locales/es.json +++ b/apps/web/locales/es.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plan para hoy", + "TASK_TIME": "tiempo de tarea", + "TOTAL_TODAY": "Total Hoy", "PLAN_FOR_TOMORROW": "Plan para mañana", "PLAN_FOR_SOME_DAY": "Plan para algún día", "ADD_TASK_TO_PLAN": "Agregar esta tarea a un plan", diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json index 00bb80be0..b422827f3 100644 --- a/apps/web/locales/fr.json +++ b/apps/web/locales/fr.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plan pour aujourd'hui", + "TASK_TIME": "temps de tâche", + "TOTAL_TODAY": "Total Aujourd'hui", "PLAN_FOR_TOMORROW": "Plan pour demain", "PLAN_FOR_SOME_DAY": "Plan pour une date", "ADD_TASK_TO_PLAN": "Ajouter cette tâche au plan", diff --git a/apps/web/locales/he.json b/apps/web/locales/he.json index 58dee1f99..36c0a130c 100644 --- a/apps/web/locales/he.json +++ b/apps/web/locales/he.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "תוכנית להיום", + "TASK_TIME": "זמן משימה", + "TOTAL_TODAY": "סך הכול היום", "PLAN_FOR_TOMORROW": "תוכנית למחר", "PLAN_FOR_SOME_DAY": "תוכנית ליום כלשהו", "ADD_TASK_TO_PLAN": "הוסף משימה זו לתוכנית", diff --git a/apps/web/locales/it.json b/apps/web/locales/it.json index 9c31d6ef1..09de582a1 100644 --- a/apps/web/locales/it.json +++ b/apps/web/locales/it.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Piano per oggi", + "TASK_TIME": "tempo di compito", + "TOTAL_TODAY": "Totale Oggi", "PLAN_FOR_TOMORROW": "Piano per domani", "PLAN_FOR_SOME_DAY": "Piano per un giorno", "ADD_TASK_TO_PLAN": "Aggiungi questa attività al piano", diff --git a/apps/web/locales/nl.json b/apps/web/locales/nl.json index b5dd23f75..d529bc994 100644 --- a/apps/web/locales/nl.json +++ b/apps/web/locales/nl.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plan voor vandaag", + "TASK_TIME": "taken tijd", + "TOTAL_TODAY": "Totaal Vandaag", "PLAN_FOR_TOMORROW": "Plan voor morgen", "PLAN_FOR_SOME_DAY": "Plan voor een dag", "ADD_TASK_TO_PLAN": "Voeg deze taak toe aan een plan", diff --git a/apps/web/locales/pl.json b/apps/web/locales/pl.json index 6f9576da1..b3c753449 100644 --- a/apps/web/locales/pl.json +++ b/apps/web/locales/pl.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plan na dzisiaj", + "TASK_TIME": "czas zadania", + "TOTAL_TODAY": "Całkowity Dzisiaj", "PLAN_FOR_TOMORROW": "Plan na jutro", "PLAN_FOR_SOME_DAY": "Plan na pewien dzień", "ADD_TASK_TO_PLAN": "Dodaj tę zadanie do planu", diff --git a/apps/web/locales/pt.json b/apps/web/locales/pt.json index c9ccab653..efcbf1bc8 100644 --- a/apps/web/locales/pt.json +++ b/apps/web/locales/pt.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "Plano para hoje", + "TASK_TIME": "tempo de tarefa", + "TOTAL_TODAY": "Total Hoje", "PLAN_FOR_TOMORROW": "Plano para amanhã", "PLAN_FOR_SOME_DAY": "Plano para algum dia", "ADD_TASK_TO_PLAN": "Adicionar esta tarefa ao plano", diff --git a/apps/web/locales/ru.json b/apps/web/locales/ru.json index 6b77428b1..defbb101c 100644 --- a/apps/web/locales/ru.json +++ b/apps/web/locales/ru.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "План на сегодня", + "TASK_TIME": "время задачи", + "TOTAL_TODAY": "Итого сегодня", "PLAN_FOR_TOMORROW": "План на завтра", "PLAN_FOR_SOME_DAY": "План на какой-то день", "ADD_TASK_TO_PLAN": "Добавить эту задачу в план", diff --git a/apps/web/locales/zh.json b/apps/web/locales/zh.json index 3a0522790..d60241854 100644 --- a/apps/web/locales/zh.json +++ b/apps/web/locales/zh.json @@ -589,6 +589,8 @@ }, "dailyPlan": { "PLAN_FOR_TODAY": "今日计划", + "TASK_TIME": "任务时间", + "TOTAL_TODAY": "今天的总计", "PLAN_FOR_TOMORROW": "明日计划", "PLAN_FOR_SOME_DAY": "未来计划", "ADD_TASK_TO_PLAN": "添加此任务到计划", From d9a8aa16c629a337c898bf976fb44398f3ac07de Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 25 Sep 2024 20:10:36 +0200 Subject: [PATCH 06/10] feat: optimize space in the profile page --- apps/web/app/[locale]/page-component.tsx | 277 +++++++++++++---------- 1 file changed, 156 insertions(+), 121 deletions(-) diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 24706b353..3a2b3b778 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -8,7 +8,13 @@ import { clsxm } from '@app/utils'; import NoTeam from '@components/pages/main/no-team'; import { withAuthentication } from 'lib/app/authenticator'; import { Breadcrumb, Card } from 'lib/components'; -import { AuthUserTaskInput, TeamInvitations, TeamMembers, Timer, UnverifiedEmail } from 'lib/features'; +import { + AuthUserTaskInput, + TeamInvitations, + TeamMembers, + Timer, + UnverifiedEmail +} from 'lib/features'; import { MainLayout } from 'lib/layout'; import { IssuesView } from '@app/constants'; import { useNetworkState } from '@uidotdev/usehooks'; @@ -29,134 +35,163 @@ import { headerTabs } from '@app/stores/header-tabs'; import { usePathname } from 'next/navigation'; import { PeoplesIcon } from 'assets/svg'; import TeamMemberHeader from 'lib/features/team-member-header'; -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable'; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup +} from '@components/ui/resizable'; import { TeamOutstandingNotifications } from 'lib/features/team/team-outstanding-notifications'; function MainPage() { - const t = useTranslations(); - const [headerSize, setHeaderSize] = useState(10); - const { isTeamMember, isTrackingEnabled, activeTeam } = useOrganizationTeams(); - const [fullWidth, setFullWidth] = useAtom(fullWidthState); - const [view, setView] = useAtom(headerTabs); - const path = usePathname(); - const breadcrumb = [ - { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, - { title: activeTeam?.name || '', href: '/' }, - { title: t(`common.${view}`), href: `/` } - ]; - const { online } = useNetworkState(); - useEffect(() => { - if (view == IssuesView.KANBAN && path == '/') { - setView(IssuesView.CARDS); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [path, setView]); + const t = useTranslations(); + const [headerSize, setHeaderSize] = useState(10); + const { + isTeamMember, + isTrackingEnabled, + activeTeam + } = useOrganizationTeams(); + const [fullWidth, setFullWidth] = useAtom(fullWidthState); + const [view, setView] = useAtom(headerTabs); + const path = usePathname(); + const breadcrumb = [ + { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, + { title: activeTeam?.name || '', href: '/' }, + { title: t(`common.${view}`), href: `/` } + ]; + const { online } = useNetworkState(); + useEffect(() => { + if (view == IssuesView.KANBAN && path == '/') { + setView(IssuesView.CARDS); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [path, setView]); - React.useEffect(() => { - window && window?.localStorage.getItem('conf-fullWidth-mode'); - setFullWidth(JSON.parse(window?.localStorage.getItem('conf-fullWidth-mode') || 'true')); - }, [setFullWidth]); + React.useEffect(() => { + window && window?.localStorage.getItem('conf-fullWidth-mode'); + setFullWidth( + JSON.parse(window?.localStorage.getItem('conf-fullWidth-mode') || 'true') + ); + }, [setFullWidth]); - if (!online) { - return ; - } - return ( - <> -
- {/*
*/} - - -
- - {/* */} - setHeaderSize(size)} - > -
-
-
-
- - -
-
- -
-
-
-
- - - -
- {isTeamMember ? ( - - ) : null} -
- -
-
-
- - {/*
*/} - -
{isTeamMember ? : }
-
-
+ if (!online) { + return ; + } + return ( + <> +
+ {/*
*/} + + +
+ + {/* */} + setHeaderSize(size)} + > +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
- -
- - - ); + {isTeamMember ? ( + + ) : null} +
+ +
+
+ + + {/* */} + +
+ {isTeamMember ? ( + + ) : ( + + )} +
+
+ +
+ +
+ + + ); } -function TaskTimerSection({ isTrackingEnabled }: { isTrackingEnabled: boolean }) { - const [showInput, setShowInput] = React.useState(false); - return ( - - -
setShowInput((p) => !p)} - className="border dark:border-[#26272C] w-full rounded p-2 md:hidden flex justify-center mt-2" - > - - {showInput ? 'hide the issue input' : 'show the issue input'} - -
- {isTrackingEnabled ? ( -
- -
- ) : null} -
- ); +function TaskTimerSection({ + isTrackingEnabled +}: { + isTrackingEnabled: boolean; +}) { + const [showInput, setShowInput] = React.useState(false); + return ( + + +
setShowInput((p) => !p)} + className="border dark:border-[#26272C] w-full rounded p-2 md:hidden flex justify-center mt-2" + > + + {showInput ? 'hide the issue input' : 'show the issue input'} + +
+ {isTrackingEnabled ? ( +
+ +
+ ) : null} +
+ ); } export default withAuthentication(MainPage, { displayName: 'MainPage' }); From 9e5ee476c08e38ad9421cca54710efd31e80c3f0 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Thu, 26 Sep 2024 09:30:59 +0000 Subject: [PATCH 07/10] Optimize Jitsu config creation with useMemo hook --- apps/web/lib/settings/JitsuRoot.tsx | 53 +++++++++++++++-------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/web/lib/settings/JitsuRoot.tsx b/apps/web/lib/settings/JitsuRoot.tsx index 4d6782a44..201d2a4a5 100644 --- a/apps/web/lib/settings/JitsuRoot.tsx +++ b/apps/web/lib/settings/JitsuRoot.tsx @@ -2,7 +2,7 @@ import type { JitsuOptions } from '@jitsu/jitsu-react/dist/useJitsu'; import { JitsuProvider } from '@jitsu/jitsu-react'; import { setNextPublicEnv } from '@app/env'; -import React from 'react'; +import React, { useMemo } from 'react'; import { JitsuAnalytics } from 'lib/components/services/jitsu-analytics'; type MyAppProps = { @@ -17,33 +17,34 @@ type MyAppProps = { export function JitsuRoot({ pageProps, children }: MyAppProps) { pageProps?.envs && setNextPublicEnv(pageProps?.envs); - const jitsuConf = pageProps?.jitsuConf || { - host: process.env.NEXT_PUBLIC_JITSU_BROWSER_URL, - writeKey: process.env.NEXT_PUBLIC_JITSU_BROWSER_WRITE_KEY, - debug: false, - cookieDomain: process.env.NEXT_PUBLIC_JITSU_COOKIE_DOMAIN, - echoEvents: false - }; - const isJitsuEnvs: boolean = !!jitsuConf.host && !!jitsuConf.writeKey; - console.log(`Jitsu Enabled: ${isJitsuEnvs}`); - console.log(`Jitsu Configuration: ${JSON.stringify(jitsuConf)}`); + + const options = useMemo(() => { + const jitsuConf = pageProps?.jitsuConf || { + host: process.env.NEXT_PUBLIC_JITSU_BROWSER_URL, + writeKey: process.env.NEXT_PUBLIC_JITSU_BROWSER_WRITE_KEY, + debug: false, + cookieDomain: process.env.NEXT_PUBLIC_JITSU_COOKIE_DOMAIN, + echoEvents: false + }; + + const isJitsuEnvs: boolean = !!jitsuConf.host && !!jitsuConf.writeKey; + + console.log(`Jitsu Enabled: ${isJitsuEnvs}`); + console.log(`Jitsu Configuration: ${JSON.stringify(jitsuConf)}`); + + return isJitsuEnvs + ? { + host: jitsuConf.host ?? '', + writeKey: jitsuConf.writeKey ?? undefined, + debug: jitsuConf.debug, + cookieDomain: jitsuConf.cookieDomain ?? undefined, + echoEvents: jitsuConf.echoEvents + } + : { disabled: true }; + }, [pageProps?.jitsuConf]); return ( - + {children} From 779b5628426c2aa2ca40feca5777c06675d7f12e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 26 Sep 2024 13:47:01 +0200 Subject: [PATCH 08/10] employeeId should be a string --- apps/web/app/hooks/features/useDailyPlan.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/web/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts index 7f46646fa..e0d038ba1 100644 --- a/apps/web/app/hooks/features/useDailyPlan.ts +++ b/apps/web/app/hooks/features/useDailyPlan.ts @@ -82,11 +82,13 @@ export function useDailyPlan() { const getEmployeeDayPlans = useCallback( (employeeId: string) => { - queryCall(employeeId).then((response) => { - const { items, total } = response.data; - setProfileDailyPlans({ items, total }); - setEmployeePlans(items); - }); + if (employeeId && typeof employeeId === 'string') { + queryCall(employeeId).then((response) => { + const { items, total } = response.data; + setProfileDailyPlans({ items, total }); + setEmployeePlans(items); + }); + } }, [queryCall, setEmployeePlans, setProfileDailyPlans] ); From 88c84f301dfd86adb0ff723fafe1197232f904e5 Mon Sep 17 00:00:00 2001 From: Innocent-akim Date: Thu, 26 Sep 2024 15:55:17 +0200 Subject: [PATCH 09/10] fix: validate employee object and employeeId string format in request payload --- apps/web/app/hooks/features/useDailyPlan.ts | 6 ++- .../create-daily-plan-form-modal.tsx | 8 +-- apps/web/lib/features/task/task-card.tsx | 54 +++++++++---------- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/apps/web/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts index 7f46646fa..2d2759609 100644 --- a/apps/web/app/hooks/features/useDailyPlan.ts +++ b/apps/web/app/hooks/features/useDailyPlan.ts @@ -104,7 +104,11 @@ export function useDailyPlan() { async (data: ICreateDailyPlan) => { if (user?.tenantId) { const res = await createQueryCall( - { ...data, organizationTeamId: activeTeam?.id }, + { + ...data, + organizationTeamId: activeTeam?.id, + employeeId: user?.employee?.id + }, user?.tenantId || '' ); //Check if there is an existing plan diff --git a/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx b/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx index 282ef542a..329871028 100644 --- a/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx +++ b/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx @@ -41,7 +41,7 @@ export function CreateDailyPlanFormModal({ ) as 'Select' | 'Select & Close'; const existingPlanDates = useMemo( - () => profileDailyPlans.items.map((plan) => new Date(plan.date)), + () => profileDailyPlans?.items?.map((plan) => new Date(plan.date)), [profileDailyPlans.items] ); @@ -287,12 +287,12 @@ function MembersList({ {(member?.employee?.user?.image?.thumbUrl || member?.employee?.user?.image?.fullUrl || member?.employee?.user?.imageUrl) && - isValidUrl( - member?.employee?.user?.image?.thumbUrl || + isValidUrl( + member?.employee?.user?.image?.thumbUrl || member?.employee?.user?.image?.fullUrl || member?.employee?.user?.imageUrl || '' - ) ? ( + ) ? ( - -
- {!taskPlannedToday && ( -
  • - -
  • - )} - {!taskPlannedTomorrow && ( + <> + +
    + {!taskPlannedToday && ( +
  • + +
  • + )} + {!taskPlannedTomorrow && ( +
  • + +
  • + )}
  • - )} -
  • - -
  • -
    - - )} +
    + + )} {viewType === 'dailyplan' && (planMode === 'Today Tasks' || From 751c8b60c0ca047a29ad26cf5d814914e65a0db4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 26 Sep 2024 19:13:04 +0200 Subject: [PATCH 10/10] feat: today's date in calenday should look differently --- apps/web/lib/features/daily-plan/all-plans-modal.tsx | 3 ++- .../lib/features/daily-plan/create-daily-plan-form-modal.tsx | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/web/lib/features/daily-plan/all-plans-modal.tsx b/apps/web/lib/features/daily-plan/all-plans-modal.tsx index 5f851fe19..67096e60d 100644 --- a/apps/web/lib/features/daily-plan/all-plans-modal.tsx +++ b/apps/web/lib/features/daily-plan/all-plans-modal.tsx @@ -353,7 +353,8 @@ const FuturePlansCalendar = memo(function FuturePlansCalendar(props: ICalendarPr pastDay: clsxm( 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-yellow-600 after:rounded-full' - ) + ), + today: clsxm('border-2 !border-yellow-700 rounded') }} fromYear={new Date(sortedPlans?.[0]?.date ?? Date.now())?.getFullYear()} toYear={new Date(sortedPlans?.[sortedPlans?.length - 1]?.date ?? Date.now())?.getFullYear() + 5} diff --git a/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx b/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx index 282ef542a..345258348 100644 --- a/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx +++ b/apps/web/lib/features/daily-plan/create-daily-plan-form-modal.tsx @@ -242,7 +242,10 @@ const CustomCalendar = memo(function CustomCalendar({ modifiers={{ booked: existingPlanDates }} - modifiersClassNames={{ booked: 'bg-primary text-white' }} + modifiersClassNames={{ + booked: 'bg-primary text-white', + today: clsxm('border-2 !border-yellow-700 rounded') + }} fromYear={new Date().getUTCFullYear()} toYear={new Date().getUTCFullYear() + 5} />