diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index 8de63bd88..3a2b3b778 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -105,7 +105,7 @@ function MainPage() { !fullWidth && 'x-container' )} > -
+
@@ -115,9 +115,11 @@ function MainPage() {
- - - +
+ + + +
{isTeamMember ? ( ('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/app/hooks/features/useDailyPlan.ts b/apps/web/app/hooks/features/useDailyPlan.ts index de33fb21a..af285b25b 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] ); @@ -104,15 +106,19 @@ 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 - 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 +133,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 +157,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 +186,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 +211,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 +236,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 +271,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/components/notifications-dropdown/index.tsx b/apps/web/lib/components/notifications-dropdown/index.tsx new file mode 100644 index 000000000..af5fc8d1b --- /dev/null +++ b/apps/web/lib/components/notifications-dropdown/index.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Disclosure } from '@headlessui/react'; +import { ChevronUpIcon } from '@heroicons/react/20/solid'; +import { ScrollArea, ScrollBar } from '@components/ui/scroll-bar'; + +/** + * 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: { children: React.ReactNode }) { + 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/daily-plan/add-task-estimation-hours-modal.tsx b/apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx index 8d57812f8..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/all-plans-modal.tsx b/apps/web/lib/features/daily-plan/all-plans-modal.tsx index 1744d094b..67096e60d 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 " /> +
+ )} )} @@ -290,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..5b1b5846d 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] ); @@ -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} /> @@ -287,12 +290,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 || '' - ) ? ( + ) ? ( 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 + +
+ + +
+
+
+
+ ); +} diff --git a/apps/web/lib/features/task/task-card.tsx b/apps/web/lib/features/task/task-card.tsx index f90ea1055..03abe112c 100644 --- a/apps/web/lib/features/task/task-card.tsx +++ b/apps/web/lib/features/task/task-card.tsx @@ -711,39 +711,39 @@ function TaskCardMenu({ {(viewType == 'default' || (viewType === 'dailyplan' && planMode === 'Outstanding')) && ( - <> - -
- {!taskPlannedToday && ( -
  • - -
  • - )} - {!taskPlannedTomorrow && ( + <> + +
    + {!taskPlannedToday && ( +
  • + +
  • + )} + {!taskPlannedTomorrow && ( +
  • + +
  • + )}
  • - )} -
  • - -
  • -
    - - )} +
    + + )} {viewType === 'dailyplan' && (planMode === 'Today Tasks' || 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 ( -