shoc-frontend-new/src/domain/vendors/api/vendors-api.ts
Alexandre Brandizzi 30c43a4d86
SH-281: group vendor directory rows by company (#188)
* fix(vendors): add accessible trade overflow tooltip

* fix(vendors): add accessible trade overflow tooltip

* refactor(vendors): satisfy row maintainability gate

* feat(vendors): group directory rows by company

* chore(vendors): trigger SH-281 review checks

* test(vendors): align company directory visuals

* test(vendors): complete company visual baselines

* fix(vendors): keep empty states in viewport

* test(vendors): align centered empty baseline

* fix(vendors): use grouped directory endpoint

* test(vendors): follow grouped directory endpoint

* test(vendors): refresh combined visual snapshots

---------

Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-09-16 14:40:24 -03:00

149 lines
5.2 KiB
TypeScript

import { API_PATHS } from "@/api/api-paths";
import { apiDelete, apiGet, apiPost, apiPut } from "@/api/api";
import { handleApiResponse, handlePaginatedResponse } from "@/api/handle-api-response";
import {
mapVendor,
mapVendorDeactivationImpact,
mapVendorFacets,
mapVendorListItems,
mapVendorPortalToken,
mapVendorToBackend,
mapVendorUpdateToBackend,
} from "@/domain/vendors/mappers/vendor-mapper";
import type {
Vendor,
VendorDeactivationImpact,
VendorFacets,
VendorPortalToken,
VendorsListParams,
VendorsListResult,
} from "@/domain/vendors/types/vendor";
function pushRepeated(
searchParams: Record<string, string | number>,
key: string,
values: string[] | undefined,
): void {
if (!values || values.length === 0) return;
values.forEach((value, index) => {
if (!value) return;
searchParams[`${key}[${index}]`] = value;
});
}
export interface VendorFacetsParams {
isActive?: boolean;
}
function buildFacetsParams(params?: VendorFacetsParams): Record<string, string> {
const searchParams: Record<string, string> = {};
if (params?.isActive !== undefined) {
searchParams.isActive = String(params.isActive);
}
return searchParams;
}
function buildListParams(params: VendorsListParams): Record<string, string | number> {
const searchParams: Record<string, string | number> = {
search: params.search ?? "",
page: params.page ?? 1,
pageSize: params.pageSize ?? 12,
};
if (params.isActive !== undefined) {
searchParams.isActive = String(params.isActive);
}
pushRepeated(searchParams, "companies", params.companies);
pushRepeated(searchParams, "trades", params.trades);
pushRepeated(searchParams, "locations", params.locations);
pushRepeated(searchParams, "jobBuckets", params.jobBuckets);
return searchParams;
}
export const vendorsApi = {
getList: async (params: VendorsListParams = {}): Promise<VendorsListResult> => {
const data = await apiGet<unknown>(API_PATHS.vendor.getList, {
searchParams: buildListParams(params),
});
const paginated = handlePaginatedResponse<unknown>(data, {
page: params.page,
pageSize: params.pageSize,
});
return {
items: mapVendorListItems(paginated.items),
totalCount: paginated.totalCount,
page: paginated.page,
pageSize: paginated.pageSize,
};
},
// SH-281: grouped company rows come from the dedicated directory endpoint;
// Vendor/GetVendorList stays on the legacy technician-level contract.
getDirectoryList: async (params: VendorsListParams = {}): Promise<VendorsListResult> => {
const data = await apiGet<unknown>(API_PATHS.vendor.getDirectoryList, {
searchParams: buildListParams(params),
});
const paginated = handlePaginatedResponse<unknown>(data, {
page: params.page,
pageSize: params.pageSize,
});
return {
items: mapVendorListItems(paginated.items),
totalCount: paginated.totalCount,
page: paginated.page,
pageSize: paginated.pageSize,
};
},
getFacets: async (params?: VendorFacetsParams): Promise<VendorFacets> => {
const data = await apiGet<unknown>(API_PATHS.vendor.facets, {
searchParams: buildFacetsParams(params),
});
return mapVendorFacets(handleApiResponse(data));
},
getById: async (id: string | number): Promise<Vendor> => {
const data = await apiGet<unknown>(`${API_PATHS.rest.vendors}/${id}`);
return mapVendor(handleApiResponse(data));
},
create: async (payload: unknown): Promise<Vendor> => {
const data = await apiPost<unknown>(API_PATHS.rest.vendors, mapVendorToBackend(payload));
return mapVendor(handleApiResponse(data));
},
update: async (id: string | number, payload: unknown): Promise<Vendor> => {
const data = await apiPut<unknown>(
`${API_PATHS.rest.vendors}/${id}`,
mapVendorUpdateToBackend(payload),
);
return mapVendor(handleApiResponse(data));
},
// SH-254: confirmOpenWorkOrders tells the API the caller has been shown the
// vendor's open work orders and chose to proceed. Without it the API still blocks.
delete: async (id: string | number, confirmOpenWorkOrders = false): Promise<void> => {
const suffix = confirmOpenWorkOrders ? "?confirmOpenWorkOrders=true" : "";
await apiDelete(`${API_PATHS.rest.vendors}/${id}${suffix}`);
},
getDeactivationImpact: async (id: string | number): Promise<VendorDeactivationImpact> => {
const data = await apiGet<unknown>(
`${API_PATHS.rest.vendors}/${id}/${API_PATHS.vendor.deactivationImpact}`,
);
return mapVendorDeactivationImpact(handleApiResponse(data));
},
getPortalToken: async (id: string | number): Promise<VendorPortalToken | null> => {
const data = await apiGet<unknown>(`${API_PATHS.rest.vendors}/${id}/portal-token`);
return mapVendorPortalToken(handleApiResponse(data));
},
rotatePortalToken: async (id: string | number): Promise<VendorPortalToken | null> => {
const data = await apiPost<unknown>(`${API_PATHS.rest.vendors}/${id}/portal-token/rotate`);
return mapVendorPortalToken(handleApiResponse(data));
},
revokePortalToken: async (id: string | number): Promise<void> => {
await apiPost<unknown>(`${API_PATHS.rest.vendors}/${id}/portal-token/revoke`);
},
};