Merge pull request #142 from ghotso/develop-fix-incident_assigned_user
fix(incident): unify assigned user handling with fallback and UI update 🎯
This commit is contained in:
+6
-5
@@ -25,19 +25,20 @@ export const IncidentDetailContent = ({
|
||||
onClose,
|
||||
assignedUser
|
||||
}: IncidentDetailContentProps) => {
|
||||
// Fetch assigned user details if one wasn't provided and there's an assigned_to field
|
||||
// Fetch assigned user details if none was provided; prefer server field
|
||||
const assigneeId = incident?.assigned_users || incident?.assigned_to;
|
||||
const { data: fetchedUser } = useQuery({
|
||||
queryKey: ['user', incident?.assigned_to],
|
||||
queryKey: ['user', assigneeId],
|
||||
queryFn: async () => {
|
||||
if (!incident?.assigned_to) return null;
|
||||
if (!assigneeId) return null;
|
||||
try {
|
||||
return await userService.getUser(incident.assigned_to);
|
||||
return await userService.getUser(assigneeId);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch assigned user:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
enabled: !!incident?.assigned_to && !assignedUser,
|
||||
enabled: !!assigneeId && !assignedUser,
|
||||
staleTime: 300000 // Cache for 5 minutes
|
||||
});
|
||||
|
||||
|
||||
+5
-5
@@ -17,7 +17,7 @@ export const IncidentDetailDialog = ({
|
||||
onOpenChange,
|
||||
incident
|
||||
}: IncidentDetailDialogProps) => {
|
||||
// Fetch user data for assigned_to field
|
||||
// Fetch user data for assigned field (prefer assigned_users, fallback to assigned_to)
|
||||
const { data: users = [] } = useQuery({
|
||||
queryKey: ['users'],
|
||||
queryFn: async () => {
|
||||
@@ -25,12 +25,12 @@ export const IncidentDetailDialog = ({
|
||||
return Array.isArray(usersList) ? usersList : [];
|
||||
},
|
||||
staleTime: 300000, // Cache for 5 minutes
|
||||
enabled: !!incident?.assigned_to && open // Only run query if there's an assigned_to value and dialog is open
|
||||
enabled: !!(incident?.assigned_users || incident?.assigned_to) && open // Only run query if there's an assigned value and dialog is open
|
||||
});
|
||||
|
||||
// Find the assigned user
|
||||
const assignedUser = incident?.assigned_to
|
||||
? users.find(user => user.id === incident.assigned_to)
|
||||
// Find the assigned user (prefer assigned_users, fallback to assigned_to)
|
||||
const assignedUser = (incident?.assigned_users || incident?.assigned_to)
|
||||
? users.find(user => user.id === (incident?.assigned_users || incident?.assigned_to))
|
||||
: null;
|
||||
|
||||
if (!incident) return null;
|
||||
|
||||
+5
-5
@@ -23,19 +23,19 @@ export {
|
||||
|
||||
// Legacy component - keeping this for backward compatibility with other imports
|
||||
export const IncidentDetailSections = ({ incident }: { incident: IncidentItem | null }) => {
|
||||
// Fetch assigned user details if there's an assigned_to field
|
||||
// Fetch assigned user details if there's an assigned field (prefer assigned_users, fallback to assigned_to)
|
||||
const { data: assignedUser } = useQuery({
|
||||
queryKey: ['user', incident?.assigned_to],
|
||||
queryKey: ['user', incident?.assigned_users || incident?.assigned_to],
|
||||
queryFn: async () => {
|
||||
if (!incident?.assigned_to) return null;
|
||||
if (!(incident?.assigned_users || incident?.assigned_to)) return null;
|
||||
try {
|
||||
return await userService.getUser(incident.assigned_to);
|
||||
return await userService.getUser(incident?.assigned_users || incident?.assigned_to);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch assigned user:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
enabled: !!incident?.assigned_to,
|
||||
enabled: !!(incident?.assigned_users || incident?.assigned_to),
|
||||
staleTime: 300000 // Cache for 5 minutes
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ export const AssignmentSection: React.FC<AssignmentSectionProps> = ({ incident,
|
||||
</Avatar>
|
||||
<span>{assignedUser.full_name || assignedUser.username}</span>
|
||||
</div>
|
||||
) : incident.assigned_to ? (
|
||||
<span>{incident.assigned_to}</span>
|
||||
) : (incident.assigned_users || incident.assigned_to) ? (
|
||||
<span>{incident.assigned_users || incident.assigned_to}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground italic">{t('unassigned')}</span>
|
||||
)}
|
||||
|
||||
+2
-2
@@ -50,8 +50,8 @@ export const BasicInfoSection: React.FC<BasicInfoSectionProps> = ({ incident, as
|
||||
</Avatar>
|
||||
<span>{assignedUser.full_name || assignedUser.username}</span>
|
||||
</div>
|
||||
) : incident.assigned_to ? (
|
||||
<span>{incident.assigned_to}</span>
|
||||
) : (incident.assigned_users || incident.assigned_to) ? (
|
||||
<span>{incident.assigned_users || incident.assigned_to}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground italic">{t('unassigned')}</span>
|
||||
)}
|
||||
|
||||
@@ -68,7 +68,7 @@ export const IncidentBasicFields: React.FC = () => {
|
||||
const selectedUser = users.find(user => user.id === form.getValues('assigned_to'));
|
||||
|
||||
// Function to get user initials from name
|
||||
const getUserInitials = (user: any): string => {
|
||||
const getUserInitials = (user: { full_name?: string; username: string }): string => {
|
||||
if (user.full_name) {
|
||||
const nameParts = user.full_name.split(' ');
|
||||
if (nameParts.length > 1) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const useIncidentEditForm = (
|
||||
impact: (incident.impact?.toLowerCase() || 'minor') as any,
|
||||
priority: (incident.priority?.toLowerCase() || 'medium') as any,
|
||||
service_id: incident.service_id || '',
|
||||
assigned_to: incident.assigned_to || '',
|
||||
assigned_to: incident.assigned_users || incident.assigned_to || '',
|
||||
root_cause: incident.root_cause || '',
|
||||
resolution_steps: incident.resolution_steps || '',
|
||||
lessons_learned: incident.lessons_learned || '',
|
||||
@@ -45,11 +45,14 @@ export const useIncidentEditForm = (
|
||||
impact: data.impact,
|
||||
priority: data.priority,
|
||||
service_id: data.service_id,
|
||||
assigned_to: data.assigned_to, // This is the user ID from the form
|
||||
...(data.assigned_to
|
||||
? { assigned_to: data.assigned_to, assigned_users: data.assigned_to }
|
||||
: {}),
|
||||
root_cause: data.root_cause,
|
||||
resolution_steps: data.resolution_steps,
|
||||
lessons_learned: data.lessons_learned,
|
||||
});
|
||||
|
||||
|
||||
toast({
|
||||
title: t('incidentUpdated'),
|
||||
|
||||
@@ -57,6 +57,7 @@ export const useIncidentForm = (onSuccess: () => void, onClose: () => void) => {
|
||||
priority: data.priority,
|
||||
service_id: data.service_id,
|
||||
assigned_to: data.assigned_to,
|
||||
assigned_users: data.assigned_to, // map to server field
|
||||
root_cause: data.root_cause,
|
||||
resolution_steps: data.resolution_steps,
|
||||
lessons_learned: data.lessons_learned,
|
||||
|
||||
@@ -86,7 +86,7 @@ export const IncidentTableRow = memo(({
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<AssignedUserCell userId={localItem.assigned_to} />
|
||||
<AssignedUserCell userId={localItem.assigned_users || localItem.assigned_to} />
|
||||
</TableCell>
|
||||
<TableCell className="text-right" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex justify-end items-center space-x-2">
|
||||
|
||||
@@ -56,7 +56,9 @@ export const createIncident = async (data: CreateIncidentInput): Promise<void> =
|
||||
affected_systems: data.affected_systems,
|
||||
priority: data.priority.toLowerCase(),
|
||||
service_id: data.service_id || '',
|
||||
assigned_to: data.assigned_to || '', // Direct user ID assignment
|
||||
//assigned_to: data.assigned_to || '', // Direct user ID assignment
|
||||
//2025-08-18: // map UI field -> server schema
|
||||
assigned_users: typeof data.assigned_to === 'string' ? data.assigned_to : '',
|
||||
root_cause: data.root_cause || '',
|
||||
resolution_steps: data.resolution_steps || '',
|
||||
lessons_learned: data.lessons_learned || '',
|
||||
@@ -82,28 +84,41 @@ export const createIncident = async (data: CreateIncidentInput): Promise<void> =
|
||||
export const updateIncident = async (id: string, data: Partial<IncidentItem>): Promise<void> => {
|
||||
try {
|
||||
console.log(`Updating incident ${id} with:`, data);
|
||||
|
||||
// Make sure impact and priority are lowercase
|
||||
const payload = {
|
||||
|
||||
const payload: Record<string, any> = {
|
||||
...data,
|
||||
impact: data.impact?.toLowerCase(),
|
||||
priority: data.priority?.toLowerCase(),
|
||||
status: data.status ? formatStatus(data.status) : undefined,
|
||||
impact_status: data.status ? data.status.toLowerCase() : undefined,
|
||||
...(data.status?.toLowerCase() === 'resolved' && !data.resolution_time
|
||||
? { resolution_time: new Date().toISOString() }
|
||||
: {})
|
||||
...(data.impact ? { impact: data.impact.toLowerCase() } : {}),
|
||||
...(data.priority ? { priority: data.priority.toLowerCase() } : {}),
|
||||
...(data.status
|
||||
? {
|
||||
status: formatStatus(data.status),
|
||||
impact_status: data.status.toLowerCase(),
|
||||
}
|
||||
: {}),
|
||||
// set only if assigned_to was provided
|
||||
...(typeof data.assigned_to === 'string'
|
||||
? {
|
||||
assigned_users: data.assigned_to, // server field
|
||||
assigned_to: data.assigned_to, // legacy field for backward compatibility
|
||||
}
|
||||
: {}),
|
||||
// add resolution_time only if status changes to resolved
|
||||
...((data.status?.toLowerCase() === 'resolved' && !data.resolution_time)
|
||||
? { resolution_time: new Date().toISOString() }
|
||||
: {}),
|
||||
};
|
||||
|
||||
|
||||
console.log("Final payload for update:", payload);
|
||||
await pb.collection('incidents').update(id, payload);
|
||||
|
||||
|
||||
// Invalidate cache after update
|
||||
invalidateCache();
|
||||
|
||||
|
||||
console.log(`Incident ${id} updated successfully`);
|
||||
} catch (error) {
|
||||
console.error('Error updating incident:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
import jsPDF from 'jspdf';
|
||||
import { IncidentItem } from '../types';
|
||||
import { userService } from '@/services/userService';
|
||||
import {
|
||||
addBasicInfoSection,
|
||||
addDescriptionSection,
|
||||
@@ -22,6 +23,18 @@ export const generatePdf = async (incident: IncidentItem): Promise<string> => {
|
||||
throw new Error('Invalid incident data');
|
||||
}
|
||||
|
||||
// Fetch assigned user data if available
|
||||
let assignedUser: { full_name?: string; username?: string } | null = null;
|
||||
const assigneeId = incident?.assigned_users || incident?.assigned_to;
|
||||
if (assigneeId) {
|
||||
try {
|
||||
assignedUser = await userService.getUser(assigneeId);
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch assigned user for PDF:', error);
|
||||
// Continue without user data
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new PDF document with portrait orientation
|
||||
const doc = new jsPDF({
|
||||
@@ -85,7 +98,7 @@ export const generatePdf = async (incident: IncidentItem): Promise<string> => {
|
||||
}
|
||||
|
||||
// Add assignment section
|
||||
yPos = addAssignmentSection(doc, incident, yPos);
|
||||
yPos = addAssignmentSection(doc, incident, yPos, assignedUser);
|
||||
|
||||
// Check if we need to add a new page
|
||||
if (yPos > 250) {
|
||||
|
||||
@@ -146,7 +146,7 @@ export const addResolutionSection = (doc: jsPDF, incident: IncidentItem, yPos: n
|
||||
/**
|
||||
* Add the assignment information section to the PDF
|
||||
*/
|
||||
export const addAssignmentSection = (doc: jsPDF, incident: IncidentItem, yPos: number): number => {
|
||||
export const addAssignmentSection = (doc: jsPDF, incident: IncidentItem, yPos: number, assignedUser?: { full_name?: string; username?: string } | null): number => {
|
||||
doc.setFontSize(14);
|
||||
doc.setTextColor(30, 64, 175); // Blue-800
|
||||
doc.text('Assignment Information', 15, yPos);
|
||||
@@ -158,8 +158,16 @@ export const addAssignmentSection = (doc: jsPDF, incident: IncidentItem, yPos: n
|
||||
yPos += 10;
|
||||
doc.setFontSize(10);
|
||||
doc.setTextColor(0, 0, 0);
|
||||
const assignedTo = incident.assigned_to || 'Not assigned';
|
||||
doc.text(`Assigned to: ${assignedTo}`, 15, yPos);
|
||||
|
||||
if (assignedUser) {
|
||||
// Show user name if available
|
||||
const userName = assignedUser.full_name || assignedUser.username || 'Unknown User';
|
||||
doc.text(`Assigned to: ${userName}`, 15, yPos);
|
||||
} else {
|
||||
// Fallback to ID if no user data available
|
||||
const assignedTo = incident.assigned_users || incident.assigned_to || 'Not assigned';
|
||||
doc.text(`Assigned to: ${assignedTo}`, 15, yPos);
|
||||
}
|
||||
|
||||
return yPos + 10;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,8 @@ export type IncidentItem = {
|
||||
service_id?: string;
|
||||
timestamp?: string;
|
||||
description: string;
|
||||
assigned_to?: string;
|
||||
assigned_to?: string; // legacy UI field
|
||||
assigned_users?: string; // server field
|
||||
resolution_time?: string;
|
||||
impact: string;
|
||||
affected_systems: string;
|
||||
@@ -32,7 +33,8 @@ export type CreateIncidentInput = {
|
||||
affected_systems: string;
|
||||
priority: string;
|
||||
service_id?: string;
|
||||
assigned_to?: string;
|
||||
assigned_to?: string; // legacy UI field
|
||||
assigned_users?: string; // server field
|
||||
root_cause?: string;
|
||||
resolution_steps?: string;
|
||||
lessons_learned?: string;
|
||||
@@ -50,7 +52,8 @@ export type UpdateIncidentInput = {
|
||||
affected_systems?: string;
|
||||
priority?: string;
|
||||
service_id?: string;
|
||||
assigned_to?: string;
|
||||
assigned_to?: string; // legacy UI field
|
||||
assigned_users?: string; // server field
|
||||
root_cause?: string;
|
||||
resolution_steps?: string;
|
||||
lessons_learned?: string;
|
||||
|
||||
Reference in New Issue
Block a user