Skip to content

Commit

Permalink
2813 refactor timer all start stop buttons should use the same logic (#…
Browse files Browse the repository at this point in the history
…2814)

* feat: encapsulate start /stop timer in a custom hook

* feat: encapsulate start /stop timer logic in a custom hook
  • Loading branch information
CREDO23 authored Aug 1, 2024
1 parent 7d9059f commit e4e342b
Show file tree
Hide file tree
Showing 13 changed files with 746 additions and 578 deletions.
6 changes: 3 additions & 3 deletions apps/web/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ export const languagesFlags = [
// Local storage keys
export const LAST_WORSPACE_AND_TEAM = 'last-workspace-and-team';
export const USER_SAW_OUTSTANDING_NOTIFICATION = 'user-saw-notif';
export const TODAY_PLAN_ALERT_SHOWN_DATE = 'last-today-plan-alert-date';
export const ESTIMATE_POPUP_SHOWN_DATE = 'last-estimate-popup-date';
export const DAILY_PLAN_SHOW_MODAL = 'daily-plan-modal';
export const DAILY_PLAN_SUGGESTION_MODAL_DATE = 'daily-plan-suggestion-modal-date';
export const TASKS_ESTIMATE_HOURS_MODAL_DATE = 'tasks-estimate-hours-modal-date';
export const DAILY_PLAN_ESTIMATE_HOURS_MODAL_DATE = 'daily-plan-estimate-hours-modal';

// OAuth providers keys

Expand Down
149 changes: 149 additions & 0 deletions apps/web/app/hooks/features/useStartStopTimerHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { useCallback, useMemo } from 'react';
import { useModal } from '../useModal';
import { useTeamTasks } from './useTeamTasks';
import { useTimer } from './useTimer';
import {
DAILY_PLAN_SUGGESTION_MODAL_DATE,
TASKS_ESTIMATE_HOURS_MODAL_DATE,
DAILY_PLAN_ESTIMATE_HOURS_MODAL_DATE
} from '@app/constants';

export function useStartStopTimerHandler() {
const {
isOpen: isEnforceTaskModalOpen,
closeModal: enforceTaskCloseModal,
openModal: openEnforcePlannedTaskModal
} = useModal();

const {
isOpen: isDailyPlanWorkHoursModalOpen,
closeModal: dailyPlanWorkHoursCloseModal,
openModal: openAddDailyPlanWorkHoursModal
} = useModal();

const {
isOpen: isTasksEstimationHoursModalOpen,
closeModal: tasksEstimationHoursCloseModal,
openModal: openAddTasksEstimationHoursModal
} = useModal();

const {
isOpen: isSuggestDailyPlanModalOpen,
closeModal: suggestDailyPlanCloseModal,
openModal: openSuggestDailyPlanModal
} = useModal();

const { timerStatus, timerStatusFetching, startTimer, stopTimer, hasPlan, canRunTimer, activeTeamTask } =
useTimer();

const { activeTeam } = useTeamTasks();

const requirePlan = useMemo(() => activeTeam?.requirePlanToTrack, [activeTeam?.requirePlanToTrack]);

const hasWorkedHours = useMemo(
() => hasPlan?.workTimePlanned && hasPlan?.workTimePlanned > 0,
[hasPlan?.workTimePlanned]
);
const areAllTasksEstimated = useMemo(
() => hasPlan?.tasks?.every((el) => typeof el?.estimate === 'number' && el?.estimate > 0),
[hasPlan?.tasks]
);

const isActiveTaskPlaned = useMemo(
() => hasPlan?.tasks?.some((task) => task.id === activeTeamTask?.id),
[activeTeamTask?.id, hasPlan?.tasks]
);

const startStopTimerHandler = useCallback(() => {
const currentDate = new Date().toISOString().split('T')[0];
const dailyPlanSuggestionModalDate = window && window?.localStorage.getItem(DAILY_PLAN_SUGGESTION_MODAL_DATE);
const tasksEstimateHoursModalDate = window && window?.localStorage.getItem(TASKS_ESTIMATE_HOURS_MODAL_DATE);
const dailyPlanEstimateHoursModalDate =
window && window?.localStorage.getItem(DAILY_PLAN_ESTIMATE_HOURS_MODAL_DATE);

/**
* Handle missing working hour for a daily plN
*/
const handleMissingDailyPlanWorkHour = () => {
if (!hasWorkedHours) {
openAddDailyPlanWorkHoursModal();
} else {
startTimer();
}
};

/**
* Handler function to start or stop the timer based on various conditions.
* Shows appropriate modals and starts or stops the timer as needed.
*/
if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else if (requirePlan && !isActiveTaskPlaned) {
openEnforcePlannedTaskModal();
} else {
if (
dailyPlanSuggestionModalDate == currentDate &&
tasksEstimateHoursModalDate == currentDate &&
dailyPlanEstimateHoursModalDate == currentDate
) {
startTimer();
} else {
if (dailyPlanSuggestionModalDate != currentDate) {
openSuggestDailyPlanModal();
} else if (tasksEstimateHoursModalDate != currentDate) {
if (areAllTasksEstimated) {
if (dailyPlanEstimateHoursModalDate != currentDate) {
handleMissingDailyPlanWorkHour();
} else {
startTimer();
}
} else {
openAddTasksEstimationHoursModal();
}
} else if (dailyPlanEstimateHoursModalDate != currentDate) {
if (areAllTasksEstimated) {
handleMissingDailyPlanWorkHour();
} else {
startTimer();
}
} else {
// Default action to start the timer
startTimer();
}
}
}
}, [
areAllTasksEstimated,
canRunTimer,
hasWorkedHours,
isActiveTaskPlaned,
openAddDailyPlanWorkHoursModal,
openAddTasksEstimationHoursModal,
openEnforcePlannedTaskModal,
openSuggestDailyPlanModal,
requirePlan,
startTimer,
stopTimer,
timerStatus?.running,
timerStatusFetching
]);

return {
modals: {
isEnforceTaskModalOpen,
enforceTaskCloseModal,
openEnforcePlannedTaskModal,
isDailyPlanWorkHoursModalOpen,
dailyPlanWorkHoursCloseModal,
openAddDailyPlanWorkHoursModal,
isTasksEstimationHoursModalOpen,
tasksEstimationHoursCloseModal,
openAddTasksEstimationHoursModal,
isSuggestDailyPlanModalOpen,
suggestDailyPlanCloseModal,
openSuggestDailyPlanModal
},
startStopTimerHandler
};
}
67 changes: 37 additions & 30 deletions apps/web/components/shared/timer/timer-card.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
import { ESTIMATE_POPUP_SHOWN_DATE, TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';
import { pad } from '@app/helpers/number';
import { useModal } from '@app/hooks';
import { useTeamTasks } from '@app/hooks';
import { useStartStopTimerHandler } from '@app/hooks/features/useStartStopTimerHandler';
import { useTaskStatistics } from '@app/hooks/features/useTaskStatistics';
import { useTimer } from '@app/hooks/features/useTimer';
import { ProgressBar } from '@components/ui/progress-bar';
import { PauseIcon } from '@components/ui/svgs/pause-icon';
import { PlayIcon } from '@components/ui/svgs/play-icon';
import { AddWorkTimeAndEstimatesToPlan } from 'lib/features/daily-plan/plans-work-time-and-estimate';
import {
AddTasksEstimationHoursModal,
AddDailyPlanWorkHourModal,
EnforcePlanedTaskModal
} from 'lib/features/daily-plan';
import { useTranslations } from 'next-intl';
import { useMemo } from 'react';

const Timer = () => {
const t = useTranslations();
const {
fomatedTimeCounter: { hours, minutes, seconds, ms_p },
timerStatus,
timerStatusFetching,
startTimer,
stopTimer,
canRunTimer,
isPlanVerified,
hasPlan,
timerSeconds
} = useTimer();

const { activeTaskEstimation } = useTaskStatistics(timerSeconds);

const { closeModal, isOpen, openModal } = useModal();
const { modals, startStopTimerHandler } = useStartStopTimerHandler();

const timerHanlder = () => {
const currentDate = new Date().toISOString().split('T')[0];
const lastPopupDate = window && window?.localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
const lastPopupEstimates = window && window?.localStorage.getItem(ESTIMATE_POPUP_SHOWN_DATE);
const { activeTeam, activeTeamTask } = useTeamTasks();

if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else {
if (!isPlanVerified || lastPopupDate !== currentDate || lastPopupEstimates !== currentDate) {
openModal();
} else {
startTimer();
}
}
};
const requirePlan = useMemo(() => activeTeam?.requirePlanToTrack, [activeTeam?.requirePlanToTrack]);

return (
<>
Expand All @@ -56,17 +45,35 @@ const Timer = () => {
<div
title={timerStatusFetching || !canRunTimer ? t('timer.START_TIMER') : undefined}
className={`cursor-pointer ${timerStatusFetching || !canRunTimer ? 'opacity-30' : ''}`}
onClick={!timerStatusFetching ? timerHanlder : undefined}
onClick={!timerStatusFetching ? startStopTimerHandler : undefined}
>
{timerStatus?.running ? <PauseIcon width={68} height={68} /> : <PlayIcon width={68} height={68} />}
</div>
<AddWorkTimeAndEstimatesToPlan
closeModal={closeModal}
open={isOpen}
plan={hasPlan}
startTimer={startTimer}
hasPlan={!!hasPlan}
/>
{hasPlan && hasPlan.tasks && (
<AddTasksEstimationHoursModal
isOpen={modals.isTasksEstimationHoursModalOpen}
closeModal={modals.tasksEstimationHoursCloseModal}
plan={hasPlan}
tasks={hasPlan.tasks}
/>
)}

{hasPlan && (
<AddDailyPlanWorkHourModal
isOpen={modals.isDailyPlanWorkHoursModalOpen}
closeModal={modals.dailyPlanWorkHoursCloseModal}
plan={hasPlan}
/>
)}

{requirePlan && hasPlan && activeTeamTask && (
<EnforcePlanedTaskModal
closeModal={modals.enforceTaskCloseModal}
plan={hasPlan}
open={modals.isEnforceTaskModalOpen}
task={activeTeamTask}
/>
)}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Card, InputField, Modal, Text } from 'lib/components';
import { Button } from '@components/ui/button';
import { useCallback, useMemo, useState } from 'react';
import { DAILY_PLAN_ESTIMATE_HOURS_MODAL_DATE } from '@app/constants';
import { IDailyPlan } from '@app/interfaces';
import { useDailyPlan, useTeamTasks, useTimerView } from '@app/hooks';
import { useTranslations } from 'next-intl';

interface IAddDailyPlanWorkHoursModalProps {
closeModal: () => void;
isOpen: boolean;
plan: IDailyPlan;
}

export function AddDailyPlanWorkHourModal(props: IAddDailyPlanWorkHoursModalProps) {
const { closeModal, isOpen, plan } = props;

const t = useTranslations();
const { updateDailyPlan } = useDailyPlan();
const { startTimer } = useTimerView();
const { activeTeam } = useTeamTasks();

const [workTimePlanned, setworkTimePlanned] = useState<number | undefined>(plan.workTimePlanned);
const currentDate = useMemo(() => new Date().toISOString().split('T')[0], []);
const requirePlan = useMemo(() => activeTeam?.requirePlanToTrack, [activeTeam?.requirePlanToTrack]);
const hasWorkHours = useMemo(() => plan.workTimePlanned && plan.workTimePlanned > 0, [plan.workTimePlanned]);

const handleCloseModal = useCallback(() => {
localStorage.setItem(DAILY_PLAN_ESTIMATE_HOURS_MODAL_DATE, currentDate);
closeModal();
}, [closeModal, currentDate]);

const handleSubmit = useCallback(() => {
updateDailyPlan({ workTimePlanned }, plan.id ?? '');
startTimer();
handleCloseModal();
}, [handleCloseModal, plan.id, startTimer, updateDailyPlan, workTimePlanned]);

return (
<Modal isOpen={isOpen} closeModal={handleCloseModal} showCloseIcon={requirePlan ? false : true}>
<Card className="w-full" shadow="custom">
<div className="flex flex-col justify-between">
<div className="mb-7">
<Text.Heading as="h3" className="mb-3 text-center">
{t('timer.todayPlanSettings.TITLE')}
</Text.Heading>
<div className="mb-7 w-full flex flex-col gap-4">
<span className="text-sm">
{t('timer.todayPlanSettings.WORK_TIME_PLANNED')} <span className="text-red-600">*</span>
</span>

<InputField
type="number"
placeholder={t('timer.todayPlanSettings.WORK_TIME_PLANNED_PLACEHOLDER')}
className="mb-0 min-w-[350px]"
wrapperClassName="mb-0 rounded-lg"
onChange={(e) => setworkTimePlanned(parseFloat(e.target.value))}
required
min={0}
value={workTimePlanned}
defaultValue={plan.workTimePlanned ?? 0}
/>
</div>
</div>
<div className="mt-6 flex justify-between items-center">
<Button
variant="outline"
type="submit"
className="py-3 px-5 rounded-md font-light text-md dark:text-white dark:bg-slate-700 dark:border-slate-600"
onClick={handleCloseModal}
>
{t('common.SKIP_ADD_LATER')}
</Button>
<Button
variant="default"
type="submit"
disabled={requirePlan ? (hasWorkHours ? false : true) : false}
className="py-3 px-5 rounded-md font-light text-md dark:text-white"
onClick={handleSubmit}
>
{t('timer.todayPlanSettings.START_WORKING_BUTTON')}
</Button>
</div>
</div>
</Card>
</Modal>
);
}
Loading

0 comments on commit e4e342b

Please sign in to comment.