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

3104-3105-3106-3107 feature/vertical sidebar, projects, favorites and role based navigation in sidebar #3199

Merged
Merged
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
33 changes: 16 additions & 17 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"caseSensitive": false,
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"words": [
" X",
" X ",
NdekoCode marked this conversation as resolved.
Show resolved Hide resolved
"accepte",
"Accordian",
"adipiscing",
Expand Down Expand Up @@ -38,6 +40,7 @@
"asel",
"Authentificate",
"authjs",
"autorun",
"barcodes",
"billrate",
"binutils",
Expand Down Expand Up @@ -74,6 +77,7 @@
"Codacy",
"codecov",
"Codementor",
"collapsable",
"Combox",
"combx",
"commitlint",
Expand Down Expand Up @@ -114,6 +118,7 @@
"Efate",
"eiusmod",
"electronmon",
"Elipssis",
NdekoCode marked this conversation as resolved.
Show resolved Hide resolved
"elit",
"ellipsize",
"embla",
Expand All @@ -125,9 +130,12 @@
"Environtment",
"errr",
"everco",
"evereq",
"everteamsdesktop",
"everteamswebserver",
"excalidraw",
"exclamationcircleo",
"expanded",
"exposdk",
"extramenu",
"Fakaofo",
Expand All @@ -146,6 +154,7 @@
"Galery",
"gauzystage",
"gcloud",
"gitops",
"Gitter",
"GlobalSkeleton",
"gradlew",
Expand Down Expand Up @@ -226,6 +235,7 @@
"Loadtasks",
"localstorage",
"locatio",
"locutus",
"loglevel",
"longpress",
"Lorem",
Expand Down Expand Up @@ -253,6 +263,8 @@
"Northflank",
"Notif",
"nsis",
"nums",
"offcanvas",
"Opena",
"opentelemetry",
"Ordereds",
Expand Down Expand Up @@ -349,6 +361,7 @@
"tblr",
"teamsupercell",
"teamtask",
"TEAMTASKS",
"tempor",
"testid",
"timegrid",
Expand All @@ -364,6 +377,7 @@
"TRANSFERT",
"Transpiles",
"tsbuildinfo",
"twing",
"typeof",
"uicolors",
"uidotdev",
Expand Down Expand Up @@ -394,29 +408,14 @@
"worksace",
"Worspace",
"X",
"X ",
"xcodebuild",
"xcodeproj",
"xcworkspace",
"Xlarge",
"xlcard",
"xlight",
"yellowbox",
"twing",
"gitops",
"autorun",
"locutus",
"X",
"offcanvas",
"nums",
"Elipssis",
"evereq",
"everteamswebserver",
"expanded",
"expanded",
" X",
" X ",
"X ",
"collapsable"
"yellowbox"
],
"useGitignore": true,
"ignorePaths": [
Expand Down
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
"source.fixAll": "explicit",
"source.organizeImports": "never",
"source.sortMembers": "never",
"organizeImports": "never"
"organizeImports": "never",
"source.removeUnusedImports": "always"
},
"vsicons.presets.angular": true,
"deepscan.enable": true,
"cSpell.words": ["Timepicker"],
"cSpell.words": [
"Timepicker"
],
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
Expand Down
44 changes: 44 additions & 0 deletions apps/web/app/hooks/features/useActiveTeam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import { useOrganizationTeams, useTimer } from '@app/hooks';
import { useToast } from '@components/ui/use-toast';
import { useCallback } from 'react';
import { TeamItem } from '@/lib/features/team/team-item';
import { useTranslations } from 'next-intl';

