Merge branch 'dev'

This commit is contained in:
Marshal
2026-08-13 14:14:31 +00:00
219 changed files with 13585 additions and 5559 deletions

View File

@@ -0,0 +1,50 @@
import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
export type ContactChannel = "email" | "phone";
export interface ChangePasswordPayload {
oldPassword: string;
newPassword: string;
confirmPassword: string;
}
export interface SendContactOtpPayload {
channel: ContactChannel;
/** The NEW email/phone to verify — the OTP is sent here, not to the current one. */
value: string;
}
export interface UpdateContactPayload extends SendContactOtpPayload {
otp: string;
}
export const accountService = {
/** PATCH /auth/change-password — generic IAM route, works for any user type. */
changePassword: async (payload: ChangePasswordPayload): Promise<void> => {
const response = await client.patch("/auth/change-password", payload);
unwrap(response.data);
},
/** POST /me/contact/otp — sends a code to the new email/phone to prove ownership. */
sendContactOtp: async (
payload: SendContactOtpPayload,
): Promise<{ sentTo: string }> => {
const response = await client.post<{ sentTo: string }>(
"/me/contact/otp",
payload,
);
return unwrap(response.data);
},
/** PATCH /me/contact — verifies the OTP and writes the new email/phone. */
updateContact: async (
payload: UpdateContactPayload,
): Promise<{ success: true; value: string }> => {
const response = await client.patch<{ success: true; value: string }>(
"/me/contact",
payload,
);
return unwrap(response.data);
},
};

View File

@@ -142,6 +142,12 @@ import type {
WarehouseZone,
} from "@/types/warehouse";
import { endpoint } from "@/utils/endpoint";
import {
accountService,
type ChangePasswordPayload,
type SendContactOtpPayload,
type UpdateContactPayload,
} from "./account.service";
import {
BookingListFilter,
bookingsService,
@@ -175,7 +181,7 @@ import {
} from "./locomotives.service";
import { overviewService } from "./overview.service";
import { reportsService } from "./reports.service";
import type { ReportQueryInput, ReportResult } from "@/types/reports";
import type { ReportCatalogEntry, ReportRunParams, ReportRunResult } from "@/types/reports";
import {
paymentsService,
type PaginatedPayments,
@@ -2189,6 +2195,29 @@ export const api = {
),
},
account: {
changePassword: endpoint<ChangePasswordPayload, void>(
"me",
"change-password",
(payload) => accountService.changePassword(payload),
),
sendContactOtp: endpoint<SendContactOtpPayload, { sentTo: string }>(
"me",
"send-contact-otp",
(payload) => accountService.sendContactOtp(payload),
),
updateContact: endpoint<
UpdateContactPayload,
{ success: true; value: string }
>(
"me",
"update-contact",
(payload) => accountService.updateContact(payload),
),
},
signatures: {
mySignature: endpoint<void, SavedSignature | null>(
"me",
@@ -3045,7 +3074,10 @@ export const api = {
},
reports: {
run: endpoint<ReportQueryInput, ReportResult>(
catalog: endpoint<void, ReportCatalogEntry[]>("reports", "catalog", () =>
reportsService.catalog(),
),
run: endpoint<ReportRunParams, ReportRunResult>(
"reports",
"run",
(input) => reportsService.run(input),

View File

@@ -16,6 +16,8 @@ export interface BookingListFilter {
tab?: string;
// customerId?: string;
companyId?: string;
/** Bookings drawn down under this contract (contract detail's Shipments tab). */
contractId?: string;
freightType?: string;
/** ONE_TIME | GENERAL_CONTRACT — the booking-kind tab filter. */
bookingType?: string;
@@ -169,6 +171,7 @@ export const bookingsService = {
if (filter.schedulingStatuses) params.schedulingStatuses = filter.schedulingStatuses;
if (filter.assignedToSchedule) params.assignedToSchedule = filter.assignedToSchedule;
if (filter.companyId) params.companyId = filter.companyId;
if (filter.contractId) params.contractId = filter.contractId;
if (filter.freightType) params.freightType = filter.freightType;
if (filter.bookingType) params.bookingType = filter.bookingType;
if (filter.tradeDirection) params.tradeDirection = filter.tradeDirection;

View File

@@ -0,0 +1,31 @@
import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
import type { ApiResponse } from "@/types/apiResponse";
const BASE = "/logo-settings";
/** Company logo used on every generated document. */
export interface LogoSettings {
logoImageUrl: string | null;
updatedById: string | null;
updatedAt: string | null;
}
export const logoSettingsService = {
get: async (): Promise<LogoSettings> => {
const response = await client.get<ApiResponse<LogoSettings>>(BASE);
return unwrap(response.data);
},
set: async (logoImageBase64: string): Promise<LogoSettings> => {
const response = await client.put<ApiResponse<LogoSettings>>(BASE, {
logoImageBase64,
});
return unwrap(response.data);
},
clear: async (): Promise<LogoSettings> => {
const response = await client.delete<ApiResponse<LogoSettings>>(BASE);
return unwrap(response.data);
},
};

View File

@@ -1,14 +1,31 @@
import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
import { URL_CONSTANTS } from "@/constants/URLS";
import type { ReportQueryInput, ReportResult } from "@/types/reports";
import type { ReportCatalogEntry, ReportRunParams, ReportRunResult } from "@/types/reports";
const R = URL_CONSTANTS.REPORTS;
export const reportsService = {
run: async ({ key, ...params }: ReportQueryInput): Promise<ReportResult> => {
const response = await client.get<ReportResult>(
URL_CONSTANTS.REPORTS.RUN(key),
{ params },
);
catalog: async (): Promise<ReportCatalogEntry[]> => {
const response = await client.get(R.CATALOG);
return unwrap(response.data);
},
run: async ({ key, ...params }: ReportRunParams): Promise<ReportRunResult> => {
const response = await client.get(R.RUN(key), { params });
return unwrap(response.data);
},
/** Streams the export file as a blob — caller triggers the browser save. */
download: async (
key: string,
format: "xlsx" | "pdf",
params: Omit<ReportRunParams, "key" | "page" | "pageSize">,
): Promise<Blob> => {
const response = await client.get(R.EXPORT(key), {
params: { ...params, format },
responseType: "blob",
});
return response.data as Blob;
},
};