Skip to content

Commit

Permalink
Member cant track without plan (#2635)
Browse files Browse the repository at this point in the history
* feat: popup add work time plan and estimated time to track time

* feat: un estimated tasks popups when want tracking

* feat: update unestimated tasks

* feat: track time after provide all estimates time

* refact: tasks to display onto un estimated

* feat: update daily plan work time planned before start tracking

* feat: start timer on task card require plan

* fix: update planned time and estimates warning

* fix: oauth env vars
  • Loading branch information
GloireMutaliko21 authored Jun 18, 2024
1 parent aa8c147 commit 4265b74
Show file tree
Hide file tree
Showing 21 changed files with 401 additions and 21 deletions.
23 changes: 23 additions & 0 deletions apps/web/.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ NEXT_PUBLIC_GAUZY_API_SERVER_URL=https://api.ever.team

NEXT_PUBLIC_GA_MEASUREMENT_ID=

# Next Auth configs
AUTH_SECRET=

# Google OAuth configuration
NEXT_PUBLIC_GOOGLE_APP_NAME=ever-google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# Github OAuth configuration
NEXT_PUBLIC_GITHUB_APP_NAME=ever-github
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# Facebook OAuth configuration
NEXT_PUBLIC_FACEBOOK_APP_NAME=ever-facebook
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=

# Twitter OAuth configuration
NEXT_PUBLIC_TWITTER_APP_NAME=ever-twitter
TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=

# CAPTCHA Settings
NEXT_PUBLIC_CAPTCHA_SITE_KEY=
CAPTCHA_SECRET_KEY=
Expand Down
18 changes: 18 additions & 0 deletions apps/web/app/hooks/features/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,20 +185,32 @@ export function useTimer() {
const lastActiveTeamId = useRef<string | null>(null);
const lastActiveTaskId = useRef<string | null>(null);

// Find if the connected user has a today plan. Help to know if he can track time when require daily plan is set to true
const hasPlan = myDailyPlans.items.find(
(plan) =>
plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0]) &&
plan.tasks &&
plan.tasks?.length > 0
);

// Team setting that tells if each member must have a today plan for allowing tracking time
const requirePlan = activeTeam?.requirePlanToTrack;

// If require plan setting is activated but user don't have plan, block time tracking until a today plan will be added
let canTrack = true;

if (requirePlan) {
if (!hasPlan) canTrack = false;
}

// If require plan setting is activated,
// check if the today plan has working time planned and all the tasks into the plan are estimated
let isPlanVerified = true;
if (requirePlan) {
isPlanVerified =
!!hasPlan?.workTimePlanned && !!hasPlan?.tasks?.every((task) => task.estimate && task.estimate > 0);
}