export const useActiveTeam = () => {
const { activeTeam, setActiveTeam } = useOrganizationTeams();
const { timerStatus, stopTimer } = useTimer();
const t = useTranslations();
const { toast } = useToast();
const onChangeActiveTeam = useCallback(
(item: TeamItem) => {
if (item.data) {
/**
* If timer started in Teams and user switches the Team, stop the timer
*/
if (
timerStatus &&
timerStatus?.running &&
timerStatus.lastLog &&
timerStatus.lastLog.organizationTeamId &&
timerStatus.lastLog.source === 'TEAMS' &&
activeTeam &&
activeTeam?.id &&
timerStatus.lastLog.organizationTeamId === activeTeam?.id
) {
NdekoCode marked this conversation as resolved.
Show resolved Hide resolved
toast({
variant: 'default',
title: t('timer.TEAM_SWITCH.STOPPED_TIMER_TOAST_TITLE'),
description: t('timer.TEAM_SWITCH.STOPPED_TIMER_TOAST_DESCRIPTION')
});
stopTimer();
}

setActiveTeam(item.data);
}
},
[setActiveTeam, timerStatus, stopTimer, activeTeam, toast, t]
);
return { activeTeam, setActiveTeam, onChangeActiveTeam };
};
47 changes: 47 additions & 0 deletions apps/web/app/hooks/features/useFavoritesTask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useTeamTasks } from '@/app/hooks/features/useTeamTasks';
import { ITeamTask } from '@/app/interfaces/ITask';
import { useAtom } from 'jotai';
import { favoriteTasksStorageAtom } from '@/app/stores/team-tasks';
import { useCallback } from 'react';
/**
* A React hook that manages a list of favorite tasks for a team.
*
* The `useFavoritesTask` hook returns an object with the following properties:
*
* - `tasks`: The list of all tasks for the team, obtained from the `useTeamTasks` hook.
* - `favoriteTasks`: The list of favorite tasks.
* - `toggleFavorite`: A function that toggles the favorite status of a given task.
* - `isFavorite`: A function that checks if a given task is a favorite.
* - `addFavorite`: A function that adds a task to the list of favorites.
*/

export const useFavoritesTask = () => {
const { tasks } = useTeamTasks();
const [favoriteTasks, setFavoriteTasks] = useAtom(favoriteTasksStorageAtom);

const toggleFavorite = useCallback((task: ITeamTask) => {
if (!task?.id) {
console.warn('Invalid task provided to toggleFavorite');
return;
}
setFavoriteTasks((prev) =>
prev.some((t) => t.id === task.id) ? prev.filter((t) => t.id !== task.id) : [...prev, task]
);
}, []);
Comment on lines +22 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider optimizing array operations in toggleFavorite.

While the current implementation is correct, we can optimize the array operations by avoiding multiple array traversals.

 const toggleFavorite = useCallback((task: ITeamTask) => {
   if (!task?.id) {
     console.warn('Invalid task provided to toggleFavorite');
     return;
   }
   setFavoriteTasks((prev) => {
-    prev.some((t) => t.id === task.id) ? prev.filter((t) => t.id !== task.id) : [...prev, task]
+    const index = prev.findIndex((t) => t.id === task.id);
+    return index >= 0 
+      ? [...prev.slice(0, index), ...prev.slice(index + 1)]
+      : [...prev, task];
   });
 }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleFavorite = useCallback((task: ITeamTask) => {
if (!task?.id) {
console.warn('Invalid task provided to toggleFavorite');
return;
}
setFavoriteTasks((prev) =>
prev.some((t) => t.id === task.id) ? prev.filter((t) => t.id !== task.id) : [...prev, task]
);
}, []);
const toggleFavorite = useCallback((task: ITeamTask) => {
if (!task?.id) {
console.warn('Invalid task provided to toggleFavorite');
return;
}
setFavoriteTasks((prev) => {
const index = prev.findIndex((t) => t.id === task.id);
return index >= 0
? [...prev.slice(0, index), ...prev.slice(index + 1)]
: [...prev, task];
});
}, []);


const isFavorite = useCallback((task: ITeamTask) => favoriteTasks.some((t) => t.id === task.id), [favoriteTasks]);

const addFavorite = useCallback((task: ITeamTask) => {
if (!isFavorite(task)) {
setFavoriteTasks((prev) => [...prev, task]);
}
}, []);
Comment on lines +34 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing dependency to useCallback in addFavorite.

The addFavorite function uses isFavorite but doesn't include it in the dependency array of useCallback. This could lead to stale closure issues.

 const addFavorite = useCallback((task: ITeamTask) => {
   if (!isFavorite(task)) {
     setFavoriteTasks((prev) => [...prev, task]);
   }
-}, []);
+}, [isFavorite]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const addFavorite = useCallback((task: ITeamTask) => {
if (!isFavorite(task)) {
setFavoriteTasks((prev) => [...prev, task]);
}
}, []);
const addFavorite = useCallback((task: ITeamTask) => {
if (!isFavorite(task)) {
setFavoriteTasks((prev) => [...prev, task]);
}
}, [isFavorite]);


return {
tasks,
favoriteTasks,
toggleFavorite,
isFavorite,
addFavorite
};
};
107 changes: 59 additions & 48 deletions apps/web/app/hooks/features/useOrganizationTeamManagers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,67 @@ import { useAuthenticateUser } from './useAuthenticateUser';
import { useOrganizationTeams } from './useOrganizationTeams';
import { filterValue } from '@app/stores/all-teams';
import { useMemo } from 'react';

