mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 00:08:18 +00:00
feat: finish company profile in the backoffice
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
||||
import { endpoint } from "@/utils/endpoint";
|
||||
import type { FleetResourceSlug } from "@/pages/fleet/config/resources";
|
||||
import type { BookingDetail } from "@/types/booking";
|
||||
import type {
|
||||
CreateFileUploadFieldDto,
|
||||
CreateFileUploadSettingDto,
|
||||
FileUploadField,
|
||||
FileUploadSetting,
|
||||
UpdateFileUploadFieldDto,
|
||||
UpdateFileUploadSettingDto,
|
||||
} from "@/types/fileUploadSettings";
|
||||
Company,
|
||||
CompanyListFilter,
|
||||
CompanyProfile,
|
||||
CompanyStats,
|
||||
CustomerBooking,
|
||||
CustomerDocument,
|
||||
CustomerPayment,
|
||||
PaginatedCompanies,
|
||||
ProfileStatus,
|
||||
} from "@/types/customer";
|
||||
import {
|
||||
CreateDropdownOptionDto,
|
||||
CreateDropdownSettingDto,
|
||||
@@ -16,72 +20,20 @@ import {
|
||||
UpdateDropdownOptionDto,
|
||||
UpdateDropdownSettingDto,
|
||||
} from "@/types/dropdownSettings";
|
||||
import type {
|
||||
CreateFileUploadFieldDto,
|
||||
CreateFileUploadSettingDto,
|
||||
FileUploadField,
|
||||
FileUploadSetting,
|
||||
UpdateFileUploadFieldDto,
|
||||
UpdateFileUploadSettingDto,
|
||||
} from "@/types/fileUploadSettings";
|
||||
import type { IOverviewDashboard, OverviewRange } from "@/types/overview";
|
||||
import {
|
||||
RuleEngineListResult,
|
||||
RuleEngineRecord,
|
||||
RuleEngineResourceSlug,
|
||||
} from "@/types/rule-engine";
|
||||
import { fileUploadSettingsService } from "./fileUploadSettings.service";
|
||||
import { dropdownSettingsService } from "./dropdownSettings.service";
|
||||
import {
|
||||
ruleEngineService,
|
||||
RuleEngineListParams,
|
||||
} from "./ruleEngine/ruleEngine.service";
|
||||
import {
|
||||
bookingsService,
|
||||
BookingListFilter,
|
||||
type ApproveStepPayload,
|
||||
type PaginatedBookings,
|
||||
type RejectStepPayload,
|
||||
} from "./bookings.service";
|
||||
import type { BookingDetail } from "@/types/booking";
|
||||
import type { IOverviewDashboard, OverviewRange } from "@/types/overview";
|
||||
import { overviewService } from "./overview.service";
|
||||
import {
|
||||
cargoService,
|
||||
type Cargo,
|
||||
type DeliverCargoPayload,
|
||||
} from "./cargoService";
|
||||
import { containerService, type Container } from "./containerService";
|
||||
import { containerTypesService } from "./container-types.service";
|
||||
import {
|
||||
wagonService,
|
||||
type Wagon,
|
||||
type WagonListFilters,
|
||||
} from "./wagon.service";
|
||||
import { wagonTypesService, type WagonType } from "./wagon-types.service";
|
||||
import { trainService, type Train } from "./trains.service";
|
||||
import {
|
||||
locomotivesService,
|
||||
type Locomotive,
|
||||
type SaveLocomotivePayload,
|
||||
} from "./locomotives.service";
|
||||
import { cargoTypesService } from "./cargo-types.service";
|
||||
import {
|
||||
fleetService,
|
||||
type FleetListFilters,
|
||||
type FleetRecord,
|
||||
} from "./fleet/fleet.service";
|
||||
import type { FleetResourceSlug } from "@/pages/fleet/config/resources";
|
||||
import {
|
||||
paymentsService,
|
||||
type PaginatedPayments,
|
||||
type PaymentListFilter,
|
||||
type PaymentSummary,
|
||||
} from "./payments.service";
|
||||
import {
|
||||
signaturesService,
|
||||
type SavedSignature,
|
||||
type SaveSignaturePayload,
|
||||
} from "./signatures.service";
|
||||
import { warehouseService } from "./warehouse.service";
|
||||
import { trainSchedulingService } from "./trainScheduling.service";
|
||||
import {
|
||||
routesService,
|
||||
type RouteRecord,
|
||||
type SaveRoutePayload,
|
||||
type YardRef,
|
||||
} from "./routes.service";
|
||||
import type {
|
||||
AssignBookingsPayload,
|
||||
BatchBoardSchedule,
|
||||
@@ -157,6 +109,66 @@ import type {
|
||||
WarehouseYard,
|
||||
WarehouseZone,
|
||||
} from "@/types/warehouse";
|
||||
import { endpoint } from "@/utils/endpoint";
|
||||
import {
|
||||
BookingListFilter,
|
||||
bookingsService,
|
||||
type ApproveStepPayload,
|
||||
type PaginatedBookings,
|
||||
type RejectStepPayload,
|
||||
} from "./bookings.service";
|
||||
import { cargoTypesService } from "./cargo-types.service";
|
||||
import {
|
||||
cargoService,
|
||||
type Cargo,
|
||||
type DeliverCargoPayload,
|
||||
} from "./cargoService";
|
||||
import { containerTypesService } from "./container-types.service";
|
||||
import { containerService, type Container } from "./containerService";
|
||||
import { customersService } from "./customers.service";
|
||||
import { dropdownSettingsService } from "./dropdownSettings.service";
|
||||
import { fileUploadSettingsService } from "./fileUploadSettings.service";
|
||||
import {
|
||||
fleetService,
|
||||
type FleetListFilters,
|
||||
type FleetRecord,
|
||||
} from "./fleet/fleet.service";
|
||||
import {
|
||||
locomotivesService,
|
||||
type Locomotive,
|
||||
type SaveLocomotivePayload,
|
||||
} from "./locomotives.service";
|
||||
import { overviewService } from "./overview.service";
|
||||
import {
|
||||
paymentsService,
|
||||
type PaginatedPayments,
|
||||
type PaymentListFilter,
|
||||
type PaymentSummary,
|
||||
} from "./payments.service";
|
||||
import {
|
||||
routesService,
|
||||
type RouteRecord,
|
||||
type SaveRoutePayload,
|
||||
type YardRef,
|
||||
} from "./routes.service";
|
||||
import {
|
||||
RuleEngineListParams,
|
||||
ruleEngineService,
|
||||
} from "./ruleEngine/ruleEngine.service";
|
||||
import {
|
||||
signaturesService,
|
||||
type SavedSignature,
|
||||
type SaveSignaturePayload,
|
||||
} from "./signatures.service";
|
||||
import { trainService, type Train } from "./trains.service";
|
||||
import { trainSchedulingService } from "./trainScheduling.service";
|
||||
import { wagonTypesService, type WagonType } from "./wagon-types.service";
|
||||
import {
|
||||
wagonService,
|
||||
type Wagon,
|
||||
type WagonListFilters,
|
||||
} from "./wagon.service";
|
||||
import { warehouseService } from "./warehouse.service";
|
||||
|
||||
/** Query keys for inventory-lifecycle mutations that ripple across views. */
|
||||
const INVENTORY_INVALIDATIONS: ReadonlyArray<readonly unknown[]> = [
|
||||
@@ -1879,6 +1891,64 @@ export const api = {
|
||||
),
|
||||
},
|
||||
|
||||
customers: {
|
||||
stats: endpoint<Record<string, never>, CompanyStats>(
|
||||
"customers",
|
||||
"stats",
|
||||
() => customersService.stats(),
|
||||
() => QUERY_KEYS.CUSTOMERS.stats,
|
||||
),
|
||||
|
||||
list: endpoint<{ filter: CompanyListFilter }, PaginatedCompanies>(
|
||||
"customers",
|
||||
"list",
|
||||
({ filter }) => customersService.list(filter),
|
||||
({ filter }) => QUERY_KEYS.CUSTOMERS.list(filter),
|
||||
),
|
||||
|
||||
getById: endpoint<{ id: string }, Company | undefined>(
|
||||
"customers",
|
||||
"getById",
|
||||
({ id }) => customersService.getById(id),
|
||||
({ id }) => QUERY_KEYS.CUSTOMERS.byId(id),
|
||||
),
|
||||
|
||||
bookings: endpoint<{ id: string }, CustomerBooking[]>(
|
||||
"customers",
|
||||
"bookings",
|
||||
({ id }) => customersService.bookingsFor(id),
|
||||
({ id }) => QUERY_KEYS.CUSTOMERS.bookings(id),
|
||||
),
|
||||
|
||||
documents: endpoint<{ id: string }, CustomerDocument[]>(
|
||||
"customers",
|
||||
"documents",
|
||||
({ id }) => customersService.documentsFor(id),
|
||||
({ id }) => QUERY_KEYS.CUSTOMERS.documents(id),
|
||||
),
|
||||
|
||||
payments: endpoint<{ id: string }, CustomerPayment[]>(
|
||||
"customers",
|
||||
"payments",
|
||||
({ id }) => customersService.paymentsFor(id),
|
||||
({ id }) => QUERY_KEYS.CUSTOMERS.payments(id),
|
||||
),
|
||||
|
||||
setProfileStatus: endpoint<
|
||||
{ profileId: string; status: ProfileStatus },
|
||||
CompanyProfile
|
||||
>(
|
||||
"customers",
|
||||
"setProfileStatus",
|
||||
({ profileId, status }) => customersService.setProfileStatus(profileId, status),
|
||||
undefined,
|
||||
(_input, data) => [
|
||||
QUERY_KEYS.CUSTOMERS.byId(data.companyId),
|
||||
QUERY_KEYS.CUSTOMERS.ROOT,
|
||||
],
|
||||
),
|
||||
},
|
||||
|
||||
overview: {
|
||||
get: endpoint<{ range?: OverviewRange }, IOverviewDashboard>(
|
||||
"overview",
|
||||
@@ -1886,4 +1956,4 @@ export const api = {
|
||||
({ range }) => overviewService.getDashboard(range),
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,72 +1,90 @@
|
||||
/**
|
||||
* Customers service.
|
||||
*
|
||||
* Currently backed by in-memory mock fixtures (`customers.mock.ts`); the public
|
||||
* surface mirrors the other services (e.g. `bookings.service.ts`) — async
|
||||
* methods returning `{ items, total }` / detail objects — so it can be pointed
|
||||
* at the live `/companies` API later without touching the hooks or pages.
|
||||
*/
|
||||
import {
|
||||
getBookingsFor,
|
||||
getCompanyById,
|
||||
getDocumentsFor,
|
||||
getPaymentsFor,
|
||||
MOCK_COMPANIES,
|
||||
} from "@/pages/customers/customers.mock";
|
||||
import { api as apiClient } from "@/auth/http";
|
||||
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||
import type {
|
||||
Company,
|
||||
CompanyListFilter,
|
||||
CompanyProfile,
|
||||
CompanyStats,
|
||||
CustomerBooking,
|
||||
CustomerDocument,
|
||||
CustomerPayment,
|
||||
PaginatedCompanies,
|
||||
ProfileStatus,
|
||||
} from "@/types/customer";
|
||||
|
||||
/** Simulate network latency so loading states are visible during UI work. */
|
||||
const delay = <T>(value: T, ms = 350): Promise<T> =>
|
||||
new Promise((resolve) => setTimeout(() => resolve(value), ms));
|
||||
|
||||
function matchesSearch(company: Company, search: string): boolean {
|
||||
const q = search.trim().toLowerCase();
|
||||
if (!q) return true;
|
||||
return (
|
||||
company.name.toLowerCase().includes(q) ||
|
||||
company.tin.toLowerCase().includes(q) ||
|
||||
company.email?.toLowerCase().includes(q) === true ||
|
||||
company.companyProfiles.some((p) => p.reference.toLowerCase().includes(q))
|
||||
const cleanParams = (params: object) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(params).filter(
|
||||
([, value]) => value !== undefined && value !== "" && value !== null,
|
||||
),
|
||||
);
|
||||
|
||||
/** Lift attributes JSONB into the flat contact/manager fields the UI reads. */
|
||||
function mapCompany(dto: Record<string, unknown>): Company {
|
||||
const attrs = (dto.attributes as Record<string, unknown> | null) ?? {};
|
||||
return {
|
||||
...(dto as unknown as Company),
|
||||
contactPersonName: (attrs.contactPersonName as string | null) ?? null,
|
||||
contactPersonPhone: (attrs.contactPersonPhone as string | null) ?? null,
|
||||
generalManagerName: (attrs.generalManagerName as string | null) ?? null,
|
||||
generalManagerEmail: (attrs.generalManagerEmail as string | null) ?? null,
|
||||
generalManagerPhone: (attrs.generalManagerPhone as string | null) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export const customersService = {
|
||||
stats(): Promise<CompanyStats> {
|
||||
return apiClient
|
||||
.get<CompanyStats>(URL_CONSTANTS.COMPANIES.STATS)
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
list(filter: CompanyListFilter): Promise<PaginatedCompanies> {
|
||||
const { page, pageSize, search = "", type, status } = filter;
|
||||
|
||||
const filtered = MOCK_COMPANIES.filter(
|
||||
(c) =>
|
||||
matchesSearch(c, search) &&
|
||||
(!type || c.type === type) &&
|
||||
(!status || c.status === status),
|
||||
);
|
||||
|
||||
const start = (page - 1) * pageSize;
|
||||
const items = filtered.slice(start, start + pageSize);
|
||||
|
||||
return delay({ items, total: filtered.length });
|
||||
return apiClient
|
||||
.get<{ items: Record<string, unknown>[]; total: number }>(
|
||||
URL_CONSTANTS.COMPANIES.BASE,
|
||||
{ params: cleanParams(filter) },
|
||||
)
|
||||
.then((r) => ({
|
||||
items: r.data.items.map(mapCompany),
|
||||
total: r.data.total,
|
||||
}));
|
||||
},
|
||||
|
||||
getById(id: string): Promise<Company | undefined> {
|
||||
return delay(getCompanyById(id));
|
||||
return apiClient
|
||||
.get<Record<string, unknown>>(URL_CONSTANTS.COMPANIES.BY_ID(id))
|
||||
.then((r) => mapCompany(r.data));
|
||||
},
|
||||
|
||||
bookingsFor(companyId: string): Promise<CustomerBooking[]> {
|
||||
return delay(getBookingsFor(companyId));
|
||||
return apiClient
|
||||
.get<CustomerBooking[]>(
|
||||
URL_CONSTANTS.COMPANIES.BOOKINGS_CUSTOMER_VIEW(companyId),
|
||||
)
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
documentsFor(companyId: string): Promise<CustomerDocument[]> {
|
||||
return delay(getDocumentsFor(companyId));
|
||||
return apiClient
|
||||
.get<CustomerDocument[]>(URL_CONSTANTS.COMPANIES.DOCUMENTS(companyId))
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
paymentsFor(companyId: string): Promise<CustomerPayment[]> {
|
||||
return delay(getPaymentsFor(companyId));
|
||||
return apiClient
|
||||
.get<CustomerPayment[]>(
|
||||
URL_CONSTANTS.COMPANIES.PAYMENTS_CUSTOMER_VIEW(companyId),
|
||||
)
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
setProfileStatus(profileId: string, status: ProfileStatus): Promise<CompanyProfile> {
|
||||
return apiClient
|
||||
.patch<CompanyProfile>(
|
||||
URL_CONSTANTS.COMPANIES.PROFILE_STATUS(profileId),
|
||||
{ status },
|
||||
)
|
||||
.then((r) => r.data);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user