implement booking flow

This commit is contained in:
marshal
2026-06-04 15:16:27 +03:00
parent a5578ce714
commit 125ee18308
89 changed files with 6190 additions and 1724 deletions

View File

@@ -1,41 +1,38 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import { api } from "@/services/api";
import type { RuleEngineListParams } from "@/services/ruleEngine/ruleEngine.service";
import { ruleEngineService, type RuleEngineListParams } from "@/services/ruleEngine/ruleEngine.service";
import { RULE_ENGINE_SELECT_NONE } from "@/pages/ruleEngine/config/resources";
import type {
ApproveRatePayload,
RuleEngineRecord,
RuleEngineResourceSlug,
} from "@/types/rule-engine";
import {
invalidateRuleEngineList,
patchRuleEngineListRecord,
} from "@/utils/queryInvalidation";
const CARGO_TYPE_PARENT_PAGE_SIZE = 500;
const CONTAINER_TYPE_OPTIONS_PAGE_SIZE = 500;
const listKey = (resource: RuleEngineResourceSlug) =>
["rule-engine", resource] as const;
export const useRuleEngineList = (
resource: RuleEngineResourceSlug,
params: RuleEngineListParams,
) =>
useQuery(
api.ruleEngine.list.queryOptions({
input: { resource, params },
}),
);
useQuery({
queryKey: QUERY_KEYS.RULE_ENGINE.list(resource, params),
queryFn: () => ruleEngineService.list(resource, params),
});
export const useCargoTypeParentOptions = (excludeId?: string, enabled = true) =>
useQuery({
queryKey: api.ruleEngine.list.queryKey({
resource: "cargo-types",
params: { page: 1, pageSize: CARGO_TYPE_PARENT_PAGE_SIZE },
}),
queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("cargo-types"),
queryFn: () =>
api.ruleEngine.list.call({
resource: "cargo-types",
params: { page: 1, pageSize: CARGO_TYPE_PARENT_PAGE_SIZE },
ruleEngineService.list<RuleEngineRecord>("cargo-types", {
page: 1,
pageSize: CARGO_TYPE_PARENT_PAGE_SIZE,
}),
enabled,
select: (result) => {
@@ -55,54 +52,90 @@ export const useCargoTypeParentOptions = (excludeId?: string, enabled = true) =>
},
});
export const useContainerTypeOptions = (enabled = true) =>
export function buildContainerTypeSelectOptions(
rows: RuleEngineRecord[],
includeNone: boolean,
): { label: string; value: string }[] {
const options = rows
.filter((row) => row.id)
.map((row) => {
const label = String(row.label ?? "").trim();
const code = String(row.code ?? "").trim();
const size = row.sizeFt ? `${String(row.sizeFt)}ft` : "";
const parts = [label || code || String(row.id), size].filter(Boolean);
return {
label: parts.join(" - "),
value: String(row.id),
};
});
if (!includeNone) return options;
return [{ label: "None", value: RULE_ENGINE_SELECT_NONE }, ...options];
}
export const useContainerTypeOptions = (
includeNone = true,
enabled = true,
) =>
useQuery({
queryKey: [
...QUERY_KEYS.RULE_ENGINE.list("container-types"),
"select-options",
],
queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("container-types", {
includeNone,
}),
queryFn: () =>
ruleEngineService.list<RuleEngineRecord>("container-types", {
page: 1,
pageSize: CONTAINER_TYPE_OPTIONS_PAGE_SIZE,
}),
enabled,
select: (result) => {
const noneOption = { label: "None", value: RULE_ENGINE_SELECT_NONE };
const options = (result.data ?? []).map((row) => {
const label = String(row.label ?? "").trim();
const code = String(row.code ?? "").trim();
const size = row.sizeFt ? `${String(row.sizeFt)}ft` : "";
const parts = [label || code || String(row.id), size].filter(Boolean);
select: (result) =>
buildContainerTypeSelectOptions(result.data ?? [], includeNone),
});
return {
label: parts.join(" - "),
value: String(row.id),
};
});
const LIVE_RATE_PAGE_SIZE = 500;
return [noneOption, ...options];
},
export const useLiveRateOptions = (enabled = true) =>
useQuery({
queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("rates", { status: "LIVE" }),
queryFn: () =>
ruleEngineService.list<RuleEngineRecord>("rates", {
page: 1,
pageSize: LIVE_RATE_PAGE_SIZE,
status: "LIVE",
}),
enabled,
select: (result) =>
(result.data ?? [])
.filter((row) => row.id)
.map((row) => {
const rateType = String(row.rateType ?? "").replace(/_/g, " ");
const currency = String(row.currency ?? "");
const value = row.rateValue != null ? String(row.rateValue) : "";
const unit = row.rateUnit ? String(row.rateUnit).replace(/_/g, " ") : "";
const parts = [rateType, currency, value, unit].filter(Boolean);
return {
label: parts.join(" · "),
value: String(row.id),
};
}),
});
export const useApprovalChain = (enabled: boolean) =>
useQuery(
api.ruleEngine.getApprovalChain.queryOptions({
enabled,
}),
);
useQuery({
queryKey: QUERY_KEYS.RULE_ENGINE.chain,
queryFn: () => ruleEngineService.getApprovalChain(),
enabled,
});
export const useRuleEngineMutations = (resource: RuleEngineResourceSlug) => {
const qc = useQueryClient();
const invalidate = () =>
qc.invalidateQueries({ queryKey: listKey(resource) });
const create = useMutation({
mutationFn: (payload: Record<string, unknown>) =>
api.ruleEngine.create.call({ resource, payload }),
onSuccess: () => {
onSuccess: async (created) => {
toast.success("Created successfully");
invalidate();
patchRuleEngineListRecord(qc, resource, created);
await invalidateRuleEngineList(qc, resource);
},
onError: () => toast.error("Failed to create record"),
});
@@ -115,9 +148,10 @@ export const useRuleEngineMutations = (resource: RuleEngineResourceSlug) => {
id: string;
payload: Record<string, unknown>;
}) => api.ruleEngine.update.call({ resource, id, payload }),
onSuccess: () => {
onSuccess: async (updated) => {
toast.success("Updated successfully");
invalidate();
patchRuleEngineListRecord(qc, resource, updated);
await invalidateRuleEngineList(qc, resource);
},
onError: () => toast.error("Failed to update record"),
});
@@ -125,9 +159,9 @@ export const useRuleEngineMutations = (resource: RuleEngineResourceSlug) => {
const remove = useMutation({
mutationFn: (id: string) =>
api.ruleEngine.remove.call({ resource, id }),
onSuccess: () => {
onSuccess: async () => {
toast.success("Deleted successfully");
invalidate();
await invalidateRuleEngineList(qc, resource);
},
onError: () => toast.error("Failed to delete record"),
});
@@ -137,29 +171,23 @@ export const useRuleEngineMutations = (resource: RuleEngineResourceSlug) => {
export const useRateWorkflow = () => {
const qc = useQueryClient();
const invalidate = () =>
qc.invalidateQueries({ queryKey: listKey("rates") });
const submit = useMutation({
mutationFn: (id: string) => api.ruleEngine.submitRate.call({ id }),
onSuccess: () => {
onSuccess: async (updated) => {
toast.success("Rate submitted for approval");
invalidate();
patchRuleEngineListRecord(qc, "rates", updated);
await invalidateRuleEngineList(qc, "rates");
},
onError: () => toast.error("Failed to submit rate"),
});
const approve = useMutation({
mutationFn: ({
id,
payload,
}: {
id: string;
payload: ApproveRatePayload;
}) => api.ruleEngine.approveRate.call({ id, payload }),
onSuccess: () => {
mutationFn: (id: string) => api.ruleEngine.approveRate.call({ id }),
onSuccess: async (updated) => {
toast.success("Rate approved");
invalidate();
patchRuleEngineListRecord(qc, "rates", updated);
await invalidateRuleEngineList(qc, "rates");
},
onError: () => toast.error("Failed to approve rate"),
});