205 lines
6.8 KiB
TypeScript
205 lines
6.8 KiB
TypeScript
import apiClient from './authApi';
|
|
|
|
export type PriorityUi = 'standard' | 'express';
|
|
|
|
export interface ApproverFormItem {
|
|
email: string;
|
|
name?: string;
|
|
tat?: number | '';
|
|
tatType?: 'hours' | 'days';
|
|
}
|
|
|
|
export interface ParticipantItem {
|
|
id?: string;
|
|
name: string;
|
|
email: string;
|
|
}
|
|
|
|
export interface CreateWorkflowFromFormPayload {
|
|
templateId?: string | null;
|
|
templateType: 'CUSTOM' | 'TEMPLATE';
|
|
title: string;
|
|
description: string;
|
|
priorityUi: PriorityUi;
|
|
approverCount: number;
|
|
approvers: ApproverFormItem[];
|
|
spectators?: ParticipantItem[];
|
|
ccList?: ParticipantItem[];
|
|
}
|
|
|
|
// Utility to generate a RFC4122 v4 UUID (fallback if crypto.randomUUID not available)
|
|
function generateUuid(): string {
|
|
if (typeof crypto !== 'undefined' && (crypto as any).randomUUID) {
|
|
return (crypto as any).randomUUID();
|
|
}
|
|
// Fallback
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
const r = (Math.random() * 16) | 0;
|
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
export interface CreateWorkflowResponse {
|
|
id: string;
|
|
}
|
|
|
|
export async function createWorkflowFromForm(form: CreateWorkflowFromFormPayload): Promise<CreateWorkflowResponse> {
|
|
// Map UI priority to API enum
|
|
const priority = form.priorityUi.toUpperCase() === 'EXPRESS' ? 'EXPRESS' : 'STANDARD';
|
|
|
|
// Build approval levels to match backend schema
|
|
const approvalLevels = Array.from({ length: form.approverCount || 1 }, (_, i) => {
|
|
const idx = i;
|
|
const a = form.approvers[idx] || {} as ApproverFormItem;
|
|
const levelNumber = idx + 1;
|
|
const tatRaw = a.tat ?? '';
|
|
let tatHours = 0;
|
|
if (typeof tatRaw === 'number') {
|
|
tatHours = a.tatType === 'days' ? tatRaw * 24 : tatRaw;
|
|
}
|
|
const approverEmail = a.email || '';
|
|
const approverName = (a.name && a.name.trim()) || approverEmail.split('@')[0] || `Approver ${levelNumber}`;
|
|
return {
|
|
levelNumber,
|
|
levelName: `Level ${levelNumber}`,
|
|
approverId: generateUuid(),
|
|
approverEmail,
|
|
approverName,
|
|
tatHours: tatHours > 0 ? tatHours : 24,
|
|
isFinalApprover: levelNumber === (form.approverCount || 1),
|
|
};
|
|
});
|
|
|
|
// Participants -> spectators and ccList
|
|
const participants = [
|
|
...(form.spectators || []).map(p => ({
|
|
userId: generateUuid(),
|
|
userEmail: p.email,
|
|
userName: p.name || p.email.split('@')[0] || 'Spectator',
|
|
participantType: 'SPECTATOR' as const,
|
|
canComment: true,
|
|
canViewDocuments: true,
|
|
canDownloadDocuments: false,
|
|
notificationEnabled: true,
|
|
})),
|
|
...(form.ccList || []).map(p => ({
|
|
userId: generateUuid(),
|
|
userEmail: p.email,
|
|
userName: p.name || p.email.split('@')[0] || 'CC',
|
|
participantType: 'CONSULTATION' as const,
|
|
canComment: false,
|
|
canViewDocuments: true,
|
|
canDownloadDocuments: false,
|
|
notificationEnabled: true,
|
|
})),
|
|
];
|
|
|
|
const payload = {
|
|
templateType: form.templateType,
|
|
title: form.title,
|
|
description: form.description,
|
|
priority, // STANDARD | EXPRESS
|
|
approvalLevels,
|
|
participants: participants.length ? participants : undefined,
|
|
};
|
|
|
|
const res = await apiClient.post('/workflows', payload);
|
|
const data = (res.data?.data || res.data) as any;
|
|
return { id: data.id || data.workflowId || '' };
|
|
}
|
|
|
|
export async function createWorkflowMultipart(form: CreateWorkflowFromFormPayload, files: File[], category: 'SUPPORTING' | 'APPROVAL' | 'REFERENCE' | 'FINAL' | 'OTHER' = 'SUPPORTING') {
|
|
const isUuid = (v: any) => typeof v === 'string' && /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/.test(v.trim());
|
|
|
|
const payload: any = {
|
|
templateType: form.templateType,
|
|
title: form.title,
|
|
description: form.description,
|
|
priority: form.priorityUi.toUpperCase() === 'EXPRESS' ? 'EXPRESS' : 'STANDARD',
|
|
approvalLevels: Array.from({ length: form.approverCount || 1 }, (_, i) => {
|
|
const a = form.approvers[i] || ({} as any);
|
|
const tat = typeof a.tat === 'number' ? a.tat : 0;
|
|
const approverId = (a.userId || '').trim();
|
|
if (!isUuid(approverId)) {
|
|
throw new Error(`Invalid approverId for level ${i + 1}. Please pick an approver via @ search.`);
|
|
}
|
|
return {
|
|
levelNumber: i + 1,
|
|
levelName: `Level ${i + 1}`,
|
|
approverId,
|
|
approverEmail: a.email || '',
|
|
approverName: a.name || (a.email ? a.email.split('@')[0] : `Approver ${i + 1}`),
|
|
tatHours: a.tatType === 'days' ? tat * 24 : tat || 24,
|
|
isFinalApprover: i + 1 === (form.approverCount || 1),
|
|
};
|
|
}),
|
|
};
|
|
// Pass participants if provided by caller (CreateRequest builds this)
|
|
const incomingParticipants = (form as any).participants;
|
|
if (Array.isArray(incomingParticipants) && incomingParticipants.length) {
|
|
payload.participants = incomingParticipants;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('payload', JSON.stringify(payload));
|
|
formData.append('category', category);
|
|
files.forEach(f => formData.append('files', f));
|
|
|
|
const res = await apiClient.post('/workflows/multipart', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
});
|
|
const data = res.data?.data || res.data;
|
|
return { id: data?.requestId } as any;
|
|
}
|
|
|
|
export async function listWorkflows(params: { page?: number; limit?: number } = {}) {
|
|
const { page = 1, limit = 20 } = params;
|
|
const res = await apiClient.get('/workflows', { params: { page, limit } });
|
|
return res.data?.data || res.data;
|
|
}
|
|
|
|
export async function listMyWorkflows(params: { page?: number; limit?: number } = {}) {
|
|
const { page = 1, limit = 20 } = params;
|
|
const res = await apiClient.get('/workflows/my', { params: { page, limit } });
|
|
return res.data?.data || res.data;
|
|
}
|
|
|
|
export async function listOpenForMe(params: { page?: number; limit?: number } = {}) {
|
|
const { page = 1, limit = 20 } = params;
|
|
const res = await apiClient.get('/workflows/open-for-me', { params: { page, limit } });
|
|
return res.data?.data || res.data;
|
|
}
|
|
|
|
export async function listClosedByMe(params: { page?: number; limit?: number } = {}) {
|
|
const { page = 1, limit = 20 } = params;
|
|
const res = await apiClient.get('/workflows/closed-by-me', { params: { page, limit } });
|
|
return res.data?.data || res.data;
|
|
}
|
|
|
|
export async function getWorkflowDetails(requestId: string) {
|
|
const res = await apiClient.get(`/workflows/${requestId}/details`);
|
|
return res.data?.data || res.data;
|
|
}
|
|
|
|
export default {
|
|
createWorkflowFromForm,
|
|
createWorkflowMultipart,
|
|
listWorkflows,
|
|
listMyWorkflows,
|
|
listOpenForMe,
|
|
listClosedByMe,
|
|
submitWorkflow,
|
|
getWorkflowDetails,
|
|
};
|
|
|
|
export async function submitWorkflow(requestId: string) {
|
|
const res = await apiClient.patch(`/workflows/${requestId}/submit`);
|
|
return res.data?.data || res.data;
|
|
}
|
|
|
|
// Also export in default for convenience
|
|
// Note: keeping separate named export above for tree-shaking
|
|
|
|
|