Re_Figma_Code/src/services/workflowApi.ts

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