diff --git a/worklenz-frontend/src/components/AssigneeSelector.tsx b/worklenz-frontend/src/components/AssigneeSelector.tsx index 3f786959..28126441 100644 --- a/worklenz-frontend/src/components/AssigneeSelector.tsx +++ b/worklenz-frontend/src/components/AssigneeSelector.tsx @@ -13,6 +13,8 @@ import { sortTeamMembers } from '@/utils/sort-team-members'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { setIsFromAssigner, toggleProjectMemberDrawer } from '@/features/projects/singleProject/members/projectMembersSlice'; import { updateEnhancedKanbanTaskAssignees } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; +import { useAuthStatus } from '@/hooks/useAuthStatus'; interface AssigneeSelectorProps { task: IProjectTask; @@ -21,9 +23,9 @@ interface AssigneeSelectorProps { kanbanMode?: boolean; } -const AssigneeSelector: React.FC = ({ - task, - groupId = null, +const AssigneeSelector: React.FC = ({ + task, + groupId = null, isDarkMode = false, kanbanMode = false }) => { @@ -42,6 +44,8 @@ const AssigneeSelector: React.FC = ({ const currentSession = useAuthService().getCurrentSession(); const { socket } = useSocket(); const dispatch = useAppDispatch(); + const { isAdmin } = useAuthStatus(); + const isProjectManager = useIsProjectManager(); const filteredMembers = useMemo(() => { return teamMembers?.data?.filter(member => @@ -64,7 +68,7 @@ const AssigneeSelector: React.FC = ({ useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node) && - buttonRef.current && !buttonRef.current.contains(event.target as Node)) { + buttonRef.current && !buttonRef.current.contains(event.target as Node)) { setIsOpen(false); } }; @@ -74,10 +78,10 @@ const AssigneeSelector: React.FC = ({ // Check if the button is still visible in the viewport if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); - const isVisible = rect.top >= 0 && rect.left >= 0 && - rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth; - + const isVisible = rect.top >= 0 && rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth; + if (isVisible) { updateDropdownPosition(); } else { @@ -98,7 +102,7 @@ const AssigneeSelector: React.FC = ({ document.addEventListener('mousedown', handleClickOutside); window.addEventListener('scroll', handleScroll, true); window.addEventListener('resize', handleResize); - + return () => { document.removeEventListener('mousedown', handleClickOutside); window.removeEventListener('scroll', handleScroll, true); @@ -113,10 +117,10 @@ const AssigneeSelector: React.FC = ({ const handleDropdownToggle = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - + if (!isOpen) { updateDropdownPosition(); - + // Prepare team members data when opening const assignees = task?.assignees?.map(assignee => assignee.team_member_id); const membersData = (members?.data || []).map(member => ({ @@ -125,7 +129,7 @@ const AssigneeSelector: React.FC = ({ })); const sortedMembers = sortTeamMembers(membersData); setTeamMembers({ data: sortedMembers }); - + setIsOpen(true); // Focus search input after opening setTimeout(() => { @@ -160,8 +164,8 @@ const AssigneeSelector: React.FC = ({ // Update local team members state for dropdown UI setTeamMembers(prev => ({ ...prev, - data: (prev.data || []).map(member => - member.id === memberId + data: (prev.data || []).map(member => + member.id === memberId ? { ...member, selected: checked } : member ) @@ -198,8 +202,8 @@ const AssigneeSelector: React.FC = ({ const checkMemberSelected = (memberId: string) => { if (!memberId) return false; // Use optimistic assignees if available, otherwise fall back to task assignees - const assignees = optimisticAssignees.length > 0 - ? optimisticAssignees + const assignees = optimisticAssignees.length > 0 + ? optimisticAssignees : task?.assignees?.map(assignee => assignee.team_member_id) || []; return assignees.includes(memberId); }; @@ -218,12 +222,12 @@ const AssigneeSelector: React.FC = ({ className={` w-5 h-5 rounded-full border border-dashed flex items-center justify-center transition-colors duration-200 - ${isOpen - ? isDarkMode - ? 'border-blue-500 bg-blue-900/20 text-blue-400' + ${isOpen + ? isDarkMode + ? 'border-blue-500 bg-blue-900/20 text-blue-400' : 'border-blue-500 bg-blue-50 text-blue-600' - : isDarkMode - ? 'border-gray-600 hover:border-gray-500 hover:bg-gray-800 text-gray-400' + : isDarkMode + ? 'border-gray-600 hover:border-gray-500 hover:bg-gray-800 text-gray-400' : 'border-gray-300 hover:border-gray-400 hover:bg-gray-100 text-gray-600' } `} @@ -237,8 +241,8 @@ const AssigneeSelector: React.FC = ({ onClick={e => e.stopPropagation()} className={` fixed z-[99999] w-72 rounded-md shadow-lg border - ${isDarkMode - ? 'bg-gray-800 border-gray-600' + ${isDarkMode + ? 'bg-gray-800 border-gray-600' : 'bg-white border-gray-200' } `} @@ -274,10 +278,10 @@ const AssigneeSelector: React.FC = ({ key={member.id} className={` flex items-center gap-2 p-2 cursor-pointer transition-colors - ${member.pending_invitation - ? 'opacity-50 cursor-not-allowed' - : isDarkMode - ? 'hover:bg-gray-700' + ${member.pending_invitation + ? 'opacity-50 cursor-not-allowed' + : isDarkMode + ? 'hover:bg-gray-700' : 'hover:bg-gray-50' } `} @@ -302,23 +306,21 @@ const AssigneeSelector: React.FC = ({ /> {pendingChanges.has(member.id || '') && ( -
-
+
+
)}
- + - +
{member.name} @@ -340,22 +342,26 @@ const AssigneeSelector: React.FC = ({
{/* Footer */} -
- -
+ + {(isAdmin || isProjectManager) && ( +
+ +
+ )} +
, document.body )}