Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release #2750

Merged
merged 16 commits into from
Jul 17, 2024
Merged

Release #2750

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading