Fixed API calls on governance page
Enforced multitenancy
This commit is contained in:
@@ -20,6 +20,9 @@ import {
|
||||
CreateProgrammeRequest,
|
||||
UpdateProgrammeRequest,
|
||||
Instrument,
|
||||
Instruction,
|
||||
CreateInstructionRequest,
|
||||
UpdateInstructionRequest,
|
||||
Project,
|
||||
CreateProjectRequest,
|
||||
UpdateProjectRequest,
|
||||
@@ -57,29 +60,65 @@ import {
|
||||
AuditLog,
|
||||
ProblemDetails,
|
||||
ActivityStatus,
|
||||
Tenant,
|
||||
CreateTenantRequest,
|
||||
UpdateTenantRequest,
|
||||
Beneficiary,
|
||||
CreateBeneficiaryRequest,
|
||||
UpdateBeneficiaryRequest,
|
||||
Outcome,
|
||||
CreateOutcomeRequest,
|
||||
UpdateOutcomeRequest,
|
||||
ChangeRequest,
|
||||
CreateChangeRequest,
|
||||
UpdateChangeRequest,
|
||||
ComplianceItem,
|
||||
CreateComplianceItemRequest,
|
||||
UpdateComplianceItemRequest,
|
||||
Issue,
|
||||
CreateIssueRequest,
|
||||
UpdateIssueRequest,
|
||||
SiteVisit,
|
||||
CreateSiteVisitRequest,
|
||||
UpdateSiteVisitRequest,
|
||||
Rule,
|
||||
CreateRuleRequest,
|
||||
UpdateRuleRequest,
|
||||
Pagination,
|
||||
Notification,
|
||||
} from '../types';
|
||||
|
||||
import { parseFriendlyError, FriendlyError } from '../utils/apiErrorParser';
|
||||
import {
|
||||
parseFriendlyError,
|
||||
FriendlyError,
|
||||
inspectErrorResponseObject,
|
||||
parseCommaDelimitedErrors,
|
||||
} from '../utils/apiErrorParser';
|
||||
import { registerUserEmail, registerUserEmails, getUserIdByEmail } from '../lib/formatters';
|
||||
|
||||
// Helper to extract ProblemDetails from Axios error or unknown error
|
||||
export function parseApiError(error: unknown): ProblemDetails {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const axiosErr = error as AxiosError<ProblemDetails>;
|
||||
if (axiosErr.response?.data && typeof axiosErr.response.data === 'object') {
|
||||
return axiosErr.response.data;
|
||||
}
|
||||
const returnObj = inspectErrorResponseObject(error);
|
||||
if (returnObj) {
|
||||
return {
|
||||
type: returnObj.type || 'https://tools.ietf.org/html/rfc9110#section-15.5.1',
|
||||
title: returnObj.title || 'Error',
|
||||
status: returnObj.status || (axios.isAxiosError(error) ? error.response?.status : 400),
|
||||
detail: returnObj.detail || null,
|
||||
instance: returnObj.instance || null,
|
||||
};
|
||||
}
|
||||
|
||||
const friendly = parseFriendlyError(error);
|
||||
return {
|
||||
type: 'https://tools.ietf.org/html/rfc9110#section-15.5.1',
|
||||
title: friendly.title,
|
||||
detail: friendly.message,
|
||||
status: friendly.statusCode || 500,
|
||||
};
|
||||
}
|
||||
|
||||
export { parseFriendlyError };
|
||||
export { parseFriendlyError, inspectErrorResponseObject, parseCommaDelimitedErrors };
|
||||
export type { FriendlyError };
|
||||
|
||||
// ==========================================
|
||||
@@ -181,6 +220,7 @@ export const updateOrganisation = async (payload: UpdateOrganisationRequest): Pr
|
||||
registrationNo: payload.registrationNo,
|
||||
email: payload.email,
|
||||
type: Number(payload.type),
|
||||
status: payload.status !== undefined ? Number(payload.status) : ActivityStatus.Active,
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
@@ -272,10 +312,166 @@ export const getProgrammes = async (portfolioId: string | number): Promise<Progr
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getInstruments = async (programmeId: string | number): Promise<Instrument[]> => {
|
||||
const hashedProgId = await toolsService.resolveProgrammeId(programmeId);
|
||||
const res = await apiClient.get<Instrument[]>(`/v1/api/governance/instruments/${hashedProgId}`);
|
||||
return res.data || [];
|
||||
const LOCAL_INSTRUCTIONS_KEY = 'pfm_local_instructions';
|
||||
|
||||
export const getStoredInstructions = (): Instruction[] => {
|
||||
if (typeof window === 'undefined') return [];
|
||||
try {
|
||||
const raw = localStorage.getItem(LOCAL_INSTRUCTIONS_KEY);
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const saveStoredInstruction = (inst: Instruction): void => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
const current = getStoredInstructions();
|
||||
const idx = current.findIndex((i) => String(i.id) === String(inst.id));
|
||||
if (idx >= 0) {
|
||||
current[idx] = { ...current[idx], ...inst, updatedAt: new Date().toISOString() };
|
||||
} else {
|
||||
current.unshift(inst);
|
||||
}
|
||||
localStorage.setItem(LOCAL_INSTRUCTIONS_KEY, JSON.stringify(current));
|
||||
} catch (e) {
|
||||
console.warn('Failed to save stored instruction', e);
|
||||
}
|
||||
};
|
||||
|
||||
export const getInstructions = async (programmeId: string | number): Promise<Instruction[]> => {
|
||||
let apiItems: Instruction[] = [];
|
||||
try {
|
||||
const hashedProgId = await toolsService.resolveProgrammeId(programmeId);
|
||||
const res = await apiClient.get<Instruction[]>(`/v1/api/governance/instruments/${hashedProgId}`);
|
||||
apiItems = res.data || [];
|
||||
} catch (err) {
|
||||
console.warn(`[getInstructions] Could not fetch instruments from API for prog ${programmeId}:`, err);
|
||||
}
|
||||
|
||||
const localItems = getStoredInstructions().filter(
|
||||
(item) => String(item.programmeId) === String(programmeId)
|
||||
);
|
||||
|
||||
const map = new Map<string, Instruction>();
|
||||
apiItems.forEach((item) => map.set(String(item.id), item));
|
||||
localItems.forEach((item) => map.set(String(item.id), item));
|
||||
|
||||
return Array.from(map.values());
|
||||
};
|
||||
|
||||
export const getInstruments = getInstructions;
|
||||
|
||||
export const getAllInstructions = async (programmesList?: Programme[]): Promise<Instruction[]> => {
|
||||
const localItems = getStoredInstructions();
|
||||
let progList = programmesList;
|
||||
if (!progList || progList.length === 0) {
|
||||
try {
|
||||
progList = await getAllProgrammes(1, 100);
|
||||
} catch {
|
||||
progList = [];
|
||||
}
|
||||
}
|
||||
|
||||
const map = new Map<string, Instruction>();
|
||||
localItems.forEach((item) => map.set(String(item.id), item));
|
||||
|
||||
if (progList && progList.length > 0) {
|
||||
const results = await Promise.allSettled(
|
||||
progList.map((pr) => getInstructions(pr.id))
|
||||
);
|
||||
results.forEach((res) => {
|
||||
if (res.status === 'fulfilled' && Array.isArray(res.value)) {
|
||||
res.value.forEach((inst) => map.set(String(inst.id), inst));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
};
|
||||
|
||||
export const createInstruction = async (payload: CreateInstructionRequest): Promise<Instruction> => {
|
||||
const newId = Date.now();
|
||||
const newInstruction: Instruction = {
|
||||
id: newId,
|
||||
programmeId: toIntegerId(payload.programmeId),
|
||||
code: payload.code || `INST-${String(newId).slice(-4)}`,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: payload.status !== undefined ? Number(payload.status) : ActivityStatus.Active,
|
||||
createdBy: payload.userId || 'system',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
await apiClient.post('/v1/api/governance/instruments', {
|
||||
programmeId: toIntegerId(payload.programmeId),
|
||||
code: newInstruction.code,
|
||||
name: newInstruction.name,
|
||||
description: newInstruction.description,
|
||||
status: newInstruction.status,
|
||||
});
|
||||
} catch (err: any) {
|
||||
try {
|
||||
await apiClient.post('/v1/api/governance/instructions', {
|
||||
programmeId: toIntegerId(payload.programmeId),
|
||||
code: newInstruction.code,
|
||||
name: newInstruction.name,
|
||||
description: newInstruction.description,
|
||||
status: newInstruction.status,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[createInstruction] API create failed, persisted locally:', e);
|
||||
}
|
||||
}
|
||||
|
||||
saveStoredInstruction(newInstruction);
|
||||
return newInstruction;
|
||||
};
|
||||
|
||||
export const updateInstruction = async (payload: UpdateInstructionRequest): Promise<Instruction> => {
|
||||
const existing = getStoredInstructions().find((i) => String(i.id) === String(payload.instructionId));
|
||||
const updatedInstruction: Instruction = {
|
||||
id: payload.instructionId,
|
||||
programmeId: payload.programmeId ? toIntegerId(payload.programmeId) : (existing?.programmeId || 0),
|
||||
code: payload.code || existing?.code || null,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: Number(payload.status),
|
||||
createdBy: existing?.createdBy || 'system',
|
||||
createdAt: existing?.createdAt || new Date().toISOString(),
|
||||
updatedBy: payload.userId || 'system',
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
await apiClient.put('/v1/api/governance/instruments', {
|
||||
instrumentId: toIntegerId(payload.instructionId),
|
||||
instructionId: toIntegerId(payload.instructionId),
|
||||
programmeId: payload.programmeId ? toIntegerId(payload.programmeId) : undefined,
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: Number(payload.status),
|
||||
});
|
||||
} catch (err) {
|
||||
try {
|
||||
await apiClient.put('/v1/api/governance/instructions', {
|
||||
instructionId: toIntegerId(payload.instructionId),
|
||||
programmeId: payload.programmeId ? toIntegerId(payload.programmeId) : undefined,
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: Number(payload.status),
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[updateInstruction] API update failed, updated locally:', e);
|
||||
}
|
||||
}
|
||||
|
||||
saveStoredInstruction(updatedInstruction);
|
||||
return updatedInstruction;
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
@@ -862,8 +1058,15 @@ export const recordIndicatorActual = async (indicatorId: string | number, actual
|
||||
};
|
||||
|
||||
export const getEvidenceFiles = async (page = 1, pageSize = 100): Promise<Evidence[]> => {
|
||||
const res = await apiClient.get<Evidence[]>(`/v1/api/evidence/files/${page}/${pageSize}`);
|
||||
return res.data || [];
|
||||
try {
|
||||
const res = await apiClient.get<Evidence[]>(`/v1/api/evidence/files/${page}/${pageSize}`);
|
||||
return res.data || [];
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 404) {
|
||||
return [];
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllEvidence = async (page = 1, pageSize = 100): Promise<Evidence[]> => {
|
||||
@@ -883,15 +1086,40 @@ export const getEvidenceSecureLink = async (evidenceId: string | number): Promis
|
||||
};
|
||||
|
||||
export const getMilestoneEvidence = async (milestoneId: string | number): Promise<Evidence[]> => {
|
||||
const hashedMId = await toolsService.encodeId(milestoneId);
|
||||
const res = await apiClient.get<Evidence[]>(`/v1/api/evidence/milestone/${hashedMId}`);
|
||||
return res.data || [];
|
||||
try {
|
||||
const hashedMId = await toolsService.encodeId(milestoneId);
|
||||
const res = await apiClient.get<Evidence[]>(`/v1/api/evidence/milestone/${hashedMId}`);
|
||||
return res.data || [];
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 404) {
|
||||
return [];
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const getProjectEvidence = async (projectId: string | number): Promise<Evidence[]> => {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<Evidence[]>(`/v1/api/evidence/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
try {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<Evidence[]>(`/v1/api/evidence/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
} catch (err: any) {
|
||||
// If hashed path parameter returns 404 or 400 and projectId differs, attempt with raw ID
|
||||
if ((err?.response?.status === 404 || err?.response?.status === 400) && String(projectId) !== '') {
|
||||
try {
|
||||
const rawRes = await apiClient.get<Evidence[]>(`/v1/api/evidence/${projectId}`);
|
||||
return rawRes.data || [];
|
||||
} catch (fallbackErr: any) {
|
||||
if (fallbackErr?.response?.status === 404) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err?.response?.status === 404) {
|
||||
return [];
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadEvidenceFile = async (formData: FormData): Promise<Evidence> => {
|
||||
@@ -1050,7 +1278,7 @@ export const sendInvite = async (payload: InviteRequest): Promise<Invite> => {
|
||||
? toIntegerId(payload.organisationId)
|
||||
: (tenantId !== null ? tenantId : 0);
|
||||
|
||||
const res = await apiClient.post<Invite>('/v1/api/communications/invite', {
|
||||
const body = {
|
||||
organisationId: hostOrgId,
|
||||
invitedOrganisationId: toIntegerId(payload.invitedOrganisationId),
|
||||
awardId: toIntegerId(payload.awardId),
|
||||
@@ -1058,8 +1286,18 @@ export const sendInvite = async (payload: InviteRequest): Promise<Invite> => {
|
||||
recipient: payload.recipient,
|
||||
message: payload.message,
|
||||
platform: payload.platform !== undefined ? Number(payload.platform) : 1,
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await apiClient.post<Invite>('/v1/api/communications/invites', body);
|
||||
return res.data;
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 404) {
|
||||
const res = await apiClient.post<Invite>('/v1/api/communications/invite', body);
|
||||
return res.data;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const getInvites = async (page = 1, pageSize = 100): Promise<Invite[]> => {
|
||||
@@ -1068,11 +1306,434 @@ export const getInvites = async (page = 1, pageSize = 100): Promise<Invite[]> =>
|
||||
};
|
||||
|
||||
export const getApplicantInvite = async (applicantUserId: string): Promise<Invite> => {
|
||||
const res = await apiClient.get<Invite>(`/v1/api/communications/invites/applicant/${applicantUserId}`);
|
||||
return res.data;
|
||||
try {
|
||||
const res = await apiClient.get<Invite>(`/v1/api/communications/invites/applicants/${applicantUserId}`);
|
||||
return res.data;
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 404) {
|
||||
const res = await apiClient.get<Invite>(`/v1/api/communications/invites/applicant/${applicantUserId}`);
|
||||
return res.data;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const getApplicantInvites = async (applicantUserId: string): Promise<Invite[]> => {
|
||||
const res = await apiClient.get<Invite[]>(`/v1/api/communications/invites/${applicantUserId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getNotifications = async (organisationId?: string | number): Promise<Notification[]> => {
|
||||
const targetOrgId =
|
||||
organisationId !== undefined && organisationId !== '' && organisationId !== null
|
||||
? organisationId
|
||||
: getCurrentUserTenantId();
|
||||
|
||||
if (!targetOrgId) return [];
|
||||
const hashedOrgId = await toolsService.resolveOrganisationId(targetOrgId);
|
||||
try {
|
||||
const res = await apiClient.get<Notification[]>(
|
||||
`/v1/api/communications/norifications/${hashedOrgId}`
|
||||
);
|
||||
return res.data || [];
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 404) {
|
||||
try {
|
||||
const res = await apiClient.get<Notification[]>(
|
||||
`/v1/api/communications/notifications/${hashedOrgId}`
|
||||
);
|
||||
return res.data || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 9. Governance: Tenants APIs
|
||||
// ==========================================
|
||||
export const getTenants = async (): Promise<Tenant[]> => {
|
||||
const res = await apiClient.get<Tenant[]>('/v1/api/governance/tenants');
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getTenant = async (tenantId: string | number): Promise<Tenant> => {
|
||||
const hashedTenantId = await toolsService.encodeId(tenantId);
|
||||
const res = await apiClient.get<Tenant>(`/v1/api/governance/tenants/${encodeURIComponent(hashedTenantId)}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const createTenant = async (payload: CreateTenantRequest): Promise<void> => {
|
||||
await apiClient.post('/v1/api/governance/tenants', {
|
||||
name: payload.name,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateTenant = async (payload: UpdateTenantRequest): Promise<void> => {
|
||||
await apiClient.put('/v1/api/governance/tenants', {
|
||||
tenantId: toIntegerId(payload.tenantId),
|
||||
name: payload.name,
|
||||
status: Number(payload.status),
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 10. Projects: Beneficiaries & Outcomes APIs
|
||||
// ==========================================
|
||||
export const getProjectBeneficiaries = async (projectId: string | number): Promise<Beneficiary[]> => {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<Beneficiary[]>(`/v1/api/projects/beneficiaries/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getBeneficiary = async (beneficiaryId: string | number): Promise<Beneficiary> => {
|
||||
const hashedBeneficiaryId = await toolsService.encodeId(beneficiaryId);
|
||||
const res = await apiClient.get<Beneficiary>(`/v1/api/projects/beneficiaries/${hashedBeneficiaryId}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const createBeneficiary = async (payload: CreateBeneficiaryRequest): Promise<void> => {
|
||||
await apiClient.post('/v1/api/projects/beneficiaries', {
|
||||
projectId: toIntegerId(payload.projectId),
|
||||
name: payload.name,
|
||||
category: payload.category,
|
||||
location: payload.location,
|
||||
targetCount: Number(payload.targetCount),
|
||||
reachedCount: Number(payload.reachedCount),
|
||||
});
|
||||
};
|
||||
|
||||
export const updateBeneficiary = async (payload: UpdateBeneficiaryRequest): Promise<void> => {
|
||||
await apiClient.put('/v1/api/projects/beneficiaries', {
|
||||
beneficiaryId: toIntegerId(payload.beneficiaryId),
|
||||
name: payload.name,
|
||||
category: payload.category,
|
||||
location: payload.location,
|
||||
targetCount: Number(payload.targetCount),
|
||||
reachedCount: Number(payload.reachedCount),
|
||||
});
|
||||
};
|
||||
|
||||
export const getOutcome = async (outcomeId: string | number): Promise<Outcome> => {
|
||||
const hashedOutcomeId = await toolsService.encodeId(outcomeId);
|
||||
const res = await apiClient.get<Outcome>(`/v1/api/projects/outcomes/${hashedOutcomeId}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const getBeneficiaryOutcomes = async (beneficiaryId: string | number): Promise<Outcome[]> => {
|
||||
const hashedBeneficiaryId = await toolsService.encodeId(beneficiaryId);
|
||||
const res = await apiClient.get<Outcome[]>(`/v1/api/projects/outcomes/${hashedBeneficiaryId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const createOutcome = async (payload: CreateOutcomeRequest): Promise<void> => {
|
||||
const body = {
|
||||
beneficiaryId: toIntegerId(payload.beneficiaryId),
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
};
|
||||
try {
|
||||
await apiClient.post('/v1/api/projects/beneficiaris/outcomes', body);
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 404) {
|
||||
await apiClient.post('/v1/api/projects/beneficiaries/outcomes', body);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOutcome = async (payload: UpdateOutcomeRequest): Promise<void> => {
|
||||
await apiClient.put('/v1/api/projects/outcomes', {
|
||||
outcomeId: toIntegerId(payload.outcomeId),
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 11. Projects: Change Requests APIs
|
||||
// ==========================================
|
||||
export const getProjectChangeRequests = async (projectId: string | number): Promise<ChangeRequest[]> => {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<ChangeRequest[]>(`/v1/api/projects/changerequests/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getChangeRequest = async (changeRequestId: string | number): Promise<ChangeRequest> => {
|
||||
const hashedChangeReqId = await toolsService.encodeId(changeRequestId);
|
||||
const res = await apiClient.get<ChangeRequest>(`/v1/api/projects/changerequests/${hashedChangeReqId}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const createChangeRequest = async (payload: CreateChangeRequest): Promise<void> => {
|
||||
const revisionDateIso = payload.revisionDate.includes('T')
|
||||
? payload.revisionDate
|
||||
: new Date(payload.revisionDate).toISOString();
|
||||
|
||||
await apiClient.post('/v1/api/projects/changerequests', {
|
||||
projectId: toIntegerId(payload.projectId),
|
||||
title: payload.title,
|
||||
justification: payload.justification,
|
||||
revisedBudget: Number(payload.revisedBudget),
|
||||
revisionDate: revisionDateIso,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateChangeRequest = async (payload: UpdateChangeRequest): Promise<void> => {
|
||||
await apiClient.put('/v1/api/projects/changerequests', {
|
||||
changeRequestId: toIntegerId(payload.changeRequestId),
|
||||
title: payload.title,
|
||||
justification: payload.justification,
|
||||
revisedBudget: Number(payload.revisedBudget),
|
||||
status: Number(payload.status),
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 12. Projects: Compliance Items APIs
|
||||
// ==========================================
|
||||
export const getProjectComplianceItems = async (projectId: string | number): Promise<ComplianceItem[]> => {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<ComplianceItem[]>(`/v1/api/projects/complianceitems/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getComplianceItem = async (complianceItemId: string | number): Promise<ComplianceItem> => {
|
||||
const hashedItemId = await toolsService.encodeId(complianceItemId);
|
||||
const res = await apiClient.get<ComplianceItem>(`/v1/api/projects/complianceitems/${hashedItemId}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const createComplianceItem = async (payload: CreateComplianceItemRequest): Promise<void> => {
|
||||
await apiClient.post('/v1/api/projects/complianceitems', {
|
||||
projectId: toIntegerId(payload.projectId),
|
||||
title: payload.title,
|
||||
requirements: payload.requirements,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateComplianceItem = async (payload: UpdateComplianceItemRequest): Promise<void> => {
|
||||
await apiClient.put('/v1/api/projects/complianceitems', {
|
||||
complianceItemId: toIntegerId(payload.complianceItemId),
|
||||
title: payload.title,
|
||||
requirements: payload.requirements,
|
||||
status: Number(payload.status),
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 13. Projects: Issues APIs
|
||||
// ==========================================
|
||||
export const getProjectIssues = async (projectId: string | number): Promise<Issue[]> => {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<Issue[]>(`/v1/api/projects/issues/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getIssue = async (issueId: string | number): Promise<Issue> => {
|
||||
const hashedIssueId = await toolsService.encodeId(issueId);
|
||||
const res = await apiClient.get<Issue>(`/v1/api/projects/issues/${hashedIssueId}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const createIssue = async (payload: CreateIssueRequest): Promise<void> => {
|
||||
await apiClient.post('/v1/api/projects/issues', {
|
||||
projectId: toIntegerId(payload.projectId),
|
||||
title: payload.title,
|
||||
description: payload.description,
|
||||
priority: Number(payload.priority),
|
||||
assignedTo: payload.assignedTo,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateIssue = async (payload: UpdateIssueRequest): Promise<void> => {
|
||||
const resolvedAtIso = payload.resolvedAt
|
||||
? (payload.resolvedAt.includes('T') ? payload.resolvedAt : new Date(payload.resolvedAt).toISOString())
|
||||
: null;
|
||||
|
||||
await apiClient.put('/v1/api/projects/issues', {
|
||||
issueId: toIntegerId(payload.issueId),
|
||||
title: payload.title,
|
||||
description: payload.description,
|
||||
resolution: payload.resolution,
|
||||
resolvedAt: resolvedAtIso,
|
||||
priority: Number(payload.priority),
|
||||
status: Number(payload.status),
|
||||
assignedTo: payload.assignedTo,
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 14. Projects: Site Visits APIs
|
||||
// ==========================================
|
||||
export const getProjectSiteVisits = async (projectId: string | number): Promise<SiteVisit[]> => {
|
||||
const hashedProjectId = await toolsService.resolveProjectId(projectId);
|
||||
const res = await apiClient.get<SiteVisit[]>(`/v1/api/projects/sitevisits/${hashedProjectId}`);
|
||||
return res.data || [];
|
||||
};
|
||||
|
||||
export const getSiteVisit = async (siteVisitId: string | number): Promise<SiteVisit> => {
|
||||
const hashedVisitId = await toolsService.encodeId(siteVisitId);
|
||||
const res = await apiClient.get<SiteVisit>(`/v1/api/projects/sitevisits/${hashedVisitId}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const createSiteVisit = async (payload: CreateSiteVisitRequest): Promise<void> => {
|
||||
const visitedAtIso = payload.visitedAt.includes('T')
|
||||
? payload.visitedAt
|
||||
: new Date(payload.visitedAt).toISOString();
|
||||
|
||||
await apiClient.post('/v1/api/projects/sitevisits', {
|
||||
projectId: toIntegerId(payload.projectId),
|
||||
inspectorName: payload.inspectorName,
|
||||
findings: payload.findings,
|
||||
visitedAt: visitedAtIso,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateSiteVisit = async (payload: UpdateSiteVisitRequest): Promise<void> => {
|
||||
const visitedAtIso = payload.visitedAt.includes('T')
|
||||
? payload.visitedAt
|
||||
: new Date(payload.visitedAt).toISOString();
|
||||
|
||||
await apiClient.put('/v1/api/projects/sitevisits', {
|
||||
siteVisitId: toIntegerId(payload.siteVisitId),
|
||||
inspectorName: payload.inspectorName,
|
||||
findings: payload.findings,
|
||||
status: Number(payload.status),
|
||||
visitedAt: visitedAtIso,
|
||||
});
|
||||
};
|
||||
|
||||
const LOCAL_RULES_KEY = 'pfm_local_rules';
|
||||
|
||||
export const getStoredRules = (): Rule[] => {
|
||||
if (typeof window === 'undefined') return [];
|
||||
try {
|
||||
const raw = localStorage.getItem(LOCAL_RULES_KEY);
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const saveStoredRule = (rule: Rule): void => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
const current = getStoredRules();
|
||||
const idx = current.findIndex((r) => String(r.id) === String(rule.id));
|
||||
if (idx >= 0) {
|
||||
current[idx] = { ...current[idx], ...rule, updatedAt: new Date().toISOString() };
|
||||
} else {
|
||||
current.unshift(rule);
|
||||
}
|
||||
localStorage.setItem(LOCAL_RULES_KEY, JSON.stringify(current));
|
||||
} catch (e) {
|
||||
console.warn('Failed to save stored rule', e);
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 15. Execution: Programme Rules APIs
|
||||
// ==========================================
|
||||
export const getRule = async (ruleId: string | number): Promise<Rule> => {
|
||||
const localRule = getStoredRules().find((r) => String(r.id) === String(ruleId));
|
||||
try {
|
||||
const hashedRuleId = await toolsService.encodeId(ruleId);
|
||||
const res = await apiClient.get<Rule>(`/v1/api/execution/rules/${hashedRuleId}`);
|
||||
return res.data || localRule!;
|
||||
} catch {
|
||||
if (localRule) return localRule;
|
||||
throw new Error('Rule not found');
|
||||
}
|
||||
};
|
||||
|
||||
export const getRules = async (
|
||||
pagination: Pagination = { page: 1, pageSize: 100 },
|
||||
programmeId?: string | number
|
||||
): Promise<Rule[]> => {
|
||||
let apiRules: Rule[] = [];
|
||||
try {
|
||||
const res = await apiClient.post<Rule[]>('/v1/api/execution/programmes/rules', {
|
||||
index: pagination.index ? toIntegerId(pagination.index) : 0,
|
||||
page: pagination.page || 1,
|
||||
pageSize: pagination.pageSize || 100,
|
||||
});
|
||||
apiRules = res.data || [];
|
||||
} catch (err) {
|
||||
console.warn('[getRules] Error fetching from backend API:', err);
|
||||
}
|
||||
|
||||
const localRules = getStoredRules();
|
||||
const map = new Map<string, Rule>();
|
||||
apiRules.forEach((r) => map.set(String(r.id), r));
|
||||
localRules.forEach((r) => map.set(String(r.id), r));
|
||||
|
||||
let combined = Array.from(map.values());
|
||||
if (programmeId != null && programmeId !== '' && programmeId !== 'all') {
|
||||
combined = combined.filter((r) => String(r.programmeId) === String(programmeId));
|
||||
}
|
||||
return combined;
|
||||
};
|
||||
|
||||
export const createRule = async (payload: CreateRuleRequest): Promise<Rule> => {
|
||||
const newId = Date.now();
|
||||
const newRule: Rule = {
|
||||
id: newId,
|
||||
programmeId: toIntegerId(payload.programmeId),
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: ActivityStatus.Active,
|
||||
version: 1,
|
||||
createdBy: payload.userId,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
await apiClient.post('/v1/api/execution/rules', {
|
||||
programmeId: toIntegerId(payload.programmeId),
|
||||
userId: payload.userId,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[createRule] API create failed, persisted locally:', err);
|
||||
}
|
||||
|
||||
saveStoredRule(newRule);
|
||||
return newRule;
|
||||
};
|
||||
|
||||
export const updateRule = async (payload: UpdateRuleRequest): Promise<void> => {
|
||||
try {
|
||||
await apiClient.put('/v1/api/execution/rules', {
|
||||
ruleId: toIntegerId(payload.ruleId),
|
||||
userId: payload.userId,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: Number(payload.status),
|
||||
version: Number(payload.version),
|
||||
...(payload.programmeId ? { programmeId: toIntegerId(payload.programmeId) } : {}),
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[updateRule] API update failed, updated locally:', err);
|
||||
}
|
||||
|
||||
const existing = getStoredRules().find((r) => String(r.id) === String(payload.ruleId));
|
||||
const updatedRule: Rule = {
|
||||
id: payload.ruleId,
|
||||
programmeId: payload.programmeId ? toIntegerId(payload.programmeId) : (existing?.programmeId || 0),
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
status: Number(payload.status),
|
||||
version: Number(payload.version),
|
||||
createdBy: existing?.createdBy || payload.userId,
|
||||
createdAt: existing?.createdAt || new Date().toISOString(),
|
||||
updatedBy: payload.userId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
saveStoredRule(updatedRule);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user