258 lines
7.0 KiB
TypeScript
258 lines
7.0 KiB
TypeScript
import type {
|
|
ApiResponse,
|
|
CompanyProfileDto,
|
|
CompanyProfileInput,
|
|
FileAttachmentDto,
|
|
GanttLinkDto,
|
|
GanttTaskDto,
|
|
LoginRequest,
|
|
LoginResponse,
|
|
} from "@mrp/shared";
|
|
import type {
|
|
CrmContactDto,
|
|
CrmContactInput,
|
|
CrmContactEntryDto,
|
|
CrmContactEntryInput,
|
|
CrmCustomerHierarchyOptionDto,
|
|
CrmRecordDetailDto,
|
|
CrmRecordInput,
|
|
CrmRecordStatus,
|
|
CrmRecordSummaryDto,
|
|
} from "@mrp/shared/dist/crm/types.js";
|
|
|
|
export class ApiError extends Error {
|
|
constructor(message: string, public readonly code: string) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
async function request<T>(input: string, init?: RequestInit, token?: string): Promise<T> {
|
|
const response = await fetch(input, {
|
|
...init,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...(init?.headers ?? {}),
|
|
},
|
|
});
|
|
|
|
const json = (await response.json()) as ApiResponse<T>;
|
|
if (!json.ok) {
|
|
throw new ApiError(json.error.message, json.error.code);
|
|
}
|
|
|
|
return json.data;
|
|
}
|
|
|
|
function buildQueryString(params: Record<string, string | undefined>) {
|
|
const searchParams = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value) {
|
|
searchParams.set(key, value);
|
|
}
|
|
}
|
|
|
|
const queryString = searchParams.toString();
|
|
return queryString ? `?${queryString}` : "";
|
|
}
|
|
|
|
export const api = {
|
|
login(payload: LoginRequest) {
|
|
return request<LoginResponse>("/api/v1/auth/login", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
},
|
|
me(token: string) {
|
|
return request<LoginResponse["user"]>("/api/v1/auth/me", undefined, token);
|
|
},
|
|
getCompanyProfile(token: string) {
|
|
return request<CompanyProfileDto>("/api/v1/company-profile", undefined, token);
|
|
},
|
|
updateCompanyProfile(token: string, payload: CompanyProfileInput) {
|
|
return request<CompanyProfileDto>(
|
|
"/api/v1/company-profile",
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
async uploadFile(token: string, file: File, ownerType: string, ownerId: string) {
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
formData.append("ownerType", ownerType);
|
|
formData.append("ownerId", ownerId);
|
|
|
|
const response = await fetch("/api/v1/files/upload", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: formData,
|
|
});
|
|
const json = (await response.json()) as ApiResponse<FileAttachmentDto>;
|
|
if (!json.ok) {
|
|
throw new ApiError(json.error.message, json.error.code);
|
|
}
|
|
return json.data;
|
|
},
|
|
async getFileContentBlob(token: string, fileId: string) {
|
|
const response = await fetch(`/api/v1/files/${fileId}/content`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new ApiError("Unable to load file content.", "FILE_CONTENT_FAILED");
|
|
}
|
|
|
|
return response.blob();
|
|
},
|
|
getAttachments(token: string, ownerType: string, ownerId: string) {
|
|
return request<FileAttachmentDto[]>(
|
|
`/api/v1/files${buildQueryString({
|
|
ownerType,
|
|
ownerId,
|
|
})}`,
|
|
undefined,
|
|
token
|
|
);
|
|
},
|
|
getCustomers(token: string, filters?: { q?: string; status?: CrmRecordStatus; state?: string }) {
|
|
return request<CrmRecordSummaryDto[]>(
|
|
`/api/v1/crm/customers${buildQueryString({
|
|
q: filters?.q,
|
|
status: filters?.status,
|
|
state: filters?.state,
|
|
})}`,
|
|
undefined,
|
|
token
|
|
);
|
|
},
|
|
getCustomer(token: string, customerId: string) {
|
|
return request<CrmRecordDetailDto>(`/api/v1/crm/customers/${customerId}`, undefined, token);
|
|
},
|
|
getCustomerHierarchyOptions(token: string, excludeCustomerId?: string) {
|
|
return request<CrmCustomerHierarchyOptionDto[]>(
|
|
`/api/v1/crm/customers/hierarchy-options${buildQueryString({
|
|
excludeCustomerId,
|
|
})}`,
|
|
undefined,
|
|
token
|
|
);
|
|
},
|
|
createCustomer(token: string, payload: CrmRecordInput) {
|
|
return request<CrmRecordDetailDto>(
|
|
"/api/v1/crm/customers",
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
updateCustomer(token: string, customerId: string, payload: CrmRecordInput) {
|
|
return request<CrmRecordDetailDto>(
|
|
`/api/v1/crm/customers/${customerId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
createCustomerContactEntry(token: string, customerId: string, payload: CrmContactEntryInput) {
|
|
return request<CrmContactEntryDto>(
|
|
`/api/v1/crm/customers/${customerId}/contact-history`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
createCustomerContact(token: string, customerId: string, payload: CrmContactInput) {
|
|
return request<CrmContactDto>(
|
|
`/api/v1/crm/customers/${customerId}/contacts`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
getVendors(token: string, filters?: { q?: string; status?: CrmRecordStatus; state?: string }) {
|
|
return request<CrmRecordSummaryDto[]>(
|
|
`/api/v1/crm/vendors${buildQueryString({
|
|
q: filters?.q,
|
|
status: filters?.status,
|
|
state: filters?.state,
|
|
})}`,
|
|
undefined,
|
|
token
|
|
);
|
|
},
|
|
getVendor(token: string, vendorId: string) {
|
|
return request<CrmRecordDetailDto>(`/api/v1/crm/vendors/${vendorId}`, undefined, token);
|
|
},
|
|
createVendor(token: string, payload: CrmRecordInput) {
|
|
return request<CrmRecordDetailDto>(
|
|
"/api/v1/crm/vendors",
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
updateVendor(token: string, vendorId: string, payload: CrmRecordInput) {
|
|
return request<CrmRecordDetailDto>(
|
|
`/api/v1/crm/vendors/${vendorId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
createVendorContactEntry(token: string, vendorId: string, payload: CrmContactEntryInput) {
|
|
return request<CrmContactEntryDto>(
|
|
`/api/v1/crm/vendors/${vendorId}/contact-history`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
createVendorContact(token: string, vendorId: string, payload: CrmContactInput) {
|
|
return request<CrmContactDto>(
|
|
`/api/v1/crm/vendors/${vendorId}/contacts`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
},
|
|
token
|
|
);
|
|
},
|
|
getGanttDemo(token: string) {
|
|
return request<{ tasks: GanttTaskDto[]; links: GanttLinkDto[] }>("/api/v1/gantt/demo", undefined, token);
|
|
},
|
|
async getCompanyProfilePreviewPdf(token: string) {
|
|
const response = await fetch("/api/v1/documents/company-profile-preview.pdf", {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new ApiError("Unable to render company profile preview PDF.", "PDF_PREVIEW_FAILED");
|
|
}
|
|
|
|
return response.blob();
|
|
},
|
|
};
|