Re_Figma_Code/src/hooks/useCreateRequestForm.ts

316 lines
9.8 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { getWorkflowDetails } from '@/services/workflowApi';
import { getAllConfigurations, AdminConfiguration } from '@/services/adminApi';
export interface RequestTemplate {
id: string;
name: string;
description: string;
category: string;
icon: React.ComponentType<any>;
estimatedTime: string;
commonApprovers: string[];
suggestedSLA: number;
priority: 'high' | 'medium' | 'low';
fields: {
amount?: boolean;
vendor?: boolean;
timeline?: boolean;
impact?: boolean;
};
}
export interface FormData {
template: string;
title: string;
description: string;
category: string;
priority: string;
urgency: string;
businessImpact: string;
amount: string;
currency: string;
vendor: string;
timeline: string;
slaTemplate: string;
slaHours: number;
customSlaHours: number;
slaEndDate: Date | undefined;
expectedCompletionDate: Date | undefined;
breachEscalation: boolean;
reminderSchedule: '25' | '50' | '75';
workflowType: 'sequential' | 'parallel';
requiresAllApprovals: boolean;
escalationEnabled: boolean;
reminderEnabled: boolean;
minimumLevel: number;
maxLevel: number;
approvers: any[];
approverCount: number;
spectators: any[];
ccList: any[];
invitedUsers: any[];
allowComments: boolean;
allowDocumentUpload: boolean;
documents: File[];
tags: string[];
relatedRequests: string[];
costCenter: string;
project: string;
}
const initialFormData: FormData = {
template: '',
title: '',
description: '',
category: '',
priority: '',
urgency: '',
businessImpact: '',
amount: '',
currency: 'USD',
vendor: '',
timeline: '',
slaTemplate: '',
slaHours: 0,
customSlaHours: 0,
slaEndDate: undefined,
expectedCompletionDate: undefined,
breachEscalation: true,
reminderSchedule: '50',
workflowType: 'sequential',
requiresAllApprovals: true,
escalationEnabled: true,
reminderEnabled: true,
minimumLevel: 1,
maxLevel: 1,
approvers: [],
approverCount: 1,
spectators: [],
ccList: [],
invitedUsers: [],
allowComments: true,
allowDocumentUpload: true,
documents: [],
tags: [],
relatedRequests: [],
costCenter: '',
project: ''
};
export interface SystemPolicy {
maxApprovalLevels: number;
maxParticipants: number;
allowSpectators: boolean;
maxSpectators: number;
}
export interface DocumentPolicy {
maxFileSizeMB: number;
allowedFileTypes: string[];
}
/**
* Custom Hook: useCreateRequestForm
*
* Purpose: Manages form state, policies, and draft loading for CreateRequest
*
* Responsibilities:
* - Manages form data state
* - Loads system and document policies
* - Handles draft loading in edit mode
* - Provides form data update functions
*/
export function useCreateRequestForm(
isEditing: boolean,
editRequestId: string,
templates: RequestTemplate[]
) {
const { user } = useAuth();
const [formData, setFormData] = useState<FormData>(initialFormData);
const [selectedTemplate, setSelectedTemplate] = useState<RequestTemplate | null>(null);
const [loadingDraft, setLoadingDraft] = useState(isEditing);
const [systemPolicy, setSystemPolicy] = useState<SystemPolicy>({
maxApprovalLevels: 10,
maxParticipants: 50,
allowSpectators: true,
maxSpectators: 20
});
const [documentPolicy, setDocumentPolicy] = useState<DocumentPolicy>({
maxFileSizeMB: 10,
allowedFileTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'jpg', 'jpeg', 'png', 'gif']
});
const [existingDocuments, setExistingDocuments] = useState<any[]>([]);
// Load policies on mount
useEffect(() => {
const loadPolicies = async () => {
try {
// Load document policy
const docConfigs = await getAllConfigurations('DOCUMENT_POLICY');
const docConfigMap: Record<string, string> = {};
docConfigs.forEach((c: AdminConfiguration) => {
docConfigMap[c.configKey] = c.configValue;
});
const maxFileSizeMB = parseInt(docConfigMap['MAX_FILE_SIZE_MB'] || '10');
const allowedFileTypesStr = docConfigMap['ALLOWED_FILE_TYPES'] || 'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif';
const allowedFileTypes = allowedFileTypesStr.split(',').map(ext => ext.trim().toLowerCase());
setDocumentPolicy({
maxFileSizeMB,
allowedFileTypes
});
// Load system policy
const workflowConfigs = await getAllConfigurations('WORKFLOW_SHARING');
const tatConfigs = await getAllConfigurations('TAT_SETTINGS');
const allConfigs = [...workflowConfigs, ...tatConfigs];
const configMap: Record<string, string> = {};
allConfigs.forEach((c: AdminConfiguration) => {
configMap[c.configKey] = c.configValue;
});
setSystemPolicy({
maxApprovalLevels: parseInt(configMap['MAX_APPROVAL_LEVELS'] || '10'),
maxParticipants: parseInt(configMap['MAX_PARTICIPANTS_PER_REQUEST'] || '50'),
allowSpectators: configMap['ALLOW_ADD_SPECTATOR']?.toLowerCase() === 'true',
maxSpectators: parseInt(configMap['MAX_SPECTATORS_PER_REQUEST'] || '20')
});
} catch (error) {
console.error('Failed to load policies:', error);
}
};
loadPolicies();
}, []);
// Load draft data when in edit mode
useEffect(() => {
if (!isEditing || !editRequestId) return;
let mounted = true;
(async () => {
try {
setLoadingDraft(true);
const details = await getWorkflowDetails(editRequestId);
if (!mounted || !details) return;
const wf = details.workflow || {};
const approvals = Array.isArray(details.approvals) ? details.approvals : [];
const participants = Array.isArray(details.participants) ? details.participants : [];
const documents = Array.isArray(details.documents) ? details.documents.filter((d: any) => !d.isDeleted) : [];
// Store existing documents for tracking
setExistingDocuments(documents);
// Map priority
const priority = (wf.priority || '').toString().toLowerCase();
const priorityMap: Record<string, string> = {
'standard': 'standard',
'express': 'express'
};
// Map template type
const templateType = wf.templateType === 'TEMPLATE' ? 'existing-template' : 'custom';
const template = templates.find(t => t.id === templateType) || templates[0] || null;
setSelectedTemplate(template);
// Map approvers
const mappedApprovers = approvals
.sort((a: any, b: any) => (a.levelNumber || 0) - (b.levelNumber || 0))
.map((approval: any) => {
const tatHours = Number(approval.tatHours || 24);
const tatDays = Math.floor(tatHours / 24);
const tatHoursRemainder = tatHours % 24;
return {
id: approval.approverId || `temp-${approval.levelNumber}`,
name: approval.approverName || approval.approverEmail || '',
email: approval.approverEmail || '',
role: approval.levelName || `Level ${approval.levelNumber}`,
department: '',
avatar: (approval.approverName || approval.approverEmail || 'XX').substring(0, 2).toUpperCase(),
level: approval.levelNumber || 1,
canClose: false,
tat: tatDays > 0 ? tatDays : tatHoursRemainder,
tatType: tatDays > 0 ? 'days' as const : 'hours' as const,
userId: approval.approverId
};
});
// Map spectators
const mappedSpectators = participants
.filter((p: any) => {
const pt = (p.participantType || p.participant_type || '').toString().toUpperCase().trim();
const isSpectator = pt === 'SPECTATOR';
if (!isSpectator) return false;
const hasEmail = !!(p.userEmail || p.user_email || p.email);
return hasEmail;
})
.map((p: any, index: number) => {
const userId = p.userId || p.user_id || p.id;
const userName = p.userName || p.user_name || p.name || '';
const userEmail = p.userEmail || p.user_email || p.email || '';
const avatarText = userName || userEmail || 'XX';
const avatar = avatarText
.split(' ')
.map((s: string) => s[0])
.filter(Boolean)
.join('')
.slice(0, 2)
.toUpperCase();
return {
id: userId || `spectator-${editRequestId}-${index}-${Date.now()}`,
userId: userId,
name: userName || userEmail || 'Spectator',
email: userEmail,
role: 'Spectator',
department: p.department || '',
avatar: avatar,
level: 1,
canClose: false
};
});
setFormData(prev => ({
...prev,
template: templateType,
title: wf.title || '',
description: wf.description || '',
priority: priorityMap[priority] || 'standard',
approvers: mappedApprovers,
approverCount: mappedApprovers.length || 1,
spectators: mappedSpectators,
maxLevel: Math.max(...mappedApprovers.map((a: any) => a.level || 1), 1)
}));
} catch (error) {
console.error('Failed to load draft:', error);
} finally {
if (mounted) setLoadingDraft(false);
}
})();
return () => { mounted = false; };
}, [isEditing, editRequestId, templates]);
const updateFormData = (field: keyof FormData, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
return {
formData,
setFormData,
updateFormData,
selectedTemplate,
setSelectedTemplate,
loadingDraft,
systemPolicy,
documentPolicy,
existingDocuments,
setExistingDocuments
};
}