Skip to content

Commit

Permalink
Merge pull request #2749 from ever-co/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
evereq authored Jul 17, 2024
2 parents 917c048 + 4abbf26 commit 7caa404
Show file tree
Hide file tree
Showing 55 changed files with 1,702 additions and 547 deletions.
2 changes: 1 addition & 1 deletion apps/extensions/components/popup/Timer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Timer: React.FC<Props> = ({ port }) => {
? new Date(msg.payload.timer * 1000).toISOString().substr(11, 8)
: '00:00:00';
const totalWorkedTime =
msg.payload.totalWorked > 0
msg.payload!.totalWorked > 0
? new Date(msg.payload.totalWorked * 1000).toISOString().substr(11, 8)
: '00:00:00';
setTimeString(taskWorkedTime);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/[locale]/auth/passcode/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ function WorkSpaceScreen({ form, className }: { form: TAuthenticationPasscode }
if (form.workspaces.length === 1 && currentTeams?.length === 1) {
setSelectedTeam(currentTeams[0].team_id);
} else {
const lastSelectedTeam = window.localStorage.getItem(LAST_WORSPACE_AND_TEAM) || currentTeams[0].team_id;
const lastSelectedTeam = window.localStorage.getItem(LAST_WORSPACE_AND_TEAM) || currentTeams[0]?.team_id;
const lastSelectedWorkspace =
form.workspaces.findIndex((workspace) =>
workspace.current_teams.find((team) => team.team_id === lastSelectedTeam)
Expand Down
6 changes: 1 addition & 5 deletions apps/web/app/[locale]/profile/[memberId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TaskFilter, Timer, TimerStatus, UserProfileTask, getTimerStatusValue, u
import { MainHeader, MainLayout } from 'lib/layout';
import Link from 'next/link';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslations } from 'next-intl';
import { useTranslations } from 'next-intl'
import stc from 'string-to-color';

import { useRecoilValue, useSetRecoilState } from 'recoil';
Expand Down Expand Up @@ -126,7 +126,6 @@ const Profile = React.memo(function ProfilePage({ params }: { params: { memberId
{/* User Profile Detail */}
<div className="flex flex-col items-center justify-between py-5 md:py-10 md:flex-row">
<UserProfileDetail member={profile.member} />

{profileIsAuthUser && isTrackingEnabled && (
<Timer
className={clsxm(
Expand All @@ -142,9 +141,6 @@ const Profile = React.memo(function ProfilePage({ params }: { params: { memberId
</MainHeader>
</ResizablePanel>
<ResizableHandle withHandle />

{/* Divider */}
{/* <div className="h-0.5 bg-[#FFFFFF14]"></div> */}
<ResizablePanel defaultSize={53} maxSize={95} className="!overflow-y-scroll custom-scrollbar">
{hook.tab == 'worked' && canSeeActivity && (
<Container fullWidth={fullWidth} className="py-8">
Expand Down
23 changes: 23 additions & 0 deletions apps/web/app/api/daily-plan/[id]/remove/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { INextParams, IRemoveTaskFromManyPlans } from "@app/interfaces";
import { authenticatedGuard } from "@app/services/server/guards/authenticated-guard-app";
import { deleteDailyPlansManyRequest } from "@app/services/server/requests";
import { NextResponse } from "next/server";

export async function PUT(req: Request, { params }: INextParams) {
const res = new NextResponse();
const { id } = params;
if (!id) {
return;
}
const { $res, user, access_token } = await authenticatedGuard(req, res);

if (!user) return $res('Unauthorized');

const body = (await req.json()) as unknown as IRemoveTaskFromManyPlans;
const response = await deleteDailyPlansManyRequest({
data: body,
taskId: id,
bearer_token: access_token,
});
return $res(response.data);
}
1 change: 0 additions & 1 deletion apps/web/app/api/daily-plan/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export async function PUT(req: Request, { params }: INextParams) {
if (!id) {
return;
}

const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res);

if (!user) return $res('Unauthorized');
Expand Down
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
13 changes: 13 additions & 0 deletions apps/web/app/helpers/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,16 @@ export const formatIntegerToHour = (number: number) => {

return formattedHour;
};


export const isTestDateRange = (itemDate: Date, from?: Date, to?: Date) => {
if (from && to) {
return itemDate >= from && itemDate <= to;
} else if (from) {
return itemDate >= from;
} else if (to) {
return itemDate <= to;
} else {
return true; // or false, depending on your default logic
}
}
48 changes: 48 additions & 0 deletions apps/web/app/helpers/drag-and-drop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { IDailyPlan, ITeamTask } from "@app/interfaces";
import { DropResult } from "react-beautiful-dnd";

export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setPlans: React.Dispatch<React.SetStateAction<IDailyPlan[]>>) => {
const { source, destination } = results;

if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return;

const newPlans = [...plans];

const planSourceIndex = newPlans.findIndex(plan => plan.id === source.droppableId);
const planDestinationIndex = newPlans.findIndex(plan => plan.id === destination.droppableId);

const newSourceTasks = [...newPlans[planSourceIndex].tasks!];
const newDestinationTasks = source.droppableId !== destination.droppableId
? [...newPlans[planDestinationIndex].tasks!]
: newSourceTasks;

const [deletedTask] = newSourceTasks.splice(source.index, 1);
newDestinationTasks.splice(destination.index, 0, deletedTask);

newPlans[planSourceIndex] = {
...newPlans[planSourceIndex],
tasks: newSourceTasks,
};
newPlans[planDestinationIndex] = {
...newPlans[planDestinationIndex],
tasks: newDestinationTasks,
};
setPlans(newPlans);
};


export const handleDragAndDropDailyOutstandingAll = (
results: DropResult,
tasks: ITeamTask[],
setTasks: React.Dispatch<React.SetStateAction<ITeamTask[]>>
) => {
const { source, destination } = results;

if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return;

const newTasks = [...tasks];
const [movedTask] = newTasks.splice(source.index, 1);
newTasks.splice(destination.index, 0, movedTask);

setTasks(newTasks);
};
1 change: 1 addition & 0 deletions apps/web/app/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './validations';
export * from './colors';
export * from './strings';
export * from './plan-day-badge';
export * from './drag-and-drop'
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
97 changes: 90 additions & 7 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 @@ -20,14 +20,28 @@ import {
getDayPlansByEmployeeAPI,
getMyDailyPlansAPI,
getPlansByTaskAPI,
removeManyTaskFromPlansAPI,
removeTaskFromPlanAPI,
updateDailyPlanAPI
} from '@app/services/client/api';
import { ICreateDailyPlan, IDailyPlanTasksUpdate, IUpdateDailyPlan } from '@app/interfaces';
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 All @@ -38,6 +52,8 @@ export function useDailyPlan() {
const { loading: addTaskToPlanLoading, queryCall: addTaskToPlanQueryCall } = useQuery(addTaskToPlanAPI);
const { loading: removeTaskFromPlanLoading, queryCall: removeTAskFromPlanQueryCall } =
useQuery(removeTaskFromPlanAPI);
const { loading: removeManyTaskFromPlanLoading, queryCall: removeManyTaskPlanQueryCall } = useQuery(removeManyTaskFromPlansAPI);

const { loading: deleteDailyPlanLoading, queryCall: deleteDailyPlanQueryCall } = useQuery(deleteDailyPlanAPI);

const [dailyPlan, setDailyPlan] = useRecoilState(dailyPlanListState);
Expand Down Expand Up @@ -148,6 +164,7 @@ export function useDailyPlan() {
]
);


const removeTaskFromPlan = useCallback(
async (data: IDailyPlanTasksUpdate, planId: string) => {
const res = await removeTAskFromPlanQueryCall(data, planId);
Expand All @@ -168,6 +185,42 @@ export function useDailyPlan() {
]
);

const removeManyTaskPlans = useCallback(
async (data: IRemoveTaskFromManyPlans, taskId: string) => {
const res = await removeManyTaskPlanQueryCall({ taskId, data });
const updatedProfileDailyPlans = 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
.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);

setProfileDailyPlans({
total: profileDailyPlans.total,
items: updatedProfileDailyPlans
});
setEmployeePlans(updatedEmployeePlans);
getMyDailyPlans();
return res;

},
[
removeManyTaskPlanQueryCall,
employeePlans,
getMyDailyPlans,
profileDailyPlans,
setEmployeePlans,
setProfileDailyPlans
]
)

const deleteDailyPlan = useCallback(
async (planId: string) => {
const res = await deleteDailyPlanQueryCall(planId);
Expand Down Expand Up @@ -241,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 @@ -285,13 +362,19 @@ export function useDailyPlan() {
removeTaskFromPlan,
removeTaskFromPlanLoading,

removeManyTaskPlans,
removeManyTaskFromPlanLoading,

deleteDailyPlan,
deleteDailyPlanLoading,

futurePlans,
pastPlans,
outstandingPlans,
todayPlan,
sortedPlans
sortedPlans,

addTodayPlanTrigger,
setAddTodayPlanTrigger
};
}
15 changes: 10 additions & 5 deletions apps/web/app/hooks/features/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ export function useTimer() {
plan.tasks?.length > 0
);

const tomorrow = moment().add(1, 'days');
const hasPlanForTomorrow = myDailyPlans.items.find(
(plan) => moment(plan.date).format('YYYY-MM-DD') === tomorrow.format('YYYY-MM-DD')
);

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

Expand All @@ -205,11 +210,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 Expand Up @@ -423,6 +425,7 @@ export function useTimer() {
startTimer,
stopTimer,
hasPlan,
hasPlanForTomorrow,
canRunTimer,
canTrack,
isPlanVerified,
Expand Down Expand Up @@ -463,6 +466,7 @@ export function useTimerView() {
startTimer,
stopTimer,
hasPlan,
hasPlanForTomorrow,
canRunTimer,
canTrack,
isPlanVerified,
Expand Down Expand Up @@ -494,6 +498,7 @@ export function useTimerView() {
timerStatus,
activeTeamTask,
hasPlan,
hasPlanForTomorrow,
disabled: !canRunTimer,
canTrack,
isPlanVerified,
Expand Down
Loading

0 comments on commit 7caa404

Please sign in to comment.