Refactored dashboard
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
import { apiClient } from '../httpClient';
|
||||
import { encodeHashIdSync } from '../toolsService';
|
||||
import * as Models from './models';
|
||||
|
||||
/**
|
||||
* Utility helper to ensure URL parameters are encoded using the Hashids service
|
||||
*/
|
||||
export function hashParam(id: number | string | null | undefined): string {
|
||||
if (id === undefined || id === null || id === '') return '';
|
||||
return encodeHashIdSync(id);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Users & Integration Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const usersApi = {
|
||||
getUser: (userId: string | number) =>
|
||||
apiClient.get<Models.User>(`/v1/api/users/${hashParam(userId)}`),
|
||||
|
||||
getUsersPaged: (page: number, pageSize: number, filter?: Models.UserFilterRequest) =>
|
||||
apiClient.post<Models.User[]>(`/v1/api/users/${page}/${pageSize}`, filter || {}),
|
||||
|
||||
syncIntegration: (payload: any) =>
|
||||
apiClient.post<any>('/v1/api/integration/sync', payload),
|
||||
|
||||
updateUserStatus: (userId: string | number, status: string | number) =>
|
||||
apiClient.get<any>(`/v1/api/users/${hashParam(userId)}/status/${status}`),
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Projects & Sub-resources Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const projectsApi = {
|
||||
// Projects
|
||||
getProjects: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Project[]>('/v1/api/projects', { params }),
|
||||
|
||||
getProject: (projectId: string | number) =>
|
||||
apiClient.get<Models.Project>(`/v1/api/projects/${hashParam(projectId)}`),
|
||||
|
||||
createProject: (payload: Models.CreateProjectRequest) =>
|
||||
apiClient.post<Models.Project>('/v1/api/projects', payload),
|
||||
|
||||
updateProject: (payload: Models.UpdateProjectRequest) =>
|
||||
apiClient.put<Models.Project>('/v1/api/projects', payload),
|
||||
|
||||
updateProjectStatus: (payload: Models.UpdateProjectStatusRequest) =>
|
||||
apiClient.put<any>('/v1/api/projects/status', payload),
|
||||
|
||||
// Activities
|
||||
createActivity: (payload: Models.CreateActivityRequest) =>
|
||||
apiClient.post<Models.Activity>('/v1/api/projects/activities', payload),
|
||||
|
||||
getProjectActivities: (projectId: string | number) =>
|
||||
apiClient.get<Models.Activity[]>(`/v1/api/projects/${hashParam(projectId)}/activities`),
|
||||
|
||||
getActivity: (activityId: string | number) =>
|
||||
apiClient.get<Models.Activity>(`/v1/api/projects/activities/${hashParam(activityId)}`),
|
||||
|
||||
// Beneficiaries
|
||||
createBeneficiary: (payload: Models.CreateBeneficiaryRequest) =>
|
||||
apiClient.post<Models.Beneficiary>('/v1/api/projects/beneficiaries', payload),
|
||||
|
||||
updateBeneficiary: (payload: Models.UpdateBeneficiaryRequest) =>
|
||||
apiClient.put<Models.Beneficiary>('/v1/api/projects/beneficiaries', payload),
|
||||
|
||||
getProjectBeneficiaries: (projectId: string | number) =>
|
||||
apiClient.get<Models.Beneficiary[]>(`/v1/api/projects/${hashParam(projectId)}/beneficiaries`),
|
||||
|
||||
getBeneficiary: (beneficiaryId: string | number) =>
|
||||
apiClient.get<Models.Beneficiary>(`/v1/api/projects/beneficiaries/${hashParam(beneficiaryId)}`),
|
||||
|
||||
// Outcomes
|
||||
createOutcome: (payload: Models.CreateOutcomeRequest) =>
|
||||
apiClient.post<Models.Outcome>('/v1/api/projects/beneficiaris/outcomes', payload),
|
||||
|
||||
updateOutcome: (payload: Models.UpdateOutcomeRequest) =>
|
||||
apiClient.put<Models.Outcome>('/v1/api/projects/outcomes', payload),
|
||||
|
||||
getOutcome: (outcomeId: string | number) =>
|
||||
apiClient.get<Models.Outcome>(`/v1/api/projects/outcomes/${hashParam(outcomeId)}`),
|
||||
|
||||
getBeneficiaryOutcomes: (beneficiaryId: string | number) =>
|
||||
apiClient.get<Models.Outcome[]>(`/v1/api/projects/outcomes/${hashParam(beneficiaryId)}`),
|
||||
|
||||
// Change Requests
|
||||
createChangeRequest: (payload: Models.CreateChangeRequest) =>
|
||||
apiClient.post<Models.ChangeRequest>('/v1/api/projects/changerequests', payload),
|
||||
|
||||
updateChangeRequest: (payload: Models.UpdateChangeRequest) =>
|
||||
apiClient.put<Models.ChangeRequest>('/v1/api/projects/changerequests', payload),
|
||||
|
||||
getProjectChangeRequests: (projectId: string | number) =>
|
||||
apiClient.get<Models.ChangeRequest[]>(`/v1/api/projects/${hashParam(projectId)}/changerequests`),
|
||||
|
||||
getChangeRequest: (changeRequestId: string | number) =>
|
||||
apiClient.get<Models.ChangeRequest>(`/v1/api/projects/changerequests/${hashParam(changeRequestId)}`),
|
||||
|
||||
// Compliance
|
||||
createComplianceItem: (payload: Models.CreateComplianceItemRequest) =>
|
||||
apiClient.post<Models.ComplianceItem>('/v1/api/projects/complianceitems', payload),
|
||||
|
||||
updateComplianceItem: (payload: Models.UpdateComplianceItemRequest) =>
|
||||
apiClient.put<Models.ComplianceItem>('/v1/api/projects/complianceitems', payload),
|
||||
|
||||
getProjectComplianceItems: (projectId: string | number) =>
|
||||
apiClient.get<Models.ComplianceItem[]>(`/v1/api/projects/${hashParam(projectId)}/complianceitems`),
|
||||
|
||||
getComplianceItem: (complianceItemId: string | number) =>
|
||||
apiClient.get<Models.ComplianceItem>(`/v1/api/projects/complianceitems/${hashParam(complianceItemId)}`),
|
||||
|
||||
// Issues
|
||||
createIssue: (payload: Models.CreateIssueRequest) =>
|
||||
apiClient.post<Models.Issue>('/v1/api/projects/issues', payload),
|
||||
|
||||
updateIssue: (payload: Models.UpdateIssueRequest) =>
|
||||
apiClient.put<Models.Issue>('/v1/api/projects/issues', payload),
|
||||
|
||||
getProjectIssues: (projectId: string | number) =>
|
||||
apiClient.get<Models.Issue[]>(`/v1/api/projects/${hashParam(projectId)}/issues`),
|
||||
|
||||
getIssue: (issueId: string | number) =>
|
||||
apiClient.get<Models.Issue>(`/v1/api/projects/issues/${hashParam(issueId)}`),
|
||||
|
||||
// Milestones
|
||||
getProjectMilestones: (projectId: string | number) =>
|
||||
apiClient.get<Models.Milestone[]>(`/v1/api/projects/${hashParam(projectId)}/milestones`),
|
||||
|
||||
getMilestone: (milestoneId: string | number) =>
|
||||
apiClient.get<Models.Milestone>(`/v1/api/projects/milestones/${hashParam(milestoneId)}`),
|
||||
|
||||
getMilestonesPaged: (page: number, pageSize: number) =>
|
||||
apiClient.get<Models.Milestone[]>(`/v1/api/projects/milestones/${page}/${pageSize}`),
|
||||
|
||||
updateMilestone: (payload: Models.UpdateMilestoneRequest) =>
|
||||
apiClient.put<Models.Milestone>('/v1/api/projects/milestone', payload),
|
||||
|
||||
// Indicators & Risks & Site Visits
|
||||
getProjectIndicators: (projectId: string | number) =>
|
||||
apiClient.get<Models.Indicator[]>(`/v1/api/projects/indicators/project/${hashParam(projectId)}`),
|
||||
|
||||
getProjectRisks: (projectId: string | number) =>
|
||||
apiClient.get<Models.Risk[]>(`/v1/api/projects/${hashParam(projectId)}/risks`),
|
||||
|
||||
getProjectSiteVisits: (projectId: string | number) =>
|
||||
apiClient.get<Models.SiteVisit[]>(`/v1/api/projects/${hashParam(projectId)}/sitevisits`),
|
||||
|
||||
getSiteVisit: (siteVisitId: string | number) =>
|
||||
apiClient.get<Models.SiteVisit>(`/v1/api/projects/sitevisits/${hashParam(siteVisitId)}`),
|
||||
|
||||
createSiteVisit: (payload: Models.CreateSiteVisitRequest) =>
|
||||
apiClient.post<Models.SiteVisit>('/v1/api/projects/sitevisits', payload),
|
||||
|
||||
updateSiteVisit: (payload: Models.UpdateSiteVisitRequest) =>
|
||||
apiClient.put<Models.SiteVisit>('/v1/api/projects/sitevisits', payload),
|
||||
|
||||
// Programmes under projects
|
||||
createProgramme: (payload: Models.CreateProgrammeRequest) =>
|
||||
apiClient.post<Models.Programme>('/v1/api/projects/programme', payload),
|
||||
|
||||
updateProgramme: (payload: Models.UpdateProgrammeRequest) =>
|
||||
apiClient.put<Models.Programme>('/v1/api/projects/programme', payload),
|
||||
|
||||
getProgrammeProjects: (programmeId: string | number) =>
|
||||
apiClient.get<Models.Project[]>(`/v1/api/projects/${hashParam(programmeId)}/programme`),
|
||||
|
||||
getProgrammeById: (programmeId: string | number) =>
|
||||
apiClient.get<Models.Programme>(`/v1/api/projects/programme/${hashParam(programmeId)}`),
|
||||
|
||||
getProgrammesPaged: (page: number, pageSize: number) =>
|
||||
apiClient.get<Models.Programme[]>(`/v1/api/projects/programme/${page}/${pageSize}`),
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Grants Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const grantsApi = {
|
||||
// Awards
|
||||
createAward: (payload: Models.CreateAwardRequest) =>
|
||||
apiClient.post<Models.Award>('/v1/api/grants/awards', payload),
|
||||
|
||||
getAwards: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Award[]>('/v1/api/grants/awards', { params }),
|
||||
|
||||
getAward: (awardId: string | number) =>
|
||||
apiClient.get<Models.Award>(`/v1/api/grants/awards/${hashParam(awardId)}`),
|
||||
|
||||
// Budgets
|
||||
createBudget: (payload: Models.CreateBudgetRequest) =>
|
||||
apiClient.post<Models.Budget>('/v1/api/grants/budgets', payload),
|
||||
|
||||
getBudgets: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Budget[]>('/v1/api/grants/budgets', { params }),
|
||||
|
||||
updateBudget: (payload: Models.UpdateBudgetRequest) =>
|
||||
apiClient.put<Models.Budget>('/v1/api/grants/budgets', payload),
|
||||
|
||||
getBudget: (budgetId: string | number) =>
|
||||
apiClient.get<Models.Budget>(`/v1/api/grants/budgets/${hashParam(budgetId)}`),
|
||||
|
||||
// Contracts
|
||||
createContract: (payload: Models.CreateContractRequest) =>
|
||||
apiClient.post<Models.Contract>('/v1/api/grants/contracts', payload),
|
||||
|
||||
getContracts: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Contract[]>('/v1/api/grants/contracts', { params }),
|
||||
|
||||
updateContract: (payload: Models.UpdateContractRequest) =>
|
||||
apiClient.put<Models.Contract>('/v1/api/grants/contracts', payload),
|
||||
|
||||
executeContract: (payload: Models.ExecuteContractRequest) =>
|
||||
apiClient.post<any>('/v1/api/grants/contracts/execute', payload),
|
||||
|
||||
getContract: (contractId: string | number) =>
|
||||
apiClient.get<Models.Contract>(`/v1/api/grants/contracts/${hashParam(contractId)}`),
|
||||
|
||||
uploadContractFile: (contractId: string | number, formData: FormData) =>
|
||||
apiClient.post<any>(`/v1/api/grants/contracts/${hashParam(contractId)}`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
}),
|
||||
|
||||
getContractSecureLink: (contractId: string | number) =>
|
||||
apiClient.get<{ url: string }>(`/v1/api/grants/contracts/${hashParam(contractId)}/securelink`),
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Governance Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const governanceApi = {
|
||||
getDashboard: () =>
|
||||
apiClient.get<Models.Dashboard>('/v1/api/governance/dashboard'),
|
||||
|
||||
getAuditLogs: (payload?: any) =>
|
||||
apiClient.post<Models.AuditLog[]>('/v1/api/governance/auditlog', payload || {}),
|
||||
|
||||
// Instruments
|
||||
createInstrument: (payload: Models.CreateInstrumentRequest) =>
|
||||
apiClient.post<Models.Instrument>('/v1/api/governance/instruments', payload),
|
||||
|
||||
updateInstrument: (payload: Models.UpdateInstrumentRequest) =>
|
||||
apiClient.put<Models.Instrument>('/v1/api/governance/instruments', payload),
|
||||
|
||||
getProgrammeInstruments: (programmeId: string | number) =>
|
||||
apiClient.get<Models.Instrument[]>(`/v1/api/governance/instruments/${hashParam(programmeId)}`),
|
||||
|
||||
// Organisations
|
||||
createOrganisation: (payload: Models.CreateOrganisationRequest) =>
|
||||
apiClient.post<Models.Organisation>('/v1/api/governance/organisations', payload),
|
||||
|
||||
getOrganisations: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Organisation[]>('/v1/api/governance/organisations', { params }),
|
||||
|
||||
updateOrganisation: (payload: Models.UpdateOrganisationRequest) =>
|
||||
apiClient.put<Models.Organisation>('/v1/api/governance/organisations', payload),
|
||||
|
||||
getOrganisation: (organisationId: string | number) =>
|
||||
apiClient.get<Models.Organisation>(`/v1/api/governance/organisations/${hashParam(organisationId)}`),
|
||||
|
||||
getOrganisationPortfolios: (organisationId: string | number) =>
|
||||
apiClient.get<Models.Portfolio[]>(`/v1/api/governance/organisations/${hashParam(organisationId)}/portfolio`),
|
||||
|
||||
createOrganisationPortfolio: (payload: Models.CreatePortfolioRequest) =>
|
||||
apiClient.post<Models.Portfolio>('/v1/api/governance/organisations/portfolio', payload),
|
||||
|
||||
updateOrganisationPortfolio: (payload: Models.UpdatePortfolioRequest) =>
|
||||
apiClient.put<Models.Portfolio>('/v1/api/governance/organisations/portfolio', payload),
|
||||
|
||||
getOrganisationsPortfoliosList: (payload?: any) =>
|
||||
apiClient.post<Models.Portfolio[]>('/v1/api/governance/organisations/portfolios', payload || {}),
|
||||
|
||||
// Portfolios
|
||||
getPortfolio: (portfolioId: string | number) =>
|
||||
apiClient.get<Models.Portfolio>(`/v1/api/governance/portfolios/${hashParam(portfolioId)}`),
|
||||
|
||||
getPortfolioValue: (portfolioId: string | number) =>
|
||||
apiClient.get<{ value: number }>(`/v1/api/governance/portfolios/${hashParam(portfolioId)}/value`),
|
||||
|
||||
getPortfolioProgrammes: (portfolioId: string | number) =>
|
||||
apiClient.get<Models.Programme[]>(`/v1/api/governance/portfolios/${hashParam(portfolioId)}/programmes`),
|
||||
|
||||
// Programmes under governance
|
||||
getProgrammesByPortfolio: (portfolioId: string | number) =>
|
||||
apiClient.get<Models.Programme[]>(`/v1/api/governance/programmes/${hashParam(portfolioId)}`),
|
||||
|
||||
getProgrammeValue: (programmeId: string | number) =>
|
||||
apiClient.get<{ value: number }>(`/v1/api/governance/programmes/${hashParam(programmeId)}/value`),
|
||||
|
||||
// Tenants
|
||||
createTenant: (payload: Models.CreateTenantRequest) =>
|
||||
apiClient.post<Models.Tenant>('/v1/api/governance/tenants', payload),
|
||||
|
||||
getTenants: () =>
|
||||
apiClient.get<Models.Tenant[]>('/v1/api/governance/tenants'),
|
||||
|
||||
updateTenant: (payload: Models.UpdateTenantRequest) =>
|
||||
apiClient.put<Models.Tenant>('/v1/api/governance/tenants', payload),
|
||||
|
||||
getTenant: (tenantId: string | number) =>
|
||||
apiClient.get<Models.Tenant>(`/v1/api/governance/tenants/${hashParam(tenantId)}`),
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Execution Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const executionApi = {
|
||||
// Disbursements
|
||||
getDisbursements: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Disbursement[]>('/v1/api/execution/disbursements', { params }),
|
||||
|
||||
getDisbursement: (disbursementId: string | number) =>
|
||||
apiClient.get<Models.Disbursement>(`/v1/api/execution/disbursements/${hashParam(disbursementId)}`),
|
||||
|
||||
createDisbursement: (payload: Models.CreateDisbursementRequest) =>
|
||||
apiClient.post<Models.Disbursement>('/v1/api/execution/disbursements', payload),
|
||||
|
||||
updateDisbursement: (payload: Models.UpdateDisbursementRequest) =>
|
||||
apiClient.put<Models.Disbursement>('/v1/api/execution/disbursements', payload),
|
||||
|
||||
approveDisbursement: (disbursementId: string | number) =>
|
||||
apiClient.post<any>(`/v1/api/execution/disbursements/${hashParam(disbursementId)}/approve`),
|
||||
|
||||
// Risks
|
||||
getRisks: (params?: { page?: number; pageSize?: number }) =>
|
||||
apiClient.get<Models.Risk[]>('/v1/api/execution/risks', { params }),
|
||||
|
||||
getRisk: (riskId: string | number) =>
|
||||
apiClient.get<Models.Risk>(`/v1/api/execution/risks/${hashParam(riskId)}`),
|
||||
|
||||
createRisk: (payload: Models.CreateRiskRequest) =>
|
||||
apiClient.post<Models.Risk>('/v1/api/execution/risks', payload),
|
||||
|
||||
updateRisk: (payload: Models.UpdateRiskRequest) =>
|
||||
apiClient.put<Models.Risk>('/v1/api/execution/risks', payload),
|
||||
|
||||
// Rules
|
||||
getRule: (ruleId: string | number) =>
|
||||
apiClient.get<Models.Rule>(`/v1/api/execution/rules/${hashParam(ruleId)}`),
|
||||
|
||||
createRule: (payload: Models.CreateRuleRequest) =>
|
||||
apiClient.post<Models.Rule>('/v1/api/execution/rules', payload),
|
||||
|
||||
updateRule: (payload: Models.UpdateRuleRequest) =>
|
||||
apiClient.put<Models.Rule>('/v1/api/execution/rules', payload),
|
||||
|
||||
getProgrammeRules: (payload?: any) =>
|
||||
apiClient.post<Models.Rule[]>('/v1/api/execution/programmes/rules', payload || {}),
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Evidence & M&E Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const evidenceApi = {
|
||||
// Evidence files & records
|
||||
createEvidenceFile: (payload: FormData | any) => {
|
||||
const isFormData = typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||
return apiClient.put<Models.Evidence>('/v1/api/evidence/file', payload, {
|
||||
headers: isFormData ? { 'Content-Type': 'multipart/form-data' } : undefined,
|
||||
});
|
||||
},
|
||||
|
||||
updateEvidence: (payload: Models.UpdateEvidenceRequest) =>
|
||||
apiClient.put<Models.Evidence>('/v1/api/evidence', payload),
|
||||
|
||||
getEvidence: (evidenceId: string | number) =>
|
||||
apiClient.get<Models.Evidence>(`/v1/api/evidence/${hashParam(evidenceId)}`),
|
||||
|
||||
getEvidenceFilesPaged: (page: number, pageSize: number) =>
|
||||
apiClient.get<Models.Evidence[]>(`/v1/api/evidence/files/${page}/${pageSize}`),
|
||||
|
||||
getEvidenceSecureLink: (evidenceId: string | number) =>
|
||||
apiClient.get<{ url: string }>(`/v1/api/evidence/${hashParam(evidenceId)}/securelink`),
|
||||
|
||||
getMilestoneEvidence: (milestoneId: string | number) =>
|
||||
apiClient.get<Models.Evidence[]>(`/v1/api/evidence/milestone/${hashParam(milestoneId)}`),
|
||||
|
||||
getProjectEvidence: (projectId: string | number) =>
|
||||
apiClient.get<Models.Evidence[]>(`/v1/api/evidence/${hashParam(projectId)}`),
|
||||
|
||||
createMilestoneRequest: (payload: Models.CreateMilestoneRequest) =>
|
||||
apiClient.post<Models.Milestone>('/v1/api/evidence/milestones', payload),
|
||||
|
||||
// Indicators
|
||||
createIndicator: (payload: Models.CreateIndicatorRequest) =>
|
||||
apiClient.post<Models.Indicator>('/v1/api/evidence/indicators', payload),
|
||||
|
||||
getIndicator: (indicatorId: string | number) =>
|
||||
apiClient.get<Models.Indicator>(`/v1/api/evidence/indicators/${hashParam(indicatorId)}`),
|
||||
|
||||
getIndicatorsPaged: (page: number, pageSize: number) =>
|
||||
apiClient.get<Models.Indicator[]>(`/v1/api/evidence/indicators/${page}/${pageSize}`),
|
||||
|
||||
recordIndicatorActualAmount: (indicatorId: string | number, actualAmount: number) =>
|
||||
apiClient.post<any>(`/v1/api/evidence/indicators/${hashParam(indicatorId)}/actuals/${actualAmount}`),
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Communications Endpoints
|
||||
// ----------------------------------------------------------------------
|
||||
export const communicationsApi = {
|
||||
sendInvite: (payload: Models.InviteRequest) =>
|
||||
apiClient.post<Models.Invite>('/v1/api/communications/invites', payload),
|
||||
|
||||
getInvitesPaged: (page: number, pageSize: number) =>
|
||||
apiClient.get<Models.Invite[]>(`/v1/api/communications/invites/${page}/${pageSize}`),
|
||||
|
||||
getApplicantInvites: (applicantUserId: string | number) =>
|
||||
apiClient.get<Models.Invite[]>(`/v1/api/communications/invites/${hashParam(applicantUserId)}`),
|
||||
|
||||
getApplicantInvite: (applicantUserId: string | number) =>
|
||||
apiClient.get<Models.Invite>(`/v1/api/communications/invites/applicants/${hashParam(applicantUserId)}`),
|
||||
|
||||
getNotifications: (organisationId: string | number) =>
|
||||
apiClient.get<Models.Notification[]>(`/v1/api/communications/norifications/${hashParam(organisationId)}`),
|
||||
};
|
||||
|
||||
// Consolidated export
|
||||
export const api = {
|
||||
users: usersApi,
|
||||
projects: projectsApi,
|
||||
grants: grantsApi,
|
||||
governance: governanceApi,
|
||||
execution: executionApi,
|
||||
evidence: evidenceApi,
|
||||
communications: communicationsApi,
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ProblemDetails } from './models';
|
||||
|
||||
/**
|
||||
* Extracts comma-delimited error messages from the ProblemDetails `detail` field
|
||||
* or falls back to message/title/string.
|
||||
*/
|
||||
export function extractApiErrors(error: unknown): string[] {
|
||||
if (!error) {
|
||||
return ['An unknown error occurred.'];
|
||||
}
|
||||
|
||||
// Handle Axios / Fetch response error structures
|
||||
const axiosError = error as any;
|
||||
const responseData: ProblemDetails | string | undefined = axiosError?.response?.data || axiosError?.data;
|
||||
|
||||
if (responseData) {
|
||||
if (typeof responseData === 'object') {
|
||||
if (responseData.detail && typeof responseData.detail === 'string') {
|
||||
const parsed = responseData.detail
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item.length > 0);
|
||||
if (parsed.length > 0) return parsed;
|
||||
}
|
||||
if (responseData.title && typeof responseData.title === 'string') {
|
||||
return [responseData.title.trim()];
|
||||
}
|
||||
if ((responseData as any).message && typeof (responseData as any).message === 'string') {
|
||||
return [(responseData as any).message.trim()];
|
||||
}
|
||||
} else if (typeof responseData === 'string' && responseData.trim()) {
|
||||
return [responseData.trim()];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
if ('detail' in error && typeof (error as any).detail === 'string') {
|
||||
const parsed = (error as any).detail
|
||||
.split(',')
|
||||
.map((item: string) => item.trim())
|
||||
.filter((item: string) => item.length > 0);
|
||||
if (parsed.length > 0) return parsed;
|
||||
}
|
||||
if ('message' in error && typeof (error as any).message === 'string') {
|
||||
return [(error as any).message.trim()];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof error === 'string') {
|
||||
return [error.trim()];
|
||||
}
|
||||
|
||||
return ['An unexpected error occurred while communicating with the server.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted single message from the extracted comma-delimited errors.
|
||||
*/
|
||||
export function formatApiErrorMessage(error: unknown): string {
|
||||
const errors = extractApiErrors(error);
|
||||
return errors.join('\n• ');
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './models';
|
||||
export * from './errorHelper';
|
||||
export * from './endpoints';
|
||||
@@ -0,0 +1,692 @@
|
||||
/**
|
||||
* OpenAPI TypeScript Models for PostFundManagement.Api v1
|
||||
* Synchronized from https://api.ifri.khongisa.co.za/openapi/v1/
|
||||
*/
|
||||
|
||||
export interface ProblemDetails {
|
||||
type?: string | null;
|
||||
title?: string | null;
|
||||
status?: number | string | null;
|
||||
detail?: string | null;
|
||||
instance?: string | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type ActivityStatus = 0 | 1 | 2 | 3 | 4 | 'Planned' | 'InProgress' | 'Completed' | 'Delayed' | 'Cancelled' | number | string;
|
||||
export type ApprovalStatus = 0 | 1 | 2 | 3 | 'Draft' | 'PendingApproval' | 'Approved' | 'Rejected' | number | string;
|
||||
export type ContractStatus = 0 | 1 | 2 | 3 | 4 | 'Draft' | 'Active' | 'Executed' | 'Terminated' | 'Expired' | number | string;
|
||||
export type EvidenceType = 0 | 1 | 2 | 3 | 4 | 5 | 'Document' | 'Invoice' | 'Receipt' | 'SitePhoto' | 'Report' | 'Other' | number | string;
|
||||
export type IndustrySector = number | string;
|
||||
export type NotificationPlatform = 0 | 1 | 2 | 'Email' | 'SMS' | 'InApp' | number | string;
|
||||
export type NotificationStatus = 0 | 1 | 2 | 3 | 'Queued' | 'Sent' | 'Failed' | 'Delivered' | number | string;
|
||||
export type OrganisationType = 0 | 1 | 2 | 3 | 'Grantee' | 'Funder' | 'Partner' | 'Auditor' | number | string;
|
||||
export type Priority = 0 | 1 | 2 | 3 | 'Low' | 'Medium' | 'High' | 'Critical' | number | string;
|
||||
export type ProjectType = 0 | 1 | 2 | 3 | 'Grant' | 'Loan' | 'Equity' | 'Hybrid' | number | string;
|
||||
export type RiskImpact = 0 | 1 | 2 | 3 | 'Low' | 'Medium' | 'High' | 'Critical' | number | string;
|
||||
export type RiskLikelihood = 0 | 1 | 2 | 3 | 'Rare' | 'Unlikely' | 'Possible' | 'Likely' | number | string;
|
||||
export type UnitOfMeasure = number | string;
|
||||
|
||||
export interface Pagination {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
totalRecords?: number;
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: string;
|
||||
sub?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
role?: string;
|
||||
tenant?: string;
|
||||
tenant_id?: number | string;
|
||||
status?: string | number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface UserFilterRequest {
|
||||
email?: string;
|
||||
name?: string;
|
||||
role?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
status?: ActivityStatus;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
progressPercentage?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateActivityRequest {
|
||||
projectId: string | number;
|
||||
title: string;
|
||||
description?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
status?: ActivityStatus;
|
||||
}
|
||||
|
||||
export interface Beneficiary {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
name?: string;
|
||||
identificationNumber?: string;
|
||||
contactNumber?: string;
|
||||
email?: string;
|
||||
location?: string;
|
||||
gender?: string;
|
||||
age?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateBeneficiaryRequest {
|
||||
projectId: string | number;
|
||||
name: string;
|
||||
identificationNumber?: string;
|
||||
contactNumber?: string;
|
||||
email?: string;
|
||||
location?: string;
|
||||
gender?: string;
|
||||
age?: number;
|
||||
}
|
||||
|
||||
export interface UpdateBeneficiaryRequest {
|
||||
id: string | number;
|
||||
projectId?: string | number;
|
||||
name?: string;
|
||||
identificationNumber?: string;
|
||||
contactNumber?: string;
|
||||
email?: string;
|
||||
location?: string;
|
||||
gender?: string;
|
||||
age?: number;
|
||||
}
|
||||
|
||||
export interface Outcome {
|
||||
id?: string | number;
|
||||
beneficiaryId?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
targetMetric?: string;
|
||||
achievedMetric?: string;
|
||||
dateRecorded?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateOutcomeRequest {
|
||||
beneficiaryId?: string | number;
|
||||
projectId?: string | number;
|
||||
title: string;
|
||||
description?: string;
|
||||
targetMetric?: string;
|
||||
achievedMetric?: string;
|
||||
dateRecorded?: string;
|
||||
}
|
||||
|
||||
export interface UpdateOutcomeRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
targetMetric?: string;
|
||||
achievedMetric?: string;
|
||||
}
|
||||
|
||||
export interface ChangeRequest {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
reason?: string;
|
||||
proposedChanges?: string;
|
||||
requestedBy?: string;
|
||||
status?: ApprovalStatus;
|
||||
requestDate?: string;
|
||||
approvalDate?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateChangeRequest {
|
||||
projectId: string | number;
|
||||
reason: string;
|
||||
proposedChanges: string;
|
||||
}
|
||||
|
||||
export interface UpdateChangeRequest {
|
||||
id: string | number;
|
||||
reason?: string;
|
||||
proposedChanges?: string;
|
||||
status?: ApprovalStatus;
|
||||
}
|
||||
|
||||
export interface ComplianceItem {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
isCompliant?: boolean;
|
||||
dueDate?: string;
|
||||
verifiedAt?: string;
|
||||
verifiedBy?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateComplianceItemRequest {
|
||||
projectId: string | number;
|
||||
title: string;
|
||||
description?: string;
|
||||
dueDate?: string;
|
||||
}
|
||||
|
||||
export interface UpdateComplianceItemRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
isCompliant?: boolean;
|
||||
dueDate?: string;
|
||||
}
|
||||
|
||||
export interface Issue {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
priority?: Priority;
|
||||
status?: string;
|
||||
reportedBy?: string;
|
||||
resolvedAt?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateIssueRequest {
|
||||
projectId: string | number;
|
||||
title: string;
|
||||
description?: string;
|
||||
priority?: Priority;
|
||||
}
|
||||
|
||||
export interface UpdateIssueRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
priority?: Priority;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface Milestone {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
dueDate?: string;
|
||||
amount?: number;
|
||||
status?: ApprovalStatus;
|
||||
completionPercentage?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateMilestoneRequest {
|
||||
projectId: string | number;
|
||||
title: string;
|
||||
description?: string;
|
||||
dueDate?: string;
|
||||
amount?: number;
|
||||
}
|
||||
|
||||
export interface UpdateMilestoneRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
dueDate?: string;
|
||||
amount?: number;
|
||||
status?: ApprovalStatus;
|
||||
completionPercentage?: number;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id?: string | number;
|
||||
programmeId?: string | number;
|
||||
organisationId?: string | number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
description?: string;
|
||||
type?: ProjectType;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
budget?: number;
|
||||
status?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateProjectRequest {
|
||||
programmeId: string | number;
|
||||
organisationId: string | number;
|
||||
name: string;
|
||||
code?: string;
|
||||
description?: string;
|
||||
type?: ProjectType;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
budget?: number;
|
||||
}
|
||||
|
||||
export interface UpdateProjectRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
budget?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
export interface UpdateProjectStatusRequest {
|
||||
projectId: string | number;
|
||||
status: string | number;
|
||||
}
|
||||
|
||||
export interface SiteVisit {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
visitDate?: string;
|
||||
visitorName?: string;
|
||||
findings?: string;
|
||||
recommendations?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateSiteVisitRequest {
|
||||
projectId: string | number;
|
||||
visitDate: string;
|
||||
visitorName: string;
|
||||
findings?: string;
|
||||
recommendations?: string;
|
||||
}
|
||||
|
||||
export interface UpdateSiteVisitRequest {
|
||||
id: string | number;
|
||||
visitDate?: string;
|
||||
visitorName?: string;
|
||||
findings?: string;
|
||||
recommendations?: string;
|
||||
}
|
||||
|
||||
export interface Award {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
amount?: number;
|
||||
awardDate?: string;
|
||||
conditions?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateAwardRequest {
|
||||
projectId: string | number;
|
||||
amount: number;
|
||||
awardDate?: string;
|
||||
conditions?: string;
|
||||
}
|
||||
|
||||
export interface Budget {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
category?: string;
|
||||
allocatedAmount?: number;
|
||||
spentAmount?: number;
|
||||
notes?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateBudgetRequest {
|
||||
projectId: string | number;
|
||||
category: string;
|
||||
allocatedAmount: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface UpdateBudgetRequest {
|
||||
id: string | number;
|
||||
category?: string;
|
||||
allocatedAmount?: number;
|
||||
spentAmount?: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface Contract {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
contractNumber?: string;
|
||||
status?: ContractStatus;
|
||||
signedDate?: string;
|
||||
expiryDate?: string;
|
||||
value?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateContractRequest {
|
||||
projectId: string | number;
|
||||
title: string;
|
||||
contractNumber?: string;
|
||||
signedDate?: string;
|
||||
expiryDate?: string;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface UpdateContractRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
status?: ContractStatus;
|
||||
expiryDate?: string;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface ExecuteContractRequest {
|
||||
contractId: string | number;
|
||||
executionDate: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface Instrument {
|
||||
id?: string | number;
|
||||
programmeId?: string | number;
|
||||
name?: string;
|
||||
type?: string;
|
||||
description?: string;
|
||||
terms?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateInstrumentRequest {
|
||||
programmeId: string | number;
|
||||
name: string;
|
||||
type?: string;
|
||||
description?: string;
|
||||
terms?: string;
|
||||
}
|
||||
|
||||
export interface UpdateInstrumentRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
type?: string;
|
||||
description?: string;
|
||||
terms?: string;
|
||||
}
|
||||
|
||||
export interface Organisation {
|
||||
id?: string | number;
|
||||
name?: string;
|
||||
registrationNumber?: string;
|
||||
type?: OrganisationType;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
address?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateOrganisationRequest {
|
||||
name: string;
|
||||
registrationNumber?: string;
|
||||
type?: OrganisationType;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface UpdateOrganisationRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
registrationNumber?: string;
|
||||
type?: OrganisationType;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface Portfolio {
|
||||
id?: string | number;
|
||||
organisationId?: string | number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
totalValue?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreatePortfolioRequest {
|
||||
organisationId: string | number;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdatePortfolioRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Programme {
|
||||
id?: string | number;
|
||||
portfolioId?: string | number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
budget?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateProgrammeRequest {
|
||||
portfolioId: string | number;
|
||||
name: string;
|
||||
description?: string;
|
||||
budget?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
export interface UpdateProgrammeRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
budget?: number;
|
||||
}
|
||||
|
||||
export interface Tenant {
|
||||
id?: string | number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
isActive?: boolean;
|
||||
createdDate?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateTenantRequest {
|
||||
name: string;
|
||||
code?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateTenantRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface Dashboard {
|
||||
totalProjects?: number;
|
||||
totalCommittedCapital?: number;
|
||||
totalDisbursedCapital?: number;
|
||||
activeMilestonesCount?: number;
|
||||
complianceRate?: number;
|
||||
recentActivities?: any[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface AuditLog {
|
||||
id?: string | number;
|
||||
entityName?: string;
|
||||
entityId?: string | number;
|
||||
action?: string;
|
||||
timestamp?: string;
|
||||
performedBy?: string;
|
||||
details?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface Disbursement {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
milestoneId?: string | number;
|
||||
amount?: number;
|
||||
disbursementDate?: string;
|
||||
status?: ApprovalStatus;
|
||||
referenceNumber?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateDisbursementRequest {
|
||||
projectId: string | number;
|
||||
milestoneId?: string | number;
|
||||
amount: number;
|
||||
referenceNumber?: string;
|
||||
}
|
||||
|
||||
export interface UpdateDisbursementRequest {
|
||||
id: string | number;
|
||||
amount?: number;
|
||||
status?: ApprovalStatus;
|
||||
referenceNumber?: string;
|
||||
}
|
||||
|
||||
export interface Risk {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
likelihood?: RiskLikelihood;
|
||||
impact?: RiskImpact;
|
||||
mitigationPlan?: string;
|
||||
status?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateRiskRequest {
|
||||
projectId: string | number;
|
||||
title: string;
|
||||
description?: string;
|
||||
likelihood?: RiskLikelihood;
|
||||
impact?: RiskImpact;
|
||||
mitigationPlan?: string;
|
||||
}
|
||||
|
||||
export interface UpdateRiskRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
likelihood?: RiskLikelihood;
|
||||
impact?: RiskImpact;
|
||||
mitigationPlan?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface Rule {
|
||||
id?: string | number;
|
||||
programmeId?: string | number;
|
||||
name?: string;
|
||||
expression?: string;
|
||||
description?: string;
|
||||
isActive?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateRuleRequest {
|
||||
programmeId: string | number;
|
||||
name: string;
|
||||
expression: string;
|
||||
description?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateRuleRequest {
|
||||
id: string | number;
|
||||
name?: string;
|
||||
expression?: string;
|
||||
description?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface Evidence {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
milestoneId?: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
type?: EvidenceType;
|
||||
fileUrl?: string;
|
||||
fileName?: string;
|
||||
fileSize?: number;
|
||||
uploadedAt?: string;
|
||||
verified?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface UpdateEvidenceRequest {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
type?: EvidenceType;
|
||||
verified?: boolean;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
id?: string | number;
|
||||
projectId?: string | number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
targetValue?: number;
|
||||
actualValue?: number;
|
||||
unit?: UnitOfMeasure;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CreateIndicatorRequest {
|
||||
projectId: string | number;
|
||||
name: string;
|
||||
description?: string;
|
||||
targetValue: number;
|
||||
unit?: UnitOfMeasure;
|
||||
}
|
||||
|
||||
export interface Invite {
|
||||
id?: string | number;
|
||||
email?: string;
|
||||
role?: string;
|
||||
organisationId?: string | number;
|
||||
status?: string;
|
||||
expiresAt?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface InviteRequest {
|
||||
email: string;
|
||||
role?: string;
|
||||
organisationId?: string | number;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id?: string | number;
|
||||
organisationId?: string | number;
|
||||
platform?: NotificationPlatform;
|
||||
status?: NotificationStatus;
|
||||
message?: string;
|
||||
recipient?: string;
|
||||
sentAt?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
Reference in New Issue
Block a user