/**
* Provides a hook that returns the teams managed by the authenticated user, along with the ability to filter those teams based on the timer status of their members.
*
* @returns An object with two properties:
* - `userManagedTeams`: An array of teams that the authenticated user manages.
* - `filteredTeams`: An array of teams that the authenticated user manages, filtered based on the `filterValue` atom.
*/
export function useOrganizationAndTeamManagers() {
const { user } = useAuthenticateUser();
const { teams } = useOrganizationTeams();
const { value: filtered } = useAtomValue(filterValue);
const { user } = useAuthenticateUser();
const { teams } = useOrganizationTeams();
const { value: filtered } = useAtomValue(filterValue);

const userManagedTeams = useMemo(() => {
return teams.filter((team) =>
team.members.some(
(member) =>
member.employee?.user?.id === user?.id &&
member.role?.name === 'MANAGER'
)
);
}, [teams, user]);
/**
* Filters the teams managed by the authenticated user.
*
* @returns An array of teams that the authenticated user manages, where the authenticated user has the 'MANAGER' role for at least one member of the team.
*/
const userManagedTeams = useMemo(() => {
return teams.filter((team) =>
team.members.some((member) => member.employee?.user?.id === user?.id && member.role?.name === 'MANAGER')
);
}, [teams, user]);
NdekoCode marked this conversation as resolved.
Show resolved Hide resolved

const filteredTeams = useMemo(() => {
return filtered === 'all'
? userManagedTeams
: filtered === 'pause'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter(
(member) => member.timerStatus === 'pause'
)
}))
: filtered === 'running'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter(
(member) => member.timerStatus === 'running'
)
}))
: filtered === 'suspended'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter(
(member) => member.timerStatus === 'suspended'
)
}))
: filtered === 'invited'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter((member) => member.employee.acceptDate)
}))
: userManagedTeams;
}, [filtered, userManagedTeams]);
/**
* Filters the teams managed by the authenticated user based on the `filterValue` atom.
*
* @returns An array of teams that the authenticated user manages, filtered based on the `filterValue` atom. The filtering options include:
* - 'all': Returns all teams managed by the authenticated user.
* - 'pause': Returns teams where at least one member has a timer status of 'pause'.
* - 'running': Returns teams where at least one member has a timer status of 'running'.
* - 'suspended': Returns teams where at least one member has a timer status of 'suspended'.
* - 'invited': Returns teams where at least one member has an `acceptDate` value.
*/
const filteredTeams = useMemo(() => {
return filtered === 'all'
? userManagedTeams
: filtered === 'pause'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter((member) => member.timerStatus === 'pause')
}))
: filtered === 'running'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter((member) => member.timerStatus === 'running')
}))
: filtered === 'suspended'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter((member) => member.timerStatus === 'suspended')
}))
: filtered === 'invited'
? userManagedTeams.map((team) => ({
...team,
members: team.members.filter((member) => member.employee.acceptDate)
}))
: userManagedTeams;
}, [filtered, userManagedTeams]);

return {
userManagedTeams,
filteredTeams
};
return {
userManagedTeams,
filteredTeams
};
}
Loading
Loading