Skip to content

Commit

Permalink
Merge branch 'develop' into feat/drag-drop-daily-plan
Browse files Browse the repository at this point in the history
  • Loading branch information
Innocent-Akim committed Jul 17, 2024
2 parents 84b25c0 + 3280c0f commit d24cbff
Show file tree
Hide file tree
Showing 29 changed files with 317 additions and 128 deletions.
1 change: 1 addition & 0 deletions apps/web/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ 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';

// OAuth providers keys

Expand Down
10 changes: 7 additions & 3 deletions apps/web/app/helpers/plan-day-badge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { IDailyPlan, ITeamTask } from '@app/interfaces';
import { formatDayPlanDate } from './date';

export const planBadgeContent = (plans: IDailyPlan[], taskId: ITeamTask['id']): string | null => {
export const planBadgeContent = (
plans: IDailyPlan[],
taskId: ITeamTask['id'],
tab?: 'default' | 'unassign' | 'dailyplan'
): string | null => {
// Search a plan that contains a given task
const plan = plans.find((plan) => plan.tasks?.some((task) => task.id === taskId));

Expand All @@ -12,8 +16,8 @@ export const planBadgeContent = (plans: IDailyPlan[], taskId: ITeamTask['id']):
(pl) => pl.id !== plan.id && pl.tasks?.some((tsk) => tsk.id === taskId)
);

// If the task exists in other plans, the its planned many days
if (otherPlansWithTask.length > 0) {
// If the task exists in other plans, then its planned many days
if (otherPlansWithTask.length > 0 || tab === 'unassign') {
return 'Planned';
} else {
return `${formatDayPlanDate(plan.date, 'DD MMM YYYY')}`;
Expand Down
52 changes: 46 additions & 6 deletions apps/web/app/hooks/features/useDailyPlan.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use client';

import { useRecoilState } from 'recoil';
import { useCallback, useEffect } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useCallback, useEffect, useState } from 'react';
import { useQuery } from '../useQuery';
import {
activeTeamState,
dailyPlanFetchingState,
dailyPlanListState,
employeePlansListState,
myDailyPlanListState,
profileDailyPlanListState,
taskPlans,
userState
taskPlans
} from '@app/stores';
import {
addTaskToPlanAPI,
Expand All @@ -26,9 +26,22 @@ import {
} from '@app/services/client/api';
import { ICreateDailyPlan, IDailyPlanTasksUpdate, IRemoveTaskFromManyPlans, IUpdateDailyPlan } from '@app/interfaces';
import { useFirstLoad } from '../useFirstLoad';
import { useAuthenticateUser } from './useAuthenticateUser';
import { TODAY_PLAN_ALERT_SHOWN_DATE } from '@app/constants';

type TodayPlanNotificationParams = {
canBeSeen: boolean;
alreadySeen: boolean;
};

export function useDailyPlan() {
const [user] = useRecoilState(userState);
const [addTodayPlanTrigger, setAddTodayPlanTrigger] = useState<TodayPlanNotificationParams>({
canBeSeen: false,
alreadySeen: false
});

const { user } = useAuthenticateUser();
const activeTeam = useRecoilValue(activeTeamState);

const { loading, queryCall } = useQuery(getDayPlansByEmployeeAPI);
const { loading: getAllDayPlansLoading, queryCall: getAllQueryCall } = useQuery(getAllDayPlansAPI);
Expand Down Expand Up @@ -281,10 +294,34 @@ export function useDailyPlan() {
profileDailyPlans.items &&
[...profileDailyPlans.items].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());

const currentUser = activeTeam?.members?.find((member) => member.employee.userId === user?.id);

useEffect(() => {
getMyDailyPlans();
}, [getMyDailyPlans]);

useEffect(() => {
const checkAndShowAlert = () => {
if (activeTeam && currentUser) {
const lastAlertDate = localStorage.getItem(TODAY_PLAN_ALERT_SHOWN_DATE);
const today = new Date().toISOString().split('T')[0];
const totalMemberWorked = currentUser?.totalTodayTasks.reduce(
(previousValue, currentValue) => previousValue + currentValue.duration,
0
);
const showTodayPlanTrigger = todayPlan && todayPlan.length > 0 && totalMemberWorked > 0;
if (lastAlertDate === today) {
setAddTodayPlanTrigger({ canBeSeen: !!showTodayPlanTrigger, alreadySeen: true });
}
}
};

checkAndShowAlert();
const intervalId = setInterval(checkAndShowAlert, 24 * 60 * 60 * 1000); // One day check and display

return () => clearInterval(intervalId);
}, [activeTeam, currentUser, todayPlan]);

return {
dailyPlan,
setDailyPlan,
Expand Down Expand Up @@ -335,6 +372,9 @@ export function useDailyPlan() {
pastPlans,
outstandingPlans,
todayPlan,
sortedPlans
sortedPlans,

addTodayPlanTrigger,
setAddTodayPlanTrigger
};
}
7 changes: 2 additions & 5 deletions apps/web/app/hooks/features/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,8 @@ export function useTimer() {

// 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 isPlanVerified =
!!hasPlan?.workTimePlanned && !!hasPlan?.tasks?.every((task) => task.estimate && task.estimate > 0);

const canRunTimer =
user?.isEmailVerified &&
Expand Down
1 change: 1 addition & 0 deletions apps/web/components/shared/timer/timer-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const Timer = () => {
open={isOpen}
plan={hasPlan}
startTimer={startTimer}
hasPlan={!!hasPlan}
/>
</>
);
Expand Down
8 changes: 7 additions & 1 deletion apps/web/lib/components/kanban-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,13 @@ export default function Item(props: ItemProps) {
<div className="w-full justify-between h-fit">
<div className="w-full flex justify-between">
<span className="!w-64">
<TaskAllStatusTypes className="justify-start" task={item} showStatus={false} />
<TaskAllStatusTypes
className="justify-start"
task={item}
showStatus={false}
tab="default"
dayPlanTab="All Tasks"
/>
</span>
<span>
<MenuKanbanCard member={currentMember} item={props.item} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function UserTeamActiveTaskInfo({ member }: { member: OT_Member }
memberInfo={memberInfo}
className="flex-1 lg:px-4 px-2 overflow-y-hidden"
publicTeam={false}
tab="default"
/>
) : (
<div className="w-full text-start px-6">--</div>
Expand Down
169 changes: 114 additions & 55 deletions apps/web/lib/features/daily-plan/plans-work-time-and-estimate.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { PiWarningCircleFill } from 'react-icons/pi';
import { IDailyPlan, ITeamTask } from '@app/interfaces';
import { DailyPlanStatusEnum, 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';
import { useAuthenticateUser, useDailyPlan, useTeamTasks } from '@app/hooks';
import { ReloadIcon } from '@radix-ui/react-icons';

export function AddWorkTimeAndEstimatesToPlan({
open,
closeModal,
plan,
startTimer
startTimer,
hasPlan
// employee
}: {
open: boolean;
closeModal: () => void;
startTimer: () => void;
hasPlan: boolean;
plan?: IDailyPlan;

// employee?: OT_Member;
}) {
const t = useTranslations();
Expand All @@ -30,15 +34,18 @@ export function AddWorkTimeAndEstimatesToPlan({

const { updateDailyPlan } = useDailyPlan();

const { tasks: $tasks } = useTeamTasks();
const { tasks: $tasks, activeTeam } = 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;
const requirePlan = activeTeam?.requirePlanToTrack;
if (requirePlan) {
if (workTimePlanned === 0 || typeof workTimePlanned !== 'number') return;
if (tasks.some((task) => task.estimate === 0)) return;
}

updateDailyPlan({ workTimePlanned }, plan?.id ?? '');
startTimer();
Expand All @@ -47,59 +54,63 @@ export function AddWorkTimeAndEstimatesToPlan({

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>
{hasPlan ? (
<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} />
{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 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={handleSubmit}
>
{t('common.SKIP_ADD_LATER')}
</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 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>
</Card>
) : (
<CreateTodayPlanPopup closeModal={closeModal} />
)}
</Modal>
);
}
Expand Down Expand Up @@ -146,3 +157,51 @@ export function UnEstimatedTask({ task }: { task: ITeamTask }) {
</Card>
);
}

export function CreateTodayPlanPopup({ closeModal }: { closeModal: () => void }) {
const { createDailyPlan, createDailyPlanLoading } = useDailyPlan();
const { user } = useAuthenticateUser();
const { activeTeam } = useTeamTasks();
const member = activeTeam?.members.find((member) => member.employee.userId === user?.id);
const onSubmit = useCallback(
async (values: any) => {
const toDay = new Date();
createDailyPlan({
workTimePlanned: parseInt(values.workTimePlanned) || 0,
date: toDay,
status: DailyPlanStatusEnum.OPEN,
tenantId: user?.tenantId ?? '',
employeeId: member?.employeeId,
organizationId: member?.organizationId
}).then(() => {
closeModal();
});
},
[closeModal, createDailyPlan, member?.employeeId, member?.organizationId, user?.tenantId]
);

return (
<Card className="w-full" shadow="custom">
<div className="flex flex-col items-center justify-between">
<div className="mb-7">
<Text.Heading as="h3" className="mb-3 text-center">
CREATE A PLAN FOR TODAY
</Text.Heading>

<Text className="text-sm text-center text-gray-500">You are creating a new plan for today</Text>
</div>
<div className="flex flex-col w-full gap-3">
<Button
variant="default"
className="p-7 font-normal rounded-xl text-md"
disabled={createDailyPlanLoading}
onClick={onSubmit}
>
{createDailyPlanLoading && <ReloadIcon className="animate-spin mr-2 h-4 w-4" />}
OK
</Button>
</div>
</div>
</Card>
);
}
Loading

0 comments on commit d24cbff

Please sign in to comment.