const canRunTimer =
user?.isEmailVerified &&
((!!activeTeamTask && activeTeamTask.status !== 'closed') ||
Expand Down Expand Up @@ -395,8 +407,10 @@ export function useTimer() {
firstLoadTimerData,
startTimer,
stopTimer,
hasPlan,
canRunTimer,
canTrack,
isPlanVerified,
firstLoad,
toggleTimer,
timerSeconds,
Expand Down Expand Up @@ -433,8 +447,10 @@ export function useTimerView() {
timerStatusFetching,
startTimer,
stopTimer,
hasPlan,
canRunTimer,
canTrack,
isPlanVerified,
timerSeconds,
activeTeamTask,
syncTimerLoading
Expand Down Expand Up @@ -462,8 +478,10 @@ export function useTimerView() {
timerStatusFetching,
timerStatus,
activeTeamTask,
hasPlan,
disabled: !canRunTimer,
canTrack,
isPlanVerified,
startTimer,
stopTimer,
syncTimerLoading
Expand Down
18 changes: 17 additions & 1 deletion apps/web/components/shared/timer/timer-card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { pad } from '@app/helpers/number';
import { useModal } from '@app/hooks';
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 { useTranslations } from 'next-intl';

const Timer = () => {
Expand All @@ -15,17 +17,25 @@ const Timer = () => {
startTimer,
stopTimer,
canRunTimer,
isPlanVerified,
hasPlan,
timerSeconds
} = useTimer();

const { activeTaskEstimation } = useTaskStatistics(timerSeconds);

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

const timerHanlder = () => {
if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else {
startTimer();
if (!isPlanVerified) {
openModal();
} else {
startTimer();
}
}
};

Expand All @@ -45,6 +55,12 @@ const Timer = () => {
>
{timerStatus?.running ? <PauseIcon width={68} height={68} /> : <PlayIcon width={68} height={68} />}
</div>
<AddWorkTimeAndEstimatesToPlan
closeModal={closeModal}
open={isOpen}
plan={hasPlan}
startTimer={startTimer}
/>
</>
);
};
Expand Down
148 changes: 148 additions & 0 deletions apps/web/lib/features/daily-plan/plans-work-time-and-estimate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { useEffect, useState } from 'react';
import { PiWarningCircleFill } from 'react-icons/pi';
import { IDailyPlan, ITeamTask } from '@app/interfaces';
import { Card, InputField, Modal, Text, VerticalSeparator } from 'lib/components';
import { useTranslations } from 'use-intl';
import { TaskNameInfoDisplay } from '../task/task-displays';
import { Button } from '@components/ui/button';
import { TaskEstimate } from '../task/task-estimate';
import { useDailyPlan, useTeamTasks } from '@app/hooks';

export function AddWorkTimeAndEstimatesToPlan({
open,
closeModal,
plan,
startTimer
// employee
}: {
open: boolean;
closeModal: () => void;
startTimer: () => void;
plan?: IDailyPlan;
// employee?: OT_Member;
}) {
const t = useTranslations();
const [workTimePlanned, setworkTimePlanned] = useState<number | undefined>(plan?.workTimePlanned);

useEffect(() => {
if (typeof workTimePlanned === 'string') setworkTimePlanned(parseFloat(workTimePlanned));
}, [workTimePlanned]);

const { updateDailyPlan } = useDailyPlan();

const { tasks: $tasks } = useTeamTasks();

const tasks = $tasks.filter((task) =>
plan?.tasks?.some((t) => task?.id === t.id && typeof task?.estimate === 'number' && task?.estimate <= 0)
);

const handleSubmit = () => {
if (workTimePlanned === 0 || typeof workTimePlanned !== 'number') return;
if (tasks.some((task) => task.estimate === 0)) return;

updateDailyPlan({ workTimePlanned }, plan?.id ?? '');
startTimer();
closeModal();
};

return (
<Modal isOpen={open} closeModal={closeModal} className="w-[98%] md:w-[530px] relative">
<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>
<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
defaultValue={plan?.workTimePlanned ?? 0}
/>
</div>

{tasks.length > 0 && (
<div className="text-sm flex flex-col gap-3">
<UnEstimatedTasks dailyPlan={plan} />

<div className="flex gap-2 items-center text-red-500">
<PiWarningCircleFill className="text-2xl" />
<p>{t('timer.todayPlanSettings.WARNING_PLAN_ESTIMATION')}</p>
</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={closeModal}
>
{t('common.CANCEL')}
</Button>
<Button
variant="default"
type="submit"
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>
);
}

function UnEstimatedTasks({ dailyPlan }: { dailyPlan?: IDailyPlan }) {
const t = useTranslations();

const { tasks: $tasks } = useTeamTasks();

const tasks = $tasks.filter((task) =>
dailyPlan?.tasks?.some((t) => task?.id === t.id && typeof task?.estimate === 'number' && task?.estimate <= 0)
);

return (
<div>
{tasks?.length > 0 && (
<div className="text-sm flex flex-col gap-3">
<span>
{t('timer.todayPlanSettings.TASKS_WITH_NO_ESTIMATIONS')} <span className="text-red-600">*</span>
</span>
<div className="flex flex-col gap-1">
{tasks && tasks?.map((task) => <UnEstimatedTask key={task.id} task={task} />)}
</div>
</div>
)}
</div>
);
}

export function UnEstimatedTask({ task }: { task: ITeamTask }) {
return (
<Card
shadow="custom"
className={
'lg:flex items-center justify-between py-3 px-4 md:px-4 hidden min-h-[4.5rem] dark:bg-[#1E2025] border-[0.05rem] dark:border-[#FFFFFF0D] relative !text-xs'
}
>
<div className="min-w-[50%] max-w-[50%]">
<TaskNameInfoDisplay task={task} />
</div>
<VerticalSeparator />
{/* <TaskEstimateInput memberInfo={memberInfo} edition={taskEdition} /> */}
<TaskEstimate _task={task} />
</Card>
);
}
1 change: 1 addition & 0 deletions apps/web/lib/features/task/daily-plan/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './outstanding';
export * from './past-tasks';
export * from './future-tasks';
49 changes: 42 additions & 7 deletions apps/web/lib/features/task/task-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { useTranslations } from 'next-intl';
import { SixSquareGridIcon, ThreeCircleOutlineVerticalIcon } from 'assets/svg';
import { CreateDailyPlanFormModal } from '../daily-plan/create-daily-plan-form-modal';
import { AddTaskToPlan } from '../daily-plan/add-task-to-plan';
import { AddWorkTimeAndEstimatesToPlan } from '../daily-plan/plans-work-time-and-estimate';

type Props = {
active?: boolean;
Expand Down Expand Up @@ -329,7 +330,20 @@ function TimerButtonCall({
}) {
const [loading, setLoading] = useState(false);
const { updateOrganizationTeamEmployee } = useOrganizationEmployeeTeams();
const { canTrack, disabled, timerHanlder, timerStatus, activeTeamTask, startTimer, stopTimer } = useTimerView();
const { closeModal, isOpen, openModal } = useModal();

const {
canTrack,
disabled,
canRunTimer,
timerStatusFetching,
timerStatus,
activeTeamTask,
startTimer,
stopTimer,
isPlanVerified,
hasPlan
} = useTimerView();

const { setActiveTask } = useTeamTasks();

Expand Down Expand Up @@ -371,15 +385,36 @@ function TimerButtonCall({
updateOrganizationTeamEmployee
]);

const timerHanlderStartStop = useCallback(() => {
if (timerStatusFetching || !canRunTimer) return;
if (timerStatus?.running) {
stopTimer();
} else {
if (!isPlanVerified) {
openModal();
} else {
startTimer();
}
}
}, [canRunTimer, isPlanVerified, openModal, startTimer, stopTimer, timerStatus, timerStatusFetching]);

return loading ? (
<SpinnerLoader size={30} />
) : (
<TimerButton
onClick={activeTaskStatus ? timerHanlder : startTimerWithTask}
running={activeTaskStatus?.running}
disabled={activeTaskStatus ? disabled : task.status === 'closed' || !canTrack}
className={clsxm('h-14 w-14', className)}
/>
<>
<TimerButton
onClick={activeTaskStatus ? timerHanlderStartStop : startTimerWithTask}
running={activeTaskStatus?.running}
disabled={activeTaskStatus ? disabled : task.status === 'closed' || !canTrack}
className={clsxm('h-14 w-14', className)}
/>
<AddWorkTimeAndEstimatesToPlan
closeModal={closeModal}
open={isOpen}
plan={hasPlan}
startTimer={startTimer}
/>
</>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function TaskEstimateInfo({ className, activeAuthTask, showTime = true, r
);
}

function TaskEstimateInput({ memberInfo, edition }: Omit<Props, 'className' | 'activeAuthTask'>) {
export function TaskEstimateInput({ memberInfo, edition }: Omit<Props, 'className' | 'activeAuthTask'>) {
const t = useTranslations();
const loadingRef = useRef<boolean>(false);
const task = edition.task || memberInfo.memberTask;
Expand Down
Loading

0 comments on commit 4265b74

Please sign in to comment.