diff --git a/apps/web/app/[locale]/profile/[memberId]/page.tsx b/apps/web/app/[locale]/profile/[memberId]/page.tsx index b24b4c81e..2152a1cf4 100644 --- a/apps/web/app/[locale]/profile/[memberId]/page.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/page.tsx @@ -23,6 +23,7 @@ import { AppsTab } from 'lib/features/activity/apps'; import { VisitedSitesTab } from 'lib/features/activity/visited-sites'; import { activityTypeState } from '@app/stores/activity-type'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@components/ui/resizable'; +import { TableActionPopover } from 'lib/settings/table-action-popover'; // import { ActivityCalendar } from 'lib/features/activity/calendar'; export type FilterTab = 'Tasks' | 'Screenshots' | 'Apps' | 'Visited Sites'; @@ -197,17 +198,19 @@ const Profile = React.memo(function ProfilePage({ params }: { params: { memberId function UserProfileDetail({ member }: { member?: OT_Member }) { const user = useMemo(() => member?.employee.user, [member?.employee.user]); + const userName = `${user?.firstName || ''} ${user?.lastName || ''}`; const imgUrl = user?.image?.thumbUrl || user?.image?.fullUrl || user?.imageUrl; const imageUrl = useMemo(() => imgUrl, [imgUrl]); const size = 100; const { timerStatus } = useTimer(); + // const isManager = activeTeamManagers.find((member) => member.employee.user?.id === member?.employee.user?.id); const timerStatusValue: ITimerStatusEnum = useMemo(() => { return getTimerStatusValue(timerStatus, member, false); }, [timerStatus, member]); - return (
+
+ {imageUrl && isValidUrl(imageUrl) ? ( + + ) : ( <> + {imgTitle(userName).charAt(0)} + )}
- -
- - {user?.firstName} {user?.lastName} - +
+
+ + {user?.firstName} {user?.lastName} + +
+ +
+
{user?.email}
diff --git a/apps/web/app/api/employee/[id]/route.ts b/apps/web/app/api/employee/[id]/route.ts new file mode 100644 index 000000000..6bead1db0 --- /dev/null +++ b/apps/web/app/api/employee/[id]/route.ts @@ -0,0 +1,24 @@ +import { IUpdateEmployee } from "@app/interfaces"; +import { authenticatedGuard } from "@app/services/server/guards/authenticated-guard-app"; +import { updateEmployees } from "@app/services/server/requests"; +import { NextResponse } from "next/server"; + +export async function PUT(req: Request, { params }: { params: { id: string } }) { + 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 IUpdateEmployee; + + const response = await updateEmployees( + { + bearer_token: access_token, + body, + id + } + ); + return $res(response.data) +} diff --git a/apps/web/app/api/employee/working/route.ts b/apps/web/app/api/employee/working/route.ts deleted file mode 100644 index 061206e17..000000000 --- a/apps/web/app/api/employee/working/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { authenticatedGuard } from '@app/services/server/guards/authenticated-guard-app'; -import { getOrganizationEmployees } from '@app/services/server/requests'; -import { NextResponse } from 'next/server'; - -export async function GET(req: Request) { - const res = new NextResponse(); - const { $res, user, access_token, tenantId, organizationId } = await authenticatedGuard(req, res); - - if (!user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - - const { data } = await getOrganizationEmployees(access_token, tenantId, organizationId); - - return $res(data); -} diff --git a/apps/web/app/hooks/features/useEmployee.ts b/apps/web/app/hooks/features/useEmployee.ts index cf0cae1aa..916877137 100644 --- a/apps/web/app/hooks/features/useEmployee.ts +++ b/apps/web/app/hooks/features/useEmployee.ts @@ -1,10 +1,11 @@ -import { getWorkingEmployeesAPI } from '@app/services/client/api'; +import { getWorkingEmployeesAPI, updateEmployeeAPI } from '@app/services/client/api'; import { workingEmployeesEmailState, workingEmployeesState } from '@app/stores/employee'; import { useCallback, useEffect } from 'react'; import { useRecoilState } from 'recoil'; import { useQuery } from '../useQuery'; import { useAuthenticateUser } from './useAuthenticateUser'; +import { IUpdateEmployee } from '@app/interfaces'; export const useEmployee = () => { const { user } = useAuthenticateUser(); @@ -39,3 +40,19 @@ export const useEmployee = () => { workingEmployeesEmail }; }; + + +export const useEmployeeUpdate = () => { + const { queryCall: employeeUpdateQuery, loading: isLoading } = useQuery(updateEmployeeAPI); + + const updateEmployee = useCallback(({ id, data + }: { id: string, data: IUpdateEmployee }) => { + employeeUpdateQuery({ id, data }) + .then((res) => res.data) + .catch((error) => { + console.log(error); + }); + }, []); + + return { updateEmployee, isLoading } +} diff --git a/apps/web/app/interfaces/IEmployee.ts b/apps/web/app/interfaces/IEmployee.ts index 3844c9ff9..bed02d1c0 100644 --- a/apps/web/app/interfaces/IEmployee.ts +++ b/apps/web/app/interfaces/IEmployee.ts @@ -57,6 +57,7 @@ export interface IEmployee { } export type ICreateEmployee = Pick; +export type IUpdateEmployee = Pick export interface IRole { id?: string; diff --git a/apps/web/app/services/client/api/employee.ts b/apps/web/app/services/client/api/employee.ts index ca9f0529b..0d5f28362 100644 --- a/apps/web/app/services/client/api/employee.ts +++ b/apps/web/app/services/client/api/employee.ts @@ -1,6 +1,6 @@ import { GAUZY_API_BASE_SERVER_URL } from '@app/constants'; -import { IWorkingEmployee, PaginationResponse } from '@app/interfaces'; -import { get } from '../axios'; +import { IUpdateEmployee, IWorkingEmployee, PaginationResponse } from '@app/interfaces'; +import { get, put } from '../axios'; import qs from 'qs'; export async function getWorkingEmployeesAPI(tenantId: string, organizationId: string) { @@ -15,3 +15,8 @@ export async function getWorkingEmployeesAPI(tenantId: string, organizationId: s return get>(endpoint, { tenantId }); } + + +export function updateEmployeeAPI({ id, data }: { id: string, data: IUpdateEmployee }) { + return put(`/employee/${id}`, data); +} diff --git a/apps/web/app/services/server/requests/employee.ts b/apps/web/app/services/server/requests/employee.ts index 30e65f9f9..a9cbbf33c 100644 --- a/apps/web/app/services/server/requests/employee.ts +++ b/apps/web/app/services/server/requests/employee.ts @@ -1,5 +1,5 @@ import { PaginationResponse } from '@app/interfaces'; -import { ICreateEmployee, IEmployee, IWorkingEmployee } from '@app/interfaces/IEmployee'; +import { ICreateEmployee, IEmployee, IUpdateEmployee, IWorkingEmployee } from '@app/interfaces/IEmployee'; import { serverFetch } from '../fetch'; import qs from 'qs'; @@ -29,3 +29,13 @@ export function getOrganizationEmployees(bearer_token: string, tenantId: string, tenantId }); } + +export function updateEmployees({ bearer_token, id, body }: { bearer_token: string, id: string, body: IUpdateEmployee }) { + return serverFetch({ + path: `/employee/${id}`, + method: 'PUT', + bearer_token, + body + + }) +} diff --git a/apps/web/app/stores/employee.ts b/apps/web/app/stores/employee.ts index 8c9ee1f08..ecd867c97 100644 --- a/apps/web/app/stores/employee.ts +++ b/apps/web/app/stores/employee.ts @@ -1,4 +1,4 @@ -import { IWorkingEmployee } from '@app/interfaces'; +import { IUpdateEmployee, IWorkingEmployee } from '@app/interfaces'; import { atom } from 'recoil'; export const workingEmployeesState = atom({ @@ -10,3 +10,9 @@ export const workingEmployeesEmailState = atom({ key: 'workingEmployeesEmailState', default: [] }); + + +export const employeeUpdateState = atom({ + key: 'employeeUpdateState', + default: null!, +}) diff --git a/apps/web/lib/components/avatar.tsx b/apps/web/lib/components/avatar.tsx index 9f7c2fb0e..5992aac9d 100644 --- a/apps/web/lib/components/avatar.tsx +++ b/apps/web/lib/components/avatar.tsx @@ -61,8 +61,8 @@ export function Avatar({ height: size, ...(backgroundColor ? { - backgroundColor - } + backgroundColor + } : {}) }} > diff --git a/apps/web/lib/settings/member-table.tsx b/apps/web/lib/settings/member-table.tsx index 039cfd147..16ca75b60 100644 --- a/apps/web/lib/settings/member-table.tsx +++ b/apps/web/lib/settings/member-table.tsx @@ -28,7 +28,6 @@ export const MemberTable = ({ members }: { members: OT_Member[] }) => { const handleEdit = (member: OT_Member) => { setEditMember(member); }; - const handelNameChange = useCallback( (event: ChangeEvent) => { const name = event.target.value || ''; @@ -210,7 +209,7 @@ export const MemberTable = ({ members }: { members: OT_Member[] }) => { - + ))} diff --git a/apps/web/lib/settings/table-action-popover.tsx b/apps/web/lib/settings/table-action-popover.tsx index 12fcd3118..3cdab0e82 100644 --- a/apps/web/lib/settings/table-action-popover.tsx +++ b/apps/web/lib/settings/table-action-popover.tsx @@ -1,34 +1,41 @@ -import { useAuthenticateUser, useModal, useTMCardTaskEdit, useTeamMemberCard } from '@app/hooks'; +import { useAuthenticateUser, useModal, useOrganizationTeams, useTMCardTaskEdit, useTeamMemberCard } from '@app/hooks'; import { useRoles } from '@app/hooks/features/useRoles'; -import { OT_Member } from '@app/interfaces'; +import { OT_Member, RoleNameEnum } from '@app/interfaces'; import { Popover, Transition } from '@headlessui/react'; import { useDropdownAction } from 'lib/features/team/user-team-card/user-team-card-menu'; import { Fragment, useEffect } from 'react'; import { useTranslations } from 'next-intl'; import { ConfirmationModal } from './confirmation-modal'; import { ThreeCircleOutlineHorizontalIcon } from 'assets/svg'; +import { useEmployeeUpdate } from '@app/hooks/features/useEmployee'; type Props = { member: OT_Member; - handleEdit: (member: OT_Member) => void; + handleEdit?: (member: OT_Member) => void; + status?: 'settings' | 'profile' }; - -export const TableActionPopover = ({ member, handleEdit }: Props) => { +/** + * + * + */ +export const TableActionPopover = ({ member, handleEdit, status }: Props) => { // const [isOpen, setIsOpen] = useState(false); - const t = useTranslations(); const { user } = useAuthenticateUser(); + const { activeTeamManagers } = useOrganizationTeams(); + const memberInfo = useTeamMemberCard(member); const taskEdition = useTMCardTaskEdit(memberInfo.memberTask); const { onRemoveMember } = useDropdownAction({ edition: taskEdition, memberInfo }); + const { isLoading, updateEmployee } = useEmployeeUpdate(); const { isOpen, openModal, closeModal } = useModal(); const isCurrentUser = user?.employee.id === memberInfo.member?.employeeId; - + const isManager = activeTeamManagers.findIndex((member) => member.employee.user?.id === user?.id); // const handleClick = () => { // setIsOpen(!isOpen); // }; @@ -45,19 +52,19 @@ export const TableActionPopover = ({ member, handleEdit }: Props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - + {/* TODO Dynamic */} {/* Edit */} -
{ - handleEdit(member); + handleEdit && handleEdit(member); }} > {t('common.EDIT')} -
+
} {/* TODO Dynamic */} {/* Change Role */} @@ -67,6 +74,41 @@ export const TableActionPopover = ({ member, handleEdit }: Props) => { */} + {isManager !== -1 && member.role?.name !== RoleNameEnum.MANAGER && +
+ Time tracking +
{ + updateEmployee({ + data: { + isTrackingEnabled: !member.employee.isTrackingEnabled, + id: member.employee.id, + organizationId: member.employee.organizationId, + isActive: member.employee.isActive, + tenantId: member.employee.tenantId + }, + id: member.employee.id + }); + }} + style={ + member.employee.isTrackingEnabled + ? { background: 'linear-gradient(to right, #ea31244d, #ea312479)' } + : { background: '#2ead805b' } + } + > +
+ + {!isLoading && renderTrackingIcon(member.employee.isTrackingEnabled)} + + {isLoading ? + : null + }
+
+
+ } {/* TODO Dynamic */} {/* Need to integrate with API */} @@ -80,21 +122,22 @@ export const TableActionPopover = ({ member, handleEdit }: Props) => { */} {/* Delete */} -
undefined : () => openModal()} > {t('common.DELETE')} -
+ } - - - + {(status === 'settings' || (status === 'profile' && isManager !== -1 && member.role?.name !== RoleNameEnum.MANAGER)) && ( + + + + )} { ); }; + + +export const renderTrackingIcon = (isTrackingEnabled: boolean) => { + return isTrackingEnabled ? ( + + + + ) : ( + + + + ); +}