From 4acea4f87642e2b9afe57aad0752aaaa6ff4c6e9 Mon Sep 17 00:00:00 2001 From: poduck Date: Fri, 28 Nov 2025 05:49:40 -0500 Subject: [PATCH] feat: Add ticket permission UI and fix assignee dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assignee Dropdown: - Fix useUsers hook to fetch from /api/staff/ endpoint - Add useStaffForAssignment hook for formatted dropdown data - Update TicketModal to use new hook for assignee selection Staff Permissions UI: - Add "Can access support tickets" permission to invite modal for both managers and staff - Add permission to edit modal for both managers and staff - Managers default to having ticket access enabled - Staff default to having ticket access disabled (must be explicitly granted) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/components/TicketModal.tsx | 4 +- frontend/src/hooks/useUsers.ts | 63 +++++++++++++++++-- frontend/src/pages/Staff.tsx | 80 +++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/TicketModal.tsx b/frontend/src/components/TicketModal.tsx index b9075e3..6958618 100644 --- a/frontend/src/components/TicketModal.tsx +++ b/frontend/src/components/TicketModal.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { X, User, Send, MessageSquare, Clock, AlertCircle } from 'lucide-react'; import { Ticket, TicketComment, TicketStatus, TicketPriority, TicketCategory, TicketType } from '../types'; import { useCreateTicket, useUpdateTicket, useTicketComments, useCreateTicketComment } from '../hooks/useTickets'; -import { useUsers } from '../hooks/useUsers'; // Assuming a useUsers hook exists to fetch users for assignee dropdown +import { useStaffForAssignment } from '../hooks/useUsers'; import { useQueryClient } from '@tanstack/react-query'; interface TicketModalProps { @@ -34,7 +34,7 @@ const TicketModal: React.FC = ({ ticket, onClose, defaultTicke const [isInternalComment, setIsInternalComment] = useState(false); // Fetch users for assignee dropdown - const { data: users = [] } = useUsers(); // Assuming useUsers fetches all relevant users (staff/platform admins) + const { data: users = [] } = useStaffForAssignment(); // Fetch comments for the ticket if in detail/edit mode const { data: comments, isLoading: isLoadingComments } = useTicketComments(ticket?.id); diff --git a/frontend/src/hooks/useUsers.ts b/frontend/src/hooks/useUsers.ts index 3d1c98c..f09db92 100644 --- a/frontend/src/hooks/useUsers.ts +++ b/frontend/src/hooks/useUsers.ts @@ -1,17 +1,68 @@ -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import apiClient from '../api/client'; import { User } from '../types'; +interface StaffUser { + id: number | string; + email: string; + first_name: string; + last_name: string; + full_name: string; + role: string; + role_display: string; + is_active: boolean; + permissions: Record; + has_resource: boolean; + resource_id?: string; + resource_name?: string; +} + /** - * Hook to fetch all users (staff, owners, customers) for the current business. - * This can be filtered/refined later based on specific needs (e.g., only staff). + * Hook to fetch all staff members (owners, managers, staff) for the current business. + * Used for assignee dropdowns in tickets and other features. */ export const useUsers = () => { - return useQuery({ - queryKey: ['businessUsers'], + return useQuery({ + queryKey: ['staff'], queryFn: async () => { - const response = await apiClient.get('/api/business/users/'); + const response = await apiClient.get('/api/staff/'); return response.data; }, }); }; + +/** + * Hook to fetch staff members for assignee selection. + * Returns users formatted for dropdown use. + */ +export const useStaffForAssignment = () => { + return useQuery<{ id: string; name: string; email: string; role: string }[]>({ + queryKey: ['staffForAssignment'], + queryFn: async () => { + const response = await apiClient.get('/api/staff/'); + return response.data.map((user: StaffUser) => ({ + id: String(user.id), + name: user.full_name || `${user.first_name} ${user.last_name}`.trim() || user.email, + email: user.email, + role: user.role_display || user.role, + })); + }, + }); +}; + +/** + * Hook to update a staff member's permissions + */ +export const useUpdateStaffPermissions = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ userId, permissions }: { userId: string | number; permissions: Record }) => { + const response = await apiClient.patch(`/api/staff/${userId}/`, { permissions }); + return response.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['staff'] }); + }, + }); +}; diff --git a/frontend/src/pages/Staff.tsx b/frontend/src/pages/Staff.tsx index dd0c396..1c5a325 100644 --- a/frontend/src/pages/Staff.tsx +++ b/frontend/src/pages/Staff.tsx @@ -661,6 +661,26 @@ const Staff: React.FC = ({ onMasquerade, effectiveUser }) => {

+ + {/* Can Access Tickets */} + )} @@ -710,6 +730,26 @@ const Staff: React.FC = ({ onMasquerade, effectiveUser }) => {

+ + {/* Can Access Tickets */} + )} @@ -959,6 +999,26 @@ const Staff: React.FC = ({ onMasquerade, effectiveUser }) => {

+ + {/* Can Access Tickets */} + )} @@ -1008,6 +1068,26 @@ const Staff: React.FC = ({ onMasquerade, effectiveUser }) => {

+ + {/* Can Access Tickets */} + )}