diff --git a/apps/web/app/[locale]/calendar/component.tsx b/apps/web/app/[locale]/calendar/component.tsx index f000fa1d8..f85f5fef1 100644 --- a/apps/web/app/[locale]/calendar/component.tsx +++ b/apps/web/app/[locale]/calendar/component.tsx @@ -11,7 +11,7 @@ import { SelectItem, SelectTrigger, SelectValue, -} from "@components/ui/select" +} from "@components/ui/select"; import { cn } from "lib/utils"; import { CalendarDays } from "lucide-react"; import React from "react"; diff --git a/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx b/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx index ed38c2d23..f03c3f2e4 100644 --- a/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx +++ b/apps/web/app/[locale]/timesheet/components/FilterWithStatus.tsx @@ -20,11 +20,15 @@ export function FilterWithStatus({ ]; return ( -
+
{buttonData.map(({ label, count, icon }, index) => (
@@ -131,8 +137,7 @@ function TimeSheetPage() { export default withAuthentication(TimeSheetPage, { displayName: 'TimeSheet' }); - -const FooterTimeSheet = ({ fullWidth }: { fullWidth: boolean }) => { +const FooterTimeSheet: React.FC = ({ fullWidth }) => { return (
@@ -144,6 +149,7 @@ const FooterTimeSheet = ({ fullWidth }: { fullWidth: boolean }) => {
) } + const ViewToggleButton: React.FC = ({ mode, active, diff --git a/apps/web/app/interfaces/IUserData.ts b/apps/web/app/interfaces/IUserData.ts index 46cfb12fe..c3a581876 100644 --- a/apps/web/app/interfaces/IUserData.ts +++ b/apps/web/app/interfaces/IUserData.ts @@ -98,6 +98,17 @@ export interface ILanguageItemList { data: any; } +export interface IRoleList { + id: string; + createdAt: string; + updatedAt: string; + tenantId: string; + name: string; + isSystem: boolean; + items: []; + data: any; +} + //export timezone list interface export interface ITimezoneItemList { length: number | undefined; diff --git a/apps/web/components/ui/dropdown-menu.tsx b/apps/web/components/ui/dropdown-menu.tsx index eeba02e07..3c35f65e9 100644 --- a/apps/web/components/ui/dropdown-menu.tsx +++ b/apps/web/components/ui/dropdown-menu.tsx @@ -1,179 +1,197 @@ -import * as React from 'react'; -import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { Check, ChevronRight, Circle } from 'lucide-react'; +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" +import { cn } from "@/lib/utils" -import { cn } from 'lib/utils'; +const DropdownMenu = DropdownMenuPrimitive.Root -const DropdownMenu = DropdownMenuPrimitive.Root; +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +const DropdownMenuGroup = DropdownMenuPrimitive.Group -const DropdownMenuGroup = DropdownMenuPrimitive.Group; +const DropdownMenuPortal = DropdownMenuPrimitive.Portal -const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +const DropdownMenuSub = DropdownMenuPrimitive.Sub -const DropdownMenuSub = DropdownMenuPrimitive.Sub; - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } >(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)); -DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)); -DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)); -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } >(({ className, inset, ...props }, ref) => ( - -)); -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)); -DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)); -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } >(({ className, inset, ...props }, ref) => ( - -)); -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)); -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; - -const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { - return ; -}; -DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup -}; + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx index 2fa91a4ce..9d1663b4a 100644 --- a/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx +++ b/apps/web/lib/features/integrations/calendar/confirm-change-status.tsx @@ -93,11 +93,11 @@ export function StatusBadge({ selectedStatus, filterNumber }: { selectedStatus?: const getColorClass = () => { switch (selected) { case "Rejected": - return `text-red-500 ${filterNumber ? "border-gray-200 dark:border-gray-700" : " border-red-500"} `; + return `text-black ${filterNumber ? "border-gray-200 dark:border-gray-700" : "!bg-red-500"} `; case "Approved": - return `text-green-500 ${filterNumber ? "border-gray-200 dark:border-gray-700" : "border-green-500"}`; + return `text-black ${filterNumber ? "border-gray-200 dark:border-gray-700" : "!bg-green-500"}`; case "Pending": - return `text-orange-500 ${filterNumber ? "border-gray-200 dark:border-gray-700" : "border-orange-500"} `; + return `text-black ${filterNumber ? "border-gray-200 dark:border-gray-700" : "!bg-orange-500"} `; default: return `text-gray-500 dark:text-gray-200 border-gray-200 dark:border-gray-700 !py-0 font-normal`; } diff --git a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx index e6c9789a3..9169b391f 100644 --- a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx +++ b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx @@ -1,5 +1,5 @@ "use client" -import React, { useState, useRef } from 'react'; +import { useState, useRef } from 'react'; import { LuCalendarPlus } from "react-icons/lu"; import { IoIosArrowDown, IoIosArrowForward } from "react-icons/io"; import { IoTimeSharp } from "react-icons/io5"; diff --git a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx index 7f479781d..15b17e743 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -48,10 +48,10 @@ import { MdKeyboardDoubleArrowRight, MdKeyboardArrowLeft, MdKeyboardArrowRight -} from "react-icons/md"; +} from "react-icons/md" import { ConfirmStatusChange, StatusBadge, TimeSheet, dataSourceTimeSheet, statusOptions } from "." import { useModal } from "@app/hooks" -import { Checkbox } from "@components/ui/checkbox"; +import { Checkbox } from "@components/ui/checkbox" @@ -214,8 +214,8 @@ export function DataTableTimeSheet() {
- - +
+ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { diff --git a/apps/web/lib/features/roles/role-item.tsx b/apps/web/lib/features/roles/role-item.tsx new file mode 100644 index 000000000..e2dd7bc1e --- /dev/null +++ b/apps/web/lib/features/roles/role-item.tsx @@ -0,0 +1,48 @@ +import { IRoleList } from '@app/interfaces'; +import { clsxm } from '@app/utils'; +import { DropdownItem } from 'lib/components'; +import React, { HTMLAttributes } from 'react'; + +export type RoleItem = DropdownItem; + +export function mapRoleItems(roles: IRoleList[]) { + // eslint-disable-next-line react/display-name + const RoleLabel = React.memo(({ selected, name }: { selected: boolean | undefined; name: string }) => ( +
+ +
+ )); + const items = roles.map((role: IRoleList) => { + const name = role.name || 'Unnamed Role'; + return { + key: role.id, + Label: ({ selected }) => , + selectedLabel: , + data: role + }; + }); + + return items; +} + +export function RoleItem({ + title, + className +}: { + title?: string; + count?: number; + className?: HTMLAttributes['className']; + color?: string; + disabled?: boolean; +}) { + return ( +
+ + {title} + +
+ ); +} diff --git a/apps/web/lib/features/team/team-outstanding-notifications.tsx b/apps/web/lib/features/team/team-outstanding-notifications.tsx index d86c24d98..d3f7f14f6 100644 --- a/apps/web/lib/features/team/team-outstanding-notifications.tsx +++ b/apps/web/lib/features/team/team-outstanding-notifications.tsx @@ -1,11 +1,11 @@ 'use client'; -import { useAuthenticateUser, useDailyPlan } from '@app/hooks'; +import { useAuthenticateUser, useDailyPlan, useOrganizationTeams } from '@app/hooks'; import { IDailyPlan, IEmployee, IUser } from '@app/interfaces'; import { Cross2Icon, EyeOpenIcon } from '@radix-ui/react-icons'; import { Tooltip } from 'lib/components'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; -import { memo, useEffect, useState } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; import { estimatedTotalTime } from '../task/daily-plan'; import { HAS_VISITED_OUTSTANDING_TASKS } from '@app/constants'; import moment from 'moment'; @@ -16,14 +16,13 @@ interface IEmployeeWithOutstanding { } export function TeamOutstandingNotifications() { - const { dailyPlan, getEmployeeDayPlans, outstandingPlans } = useDailyPlan(); - + const { dailyPlan, getAllDayPlans, outstandingPlans } = useDailyPlan(); + const { activeTeam } = useOrganizationTeams(); const { isTeamManager, user } = useAuthenticateUser(); useEffect(() => { - // getAllDayPlans(); - getEmployeeDayPlans(user?.employee.id || ''); - }, [getEmployeeDayPlans, user?.employee.id]); + getAllDayPlans(); + }, [activeTeam, getAllDayPlans]); return (
@@ -53,9 +52,10 @@ const UserOutstandingNotification = memo(function UserOutstandingNotification({ const [visible, setVisible] = useState(false); - const outStandingTasksCount = estimatedTotalTime( - outstandingPlans.map((plan) => plan.tasks?.map((task) => task)) - ).totalTasks; + const outStandingTasksCount = useMemo( + () => estimatedTotalTime(outstandingPlans.map((plan) => plan.tasks?.map((task) => task))).totalTasks, + [outstandingPlans] + ); const lastVisited = window?.localStorage.getItem(HAS_VISITED_OUTSTANDING_TASKS); @@ -131,22 +131,23 @@ const ManagerOutstandingUsersNotification = memo(function ManagerOutstandingUser const [visible, setVisible] = useState(false); - const employeeWithOutstanding = outstandingTasks - .filter((plan) => plan.employeeId !== user?.employee.id) - .filter((plan) => !plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0])) - - .filter((plan) => { - const planDate = new Date(plan.date); - const today = new Date(); - today.setHours(23, 59, 59, 0); - return planDate.getTime() <= today.getTime(); - }) - .map((plan) => ({ - ...plan, - tasks: plan.tasks?.filter((task) => task.status !== 'completed') - })) - .filter((plan) => plan.tasks?.length && plan.tasks.length > 0) - .map((plan) => ({ employeeId: plan.employeeId, employee: plan.employee })); + const employeeWithOutstanding = useMemo( + () => + outstandingTasks + + .filter((plan) => { + if (plan.employeeId === user?.employee.id) return false; + if (!plan.date) return false; + + const isTodayOrBefore = moment(plan.date).isSameOrBefore(moment().endOf('day')); + if (!isTodayOrBefore) return false; + + const hasIncompleteTasks = plan.tasks?.some((task) => task.status !== 'completed'); + return hasIncompleteTasks; + }) + .map((plan) => ({ employeeId: plan.employeeId, employee: plan.employee })), + [outstandingTasks, user?.employee.id] + ); const uniqueEmployees: IEmployeeWithOutstanding[] = employeeWithOutstanding.reduce( (acc: IEmployeeWithOutstanding[], current) => { diff --git a/apps/web/lib/settings/edit-role-dropdown.tsx b/apps/web/lib/settings/edit-role-dropdown.tsx new file mode 100644 index 000000000..80bf48988 --- /dev/null +++ b/apps/web/lib/settings/edit-role-dropdown.tsx @@ -0,0 +1,45 @@ +import { Dropdown } from 'lib/components'; +import { clsxm } from '@app/utils'; +import {useRoles} from "@app/hooks/features/useRoles"; +import { IRole, IRoleList, OT_Member } from "@app/interfaces"; +import {useCallback, useEffect, useMemo, useState} from "react"; +import {mapRoleItems, RoleItem} from "../features/roles/role-item"; + + +export const EditUserRoleDropdown = ({ member, handleRoleChange }: { member: OT_Member,handleRoleChange : (newRole: IRole) => void}) => { + const {roles} = useRoles() + + const items = useMemo(() => mapRoleItems(roles.filter(role => ['MANAGER', 'EMPLOYEE'].includes(role.name)) as IRoleList[]), [roles]); + + const [roleItem, setRoleItem] = useState(null); + + useEffect(() => { + setRoleItem(items.find(t => t.key === member?.roleId) || null); + }, [items, member?.roleId]); + + const onChange = useCallback( + (item: RoleItem) => { + if (item.data) { + setRoleItem(item); + } + if (item.data?.id && item.data.data) { + handleRoleChange(item.data?.data) + } + }, + [handleRoleChange] + ); + + return ( + <> + + + ); +}; diff --git a/apps/web/lib/settings/member-table.tsx b/apps/web/lib/settings/member-table.tsx index 7d123fefd..83caa5430 100644 --- a/apps/web/lib/settings/member-table.tsx +++ b/apps/web/lib/settings/member-table.tsx @@ -1,268 +1,312 @@ import { CHARACTER_LIMIT_TO_SHOW } from '@app/constants'; import { imgTitle } from '@app/helpers'; -import { useSettings } from '@app/hooks'; +import { useOrganizationTeams, useSettings, useSyncRef } from '@app/hooks'; import { usePagination } from '@app/hooks/features/usePagination'; -import { OT_Member, OT_Role } from '@app/interfaces'; +import { IRole, OT_Member, OT_Role } from '@app/interfaces'; import { activeTeamIdState, organizationTeamsState } from '@app/stores'; import { clsxm } from '@app/utils'; import { Avatar, InputField, Text, Tooltip } from 'lib/components'; import { Paginate } from 'lib/components/pagination'; import cloneDeep from 'lodash/cloneDeep'; import moment from 'moment'; -import { ChangeEvent, KeyboardEvent, useCallback, useState } from 'react'; +import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react'; import { useTranslations } from 'next-intl'; import { useAtom, useAtomValue } from 'jotai'; import stc from 'string-to-color'; import { MemberTableStatus } from './member-table-status'; import { TableActionPopover } from './table-action-popover'; +import { EditUserRoleDropdown } from './edit-role-dropdown'; export const MemberTable = ({ members }: { members: OT_Member[] }) => { - const t = useTranslations(); - const { - total, - onPageChange, - itemsPerPage, - itemOffset, - endOffset, - setItemsPerPage, - currentItems - } = usePagination(members); - const { updateAvatar } = useSettings(); + const t = useTranslations(); + const { total, onPageChange, itemsPerPage, itemOffset, endOffset, setItemsPerPage, currentItems } = + usePagination(members); + const { activeTeam, updateOrganizationTeam,} = useOrganizationTeams(); + const { updateAvatar } = useSettings(); - const activeTeamId = useAtomValue(activeTeamIdState); - const [organizationTeams, setOrganizationTeams] = useAtom( - organizationTeamsState - ); - const [editMember, setEditMember] = useState(null); - const handleEdit = (member: OT_Member) => { - setEditMember(member); - }; - const handelNameChange = useCallback( - (event: ChangeEvent) => { - const name = event.target.value || ''; - if (name === editMember?.employee.fullName) { - return; - } + const activeTeamRef = useSyncRef(activeTeam); - const names = name.split(' '); - const tempMember: OT_Member | null = cloneDeep(editMember); - if (tempMember && tempMember.employee.user) { - tempMember.employee.fullName = name; - tempMember.employee.user.firstName = names[0] || ''; - tempMember.employee.user.lastName = names[1] || ''; - setEditMember(tempMember); - } - }, - [editMember] - ); - const handleEditMemberSave = useCallback(() => { - if (editMember) { - updateAvatar({ - firstName: editMember?.employee?.user?.firstName || '', - lastName: editMember?.employee?.user?.lastName || '', - id: editMember?.employee?.userId - }).then(() => { - const teamIndex = organizationTeams.findIndex( - (team) => team.id === activeTeamId - ); - const tempOrganizationTeams = cloneDeep(organizationTeams); - const memberIndex = tempOrganizationTeams[teamIndex].members.findIndex( - (member) => member.id === editMember.id - ); + const activeTeamId = useAtomValue(activeTeamIdState); + const [organizationTeams, setOrganizationTeams] = useAtom(organizationTeamsState); + const editMemberRef = useRef(null); - tempOrganizationTeams[teamIndex].members[memberIndex] = editMember; - setOrganizationTeams(tempOrganizationTeams); - setEditMember(null); - }); - } - }, [ - editMember, - organizationTeams, - activeTeamId, - setOrganizationTeams, - updateAvatar - ]); - const handleOnKeyUp = (event: KeyboardEvent) => { - if (event.key === 'Enter') { - handleEditMemberSave(); - } - }; + const updateTeamMember = useCallback((updatedMember: OT_Member) => { + const teamIndex = organizationTeams.findIndex((team) => team.id === activeTeamId); + if (teamIndex === -1) return; - return ( -
-
-
- - - - - - - - - - - - {currentItems.map((member, index) => ( - - - - - - - - - ))} - -
- {t('common.NAME')} - - {t('common.POSITION')} - - {t('common.ROLES')} - - {t('common.JOIN_OR_LEFT')} - - {t('common.STATUS')} -
- {member.employee.user?.imageId ? ( - - ) : member.employee.user?.name ? ( -
- {imgTitle(member.employee.user?.name)} -
- ) : ( - '' - )} -
- {editMember && editMember.id === member.id ? ( - - ) : ( - - CHARACTER_LIMIT_TO_SHOW - } - > -
{ - handleEdit(member); - }} - > - {member.employee.fullName} -
-
- )} + const tempTeams = cloneDeep(organizationTeams); + const memberIndex = tempTeams[teamIndex].members.findIndex( + (member) => member.id === updatedMember.id); - - CHARACTER_LIMIT_TO_SHOW - } - > - - {member.employee.user?.email || ''} - - -
-
- {/* TODO Position */}- - - - {getRoleString(member.role)} - - - {/* 12 Feb 2020 12:00 pm */} - {moment(member.employee.createdAt).format( - 'DD MMM YYYY hh:mm a' - )} - - {/* TODO dynamic */} - - - -
-
+ if (memberIndex === -1) return; - -
- ); + tempTeams[teamIndex].members[memberIndex] = updatedMember; + setOrganizationTeams(tempTeams); + }, [activeTeamId, organizationTeams, setOrganizationTeams]); + + const handleEdit = useCallback((member: OT_Member) => { + editMemberRef.current = member; + }, []); + + + const handleManagerRoleUpdate = useCallback((employeeId: string, isPromotingToManager: boolean) => { + if (!activeTeamRef.current) return; + + // Get current managers + const currentManagers: string[] = activeTeamRef.current?.members + .filter((member: OT_Member) => member.role?.name === 'MANAGER') + .map((manager: OT_Member) => manager.employee.id) || []; + + if (isPromotingToManager) { + // Add new manager + const updatedMemberIds = [...new Set([ + ...(activeTeamRef.current?.members || []).map((member: OT_Member) => member.employee.id), + employeeId + ])]; + + return updateOrganizationTeam(activeTeamRef.current, { + ...activeTeamRef.current, + memberIds: updatedMemberIds + }); + } else { + // Remove manager + const updatedMemberIds = [...new Set([...currentManagers, employeeId])]; + const updatedManagerIds = currentManagers.filter(id => id !== employeeId); + + return updateOrganizationTeam(activeTeamRef.current, { + ...activeTeamRef.current, + memberIds: updatedMemberIds, + managerIds: updatedManagerIds, + }); + } + + }, [updateOrganizationTeam, activeTeamRef]); + + + const handleRoleChange = useCallback((newRole: IRole) => { + if (!editMemberRef.current || !activeTeamRef.current) return; + + console.log({ newRole }) + + const { employeeId, role } = editMemberRef.current; + + const isPromotingToManager = role?.name !== 'MANAGER' && newRole?.name === 'MANAGER'; + handleManagerRoleUpdate(employeeId, isPromotingToManager); + + // Update Organization Team + // const updatedMember = { ...editMemberRef.current, roleId: !isPromotingToManager ? '' : }; + // updateTeamMember(updatedMember); + editMemberRef.current = null; + + }, [activeTeamRef, handleManagerRoleUpdate]); + + const handelNameChange = useCallback( + (event: ChangeEvent) => { + const name = event.target.value || ''; + if (name === editMemberRef.current?.employee.fullName) { + return; + } + + const names = name.split(' '); + const tempMember: OT_Member | null = cloneDeep(editMemberRef.current); + + if (tempMember?.employee?.user) { + tempMember.employee.fullName = name; + tempMember.employee.user.firstName = names[0] || ''; + tempMember.employee.user.lastName = names[1] || ''; + editMemberRef.current = tempMember; + } + }, + [] + ); + const handleEditMemberSave = useCallback(() => { + const member = editMemberRef.current; + if (member) { + updateAvatar({ + firstName: member.employee?.user?.firstName || '', + lastName: member.employee?.user?.lastName || '', + id: member.employee?.userId || '' + }).then(() => { + if (member) { + updateTeamMember(member); + } + }); + } + }, [updateAvatar, updateTeamMember]); + + const handleOnKeyUp = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + handleEditMemberSave(); + } + editMemberRef.current = null; + }; + + return ( +
+
+ + + + + + + + + + + + + {currentItems.map((member, index) => ( + + + + + + + + + ))} + +
+ {t('common.NAME')} + + {t('common.POSITION')} + + {t('common.ROLES')} + + {t('common.JOIN_OR_LEFT')} + + {t('common.STATUS')} +
+ {member.employee.user?.imageId ? ( + + ) : member.employee.user?.name ? ( +
+ {imgTitle(member.employee.user?.name)} +
+ ) : ( + '' + )} +
+ {editMemberRef.current && editMemberRef.current.id === member.id ? ( + + ) : ( + CHARACTER_LIMIT_TO_SHOW + } + > +
{ + handleEdit(member); + }} + > + {member.employee.fullName} +
+
+ )} + + + CHARACTER_LIMIT_TO_SHOW + } + > + + {member.employee.user?.email || ''} + + +
+
+ {/* TODO Position */}- + handleEdit(member)} + > + {editMemberRef.current && editMemberRef.current.id === member.id ? ( + + ) : ( + {getRoleString(member.role)} + )} + + {/* 12 Feb 2020 12:00 pm */} + {moment(member.employee.createdAt).format('DD MMM YYYY hh:mm a')} + + {/* TODO dynamic */} + + + +
+
+ + +
+ ); }; const getRoleString = (role: OT_Role | undefined) => { - return role?.name || 'MEMBER'; + return role?.name || 'MEMBER'; }; diff --git a/apps/web/lib/settings/table-action-popover.tsx b/apps/web/lib/settings/table-action-popover.tsx index 3cdab0e82..c74d1ed9d 100644 --- a/apps/web/lib/settings/table-action-popover.tsx +++ b/apps/web/lib/settings/table-action-popover.tsx @@ -105,7 +105,8 @@ export const TableActionPopover = ({ member, handleEdit, status }: Props) => { {isLoading ? : null - } + } + } diff --git a/yarn.lock b/yarn.lock index e8f555736..564b95bee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13476,9 +13476,9 @@ electronmon@^2.0.2: watchboy "^0.4.3" elliptic@^6.5.3, elliptic@^6.5.4: - version "6.5.7" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" - integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + version "6.6.0" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210" + integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -24712,7 +24712,16 @@ string-to-color@^2.2.2: lodash.words "^4.2.0" rgb-hex "^3.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -24822,7 +24831,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -26872,7 +26888,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -26890,6 +26906,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"