This commit is contained in:
2026-04-09 15:17:07 +03:00
parent c5f6d2f596
commit 29cc9c6f4e
23 changed files with 420 additions and 190 deletions
+11 -11
View File
@@ -5,8 +5,8 @@ import type { ApiPath, ApiRequestBody } from "../infra/types"
export const PARTS_ROUTES = {
INDEX: "/api/parts",
BY_ID: "/api/parts/{id}",
IMPORT: "/api/import-parts",
EXPORT: "/api/export-parts",
IMPORT: "/api/parts/import",
EXPORT: "/api/parts/export",
TOGGLE_STATUS: "/api/toggle-part-status",
} as const satisfies Record<string, ApiPath>
@@ -15,15 +15,15 @@ export class PartsClient extends CrudClient<
typeof PARTS_ROUTES.BY_ID
> {
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
super(baseUrl, defaultOptions, PARTS_ROUTES.INDEX, PARTS_ROUTES.BY_ID)
}
async import(payload: ApiRequestBody<typeof PARTS_ROUTES.IMPORT, "post">) {
return this.post(PARTS_ROUTES.IMPORT, payload)
}
async export(payload: ApiRequestBody<typeof PARTS_ROUTES.EXPORT, "post">) {
return this.post(PARTS_ROUTES.EXPORT, payload)
super(
baseUrl,
defaultOptions,
PARTS_ROUTES.INDEX,
PARTS_ROUTES.BY_ID,
PARTS_ROUTES.IMPORT,
PARTS_ROUTES.EXPORT,
"POST",
)
}
async toggleStatus(payload: ApiRequestBody<typeof PARTS_ROUTES.TOGGLE_STATUS, "post">) {
+10 -10
View File
@@ -1,6 +1,6 @@
import { CrudClient } from "../infra/crud-client"
import type { ApiClientOptions } from "../infra/client"
import type { ApiPath, ApiRequestBody } from "../infra/types"
import type { ApiPath } from "../infra/types"
export const SERVICE_ROUTES = {
INDEX: "/api/services",
@@ -14,14 +14,14 @@ export class ServicesClient extends CrudClient<
typeof SERVICE_ROUTES.BY_ID
> {
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
super(baseUrl, defaultOptions, SERVICE_ROUTES.INDEX, SERVICE_ROUTES.BY_ID)
}
async import(payload: ApiRequestBody<typeof SERVICE_ROUTES.IMPORT, "post">) {
return this.post(SERVICE_ROUTES.IMPORT, payload)
}
async export(payload: ApiRequestBody<typeof SERVICE_ROUTES.EXPORT, "post">) {
return this.post(SERVICE_ROUTES.EXPORT, payload)
super(
baseUrl,
defaultOptions,
SERVICE_ROUTES.INDEX,
SERVICE_ROUTES.BY_ID,
SERVICE_ROUTES.IMPORT,
SERVICE_ROUTES.EXPORT,
"POST",
)
}
}
+22
View File
@@ -190,6 +190,28 @@ export class ApiClient {
return data
}
protected async fetchBlob(endpoint: string, options?: { method?: "GET" | "POST"; body?: unknown }): Promise<Blob> {
const url = `${this.normalizeBaseUrl(this.baseUrl)}${endpoint}`
const headers = new Headers(this.defaultOptions.headers as Record<string, string>)
const method = options?.method ?? "POST"
const init: RequestInit = { method, headers }
if (options?.body) {
headers.set("Content-Type", "application/json")
init.body = JSON.stringify(options.body)
}
const response = await fetch(url, init)
if (!response.ok) {
const text = await response.text()
const data = text ? JSON.parse(text) : null
throw new ApiError(response.status, response.statusText, endpoint, method.toLowerCase(), data)
}
return response.blob()
}
private toFetchOptions<Path extends ApiPath, Method extends HttpMethod>(
options: ApiRequestOptions<Path, Method>,
): Record<string, unknown> {
+21 -1
View File
@@ -6,6 +6,8 @@ export const DEFAULT_PER_PAGE = 10
type CrudIndexRoute = ApiPathByMethod<"get"> & ApiPathByMethod<"post">
type CrudByIdRoute = ApiPathByMethod<"put"> & ApiPathByMethod<"delete">
type CrudImportRoute = ApiPathByMethod<"post">
type CrudExportRoute = ApiPathByMethod<"get"> | ApiPathByMethod<"post">
export type CrudTypeOverrides = {
listResponse?: unknown
@@ -29,7 +31,10 @@ export abstract class CrudClient<
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions,
public indexRoute?: IndexRoute,
public byIdRoute?: ByIdRoute) {
public byIdRoute?: ByIdRoute,
public importRoute?: CrudImportRoute,
public exportRoute?: CrudExportRoute,
public exportMethod: "GET" | "POST" = "GET") {
super(baseUrl, defaultOptions)
@@ -54,6 +59,21 @@ export abstract class CrudClient<
async destroy(id: string): Promise<Resolve<Overrides, 'destroyResponse', ApiResponse<ByIdRoute, "delete">>> {
return this.delete<ByIdRoute>(this.byIdRoute as ByIdRoute, { params: { id } } as never) as never
}
async importData(file: File): Promise<{ message?: string }> {
const formData = new FormData()
formData.append("file", file)
const route = (this.importRoute ?? `${this.indexRoute}/import`) as string
return this.postFormData(route, formData)
}
async exportData(filters?: Record<string, unknown>): Promise<Blob> {
const route = (this.exportRoute ?? `${this.indexRoute}/export`) as string
if (this.exportMethod === "POST") {
return this.fetchBlob(route, { method: "POST", body: filters ?? {} })
}
return this.fetchBlob(route)
}
}
export type BaseCrudItem = { id: number }