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 */}
+
)}