import type { ApiResponse, CompanyProfileDto, CompanyProfileInput, FileAttachmentDto, GanttLinkDto, GanttTaskDto, LoginRequest, LoginResponse, } from "@mrp/shared"; import type { CrmContactDto, CrmContactInput, CrmContactEntryDto, CrmContactEntryInput, CrmCustomerHierarchyOptionDto, CrmRecordDetailDto, CrmRecordInput, CrmLifecycleStage, CrmRecordStatus, CrmRecordSummaryDto, } from "@mrp/shared/dist/crm/types.js"; import type { InventoryItemDetailDto, InventoryItemInput, InventoryItemOptionDto, InventoryItemStatus, InventoryItemSummaryDto, InventoryTransactionInput, InventoryItemType, WarehouseDetailDto, WarehouseInput, WarehouseLocationOptionDto, WarehouseSummaryDto, } from "@mrp/shared/dist/inventory/types.js"; import type { SalesCustomerOptionDto, SalesDocumentDetailDto, SalesDocumentInput, SalesDocumentStatus, SalesDocumentSummaryDto, } from "@mrp/shared/dist/sales/types.js"; export class ApiError extends Error { constructor(message: string, public readonly code: string) { super(message); } } async function request(input: string, init?: RequestInit, token?: string): Promise { const response = await fetch(input, { ...init, headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(init?.headers ?? {}), }, }); const json = (await response.json()) as ApiResponse; if (!json.ok) { throw new ApiError(json.error.message, json.error.code); } return json.data; } function buildQueryString(params: Record) { 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("/api/v1/auth/login", { method: "POST", body: JSON.stringify(payload), }); }, me(token: string) { return request("/api/v1/auth/me", undefined, token); }, getCompanyProfile(token: string) { return request("/api/v1/company-profile", undefined, token); }, updateCompanyProfile(token: string, payload: CompanyProfileInput) { return request( "/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; 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( `/api/v1/files${buildQueryString({ ownerType, ownerId, })}`, undefined, token ); }, deleteAttachment(token: string, fileId: string) { return request( `/api/v1/files/${fileId}`, { method: "DELETE", }, token ); }, getCustomers( token: string, filters?: { q?: string; status?: CrmRecordStatus; lifecycleStage?: CrmLifecycleStage; state?: string; flag?: "PREFERRED" | "STRATEGIC" | "REQUIRES_APPROVAL" | "BLOCKED"; } ) { return request( `/api/v1/crm/customers${buildQueryString({ q: filters?.q, status: filters?.status, lifecycleStage: filters?.lifecycleStage, state: filters?.state, flag: filters?.flag, })}`, undefined, token ); }, getCustomer(token: string, customerId: string) { return request(`/api/v1/crm/customers/${customerId}`, undefined, token); }, getCustomerHierarchyOptions(token: string, excludeCustomerId?: string) { return request( `/api/v1/crm/customers/hierarchy-options${buildQueryString({ excludeCustomerId, })}`, undefined, token ); }, createCustomer(token: string, payload: CrmRecordInput) { return request( "/api/v1/crm/customers", { method: "POST", body: JSON.stringify(payload), }, token ); }, updateCustomer(token: string, customerId: string, payload: CrmRecordInput) { return request( `/api/v1/crm/customers/${customerId}`, { method: "PUT", body: JSON.stringify(payload), }, token ); }, createCustomerContactEntry(token: string, customerId: string, payload: CrmContactEntryInput) { return request( `/api/v1/crm/customers/${customerId}/contact-history`, { method: "POST", body: JSON.stringify(payload), }, token ); }, createCustomerContact(token: string, customerId: string, payload: CrmContactInput) { return request( `/api/v1/crm/customers/${customerId}/contacts`, { method: "POST", body: JSON.stringify(payload), }, token ); }, getVendors( token: string, filters?: { q?: string; status?: CrmRecordStatus; lifecycleStage?: CrmLifecycleStage; state?: string; flag?: "PREFERRED" | "STRATEGIC" | "REQUIRES_APPROVAL" | "BLOCKED"; } ) { return request( `/api/v1/crm/vendors${buildQueryString({ q: filters?.q, status: filters?.status, lifecycleStage: filters?.lifecycleStage, state: filters?.state, flag: filters?.flag, })}`, undefined, token ); }, getVendor(token: string, vendorId: string) { return request(`/api/v1/crm/vendors/${vendorId}`, undefined, token); }, createVendor(token: string, payload: CrmRecordInput) { return request( "/api/v1/crm/vendors", { method: "POST", body: JSON.stringify(payload), }, token ); }, updateVendor(token: string, vendorId: string, payload: CrmRecordInput) { return request( `/api/v1/crm/vendors/${vendorId}`, { method: "PUT", body: JSON.stringify(payload), }, token ); }, createVendorContactEntry(token: string, vendorId: string, payload: CrmContactEntryInput) { return request( `/api/v1/crm/vendors/${vendorId}/contact-history`, { method: "POST", body: JSON.stringify(payload), }, token ); }, createVendorContact(token: string, vendorId: string, payload: CrmContactInput) { return request( `/api/v1/crm/vendors/${vendorId}/contacts`, { method: "POST", body: JSON.stringify(payload), }, token ); }, getInventoryItems(token: string, filters?: { q?: string; status?: InventoryItemStatus; type?: InventoryItemType }) { return request( `/api/v1/inventory/items${buildQueryString({ q: filters?.q, status: filters?.status, type: filters?.type, })}`, undefined, token ); }, getInventoryItem(token: string, itemId: string) { return request(`/api/v1/inventory/items/${itemId}`, undefined, token); }, getInventoryItemOptions(token: string) { return request("/api/v1/inventory/items/options", undefined, token); }, getWarehouseLocationOptions(token: string) { return request("/api/v1/inventory/locations/options", undefined, token); }, createInventoryItem(token: string, payload: InventoryItemInput) { return request( "/api/v1/inventory/items", { method: "POST", body: JSON.stringify(payload), }, token ); }, updateInventoryItem(token: string, itemId: string, payload: InventoryItemInput) { return request( `/api/v1/inventory/items/${itemId}`, { method: "PUT", body: JSON.stringify(payload), }, token ); }, createInventoryTransaction(token: string, itemId: string, payload: InventoryTransactionInput) { return request( `/api/v1/inventory/items/${itemId}/transactions`, { method: "POST", body: JSON.stringify(payload), }, token ); }, getWarehouses(token: string) { return request("/api/v1/inventory/warehouses", undefined, token); }, getWarehouse(token: string, warehouseId: string) { return request(`/api/v1/inventory/warehouses/${warehouseId}`, undefined, token); }, createWarehouse(token: string, payload: WarehouseInput) { return request( "/api/v1/inventory/warehouses", { method: "POST", body: JSON.stringify(payload), }, token ); }, updateWarehouse(token: string, warehouseId: string, payload: WarehouseInput) { return request( `/api/v1/inventory/warehouses/${warehouseId}`, { method: "PUT", body: JSON.stringify(payload), }, token ); }, getGanttDemo(token: string) { return request<{ tasks: GanttTaskDto[]; links: GanttLinkDto[] }>("/api/v1/gantt/demo", undefined, token); }, getSalesCustomers(token: string) { return request("/api/v1/sales/customers/options", undefined, token); }, getQuotes(token: string, filters?: { q?: string; status?: SalesDocumentStatus }) { return request( `/api/v1/sales/quotes${buildQueryString({ q: filters?.q, status: filters?.status, })}`, undefined, token ); }, getQuote(token: string, quoteId: string) { return request(`/api/v1/sales/quotes/${quoteId}`, undefined, token); }, createQuote(token: string, payload: SalesDocumentInput) { return request("/api/v1/sales/quotes", { method: "POST", body: JSON.stringify(payload) }, token); }, updateQuote(token: string, quoteId: string, payload: SalesDocumentInput) { return request(`/api/v1/sales/quotes/${quoteId}`, { method: "PUT", body: JSON.stringify(payload) }, token); }, getSalesOrders(token: string, filters?: { q?: string; status?: SalesDocumentStatus }) { return request( `/api/v1/sales/orders${buildQueryString({ q: filters?.q, status: filters?.status, })}`, undefined, token ); }, getSalesOrder(token: string, orderId: string) { return request(`/api/v1/sales/orders/${orderId}`, undefined, token); }, createSalesOrder(token: string, payload: SalesDocumentInput) { return request("/api/v1/sales/orders", { method: "POST", body: JSON.stringify(payload) }, token); }, updateSalesOrder(token: string, orderId: string, payload: SalesDocumentInput) { return request(`/api/v1/sales/orders/${orderId}`, { method: "PUT", body: JSON.stringify(payload) }, 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(); }, };