PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / resources / js / hooks / useTripTargets.ts

useTripTargets.ts in Yatra – Travel Booking & Tour Operator Software 3.0.16, at resources/js/hooks/useTripTargets.ts

153 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useMemo } from "react";
2 import { useQuery } from "@tanstack/react-query";
3 import { __, sprintf } from "../lib/i18n";
4 import { apiClient, apiService } from "../lib/api-client";
5 import type { MultiSelectOption } from "../components/ui/multi-select";
6 import type { FormConditionTargets } from "../components/settings/booking-form-types";
7
8 /**
9 * Trip targeting shared by the booking-form Conditions popup and the email
10 * template overrides: one MultiSelect holds trip types, categories (nested)
11 * and individual trips, encoded as "type:x" / "category:ID" / "trip:ID".
12 * Mirrors the Pro-side matching (trip › category incl. ancestors › trip type).
13 */
14 export type TripTargets = FormConditionTargets;
15
16 // ---- picker: trips / categories / trip types in one MultiSelect -----------
17
18 const TRIP_TYPE_OPTIONS: { value: string; label: string }[] = [
19 { value: "single_day", label: __("Single Day Trip", "yatra") },
20 { value: "multi_day", label: __("Multi-Day Trip", "yatra") },
21 ];
22
23 export const encodeTargets = (t?: FormConditionTargets): string[] => [
24 ...(t?.trip_types ?? []).map((v) => `type:${v}`),
25 ...(t?.categories ?? []).map((id) => `category:${id}`),
26 ...(t?.trips ?? []).map((id) => `trip:${id}`),
27 ];
28
29 export const decodeTargets = (
30 values: (string | number)[],
31 ): FormConditionTargets => {
32 const out: FormConditionTargets = {};
33 values.forEach((raw) => {
34 const [kind, rest] = String(raw).split(":", 2);
35 if (kind === "trip" && Number(rest) > 0) {
36 (out.trips ||= []).push(Number(rest));
37 } else if (kind === "category" && Number(rest) > 0) {
38 (out.categories ||= []).push(Number(rest));
39 } else if (kind === "type" && rest) {
40 (out.trip_types ||= []).push(rest);
41 }
42 });
43 return out;
44 };
45
46 export const hasTargets = (t?: FormConditionTargets): boolean =>
47 !!t &&
48 ((t.trips?.length ?? 0) > 0 ||
49 (t.categories?.length ?? 0) > 0 ||
50 (t.trip_types?.length ?? 0) > 0);
51
52 interface CategoryNode {
53 id: number;
54 name: string;
55 subcategories?: CategoryNode[];
56 }
57
58 const flattenCategories = (
59 nodes: CategoryNode[],
60 depth = 0,
61 ): MultiSelectOption[] =>
62 nodes.flatMap((c) => [
63 {
64 value: `category:${c.id}`,
65 label: `${__("Category", "yatra")}: ${"— ".repeat(depth)}${c.name}`,
66 },
67 ...(Array.isArray(c.subcategories)
68 ? flattenCategories(c.subcategories, depth + 1)
69 : []),
70 ]);
71
72 /** Trips + categories the picker can target; fetched only while `enabled`. */
73 export const useTripTargets = (enabled: boolean) => {
74 const trips = useQuery({
75 queryKey: ["booking-form-conditions", "trips"],
76 queryFn: async () => {
77 const res: any = await apiService.getTrips({
78 per_page: 500,
79 orderby: "title",
80 order: "ASC",
81 });
82 const list = Array.isArray(res?.data)
83 ? res.data
84 : Array.isArray(res)
85 ? res
86 : [];
87 return list
88 .filter((t: any) => t && t.id && t.status !== "trash")
89 .map((t: any) => ({
90 value: `trip:${t.id}`,
91 label: `${__("Trip", "yatra")}: ${t.title || `#${t.id}`}`,
92 })) as MultiSelectOption[];
93 },
94 enabled,
95 staleTime: 5 * 60 * 1000,
96 });
97
98 const categories = useQuery({
99 queryKey: ["booking-form-conditions", "categories"],
100 queryFn: async () => {
101 const res: any = await apiClient.get("/trip-categories", {
102 params: {
103 per_page: 100,
104 hierarchical: true,
105 orderby: "name",
106 order: "ASC",
107 },
108 });
109 const list = Array.isArray(res?.data)
110 ? res.data
111 : Array.isArray(res)
112 ? res
113 : [];
114 return flattenCategories(list as CategoryNode[]);
115 },
116 enabled,
117 staleTime: 5 * 60 * 1000,
118 });
119
120 const options = useMemo<MultiSelectOption[]>(
121 () => [
122 ...TRIP_TYPE_OPTIONS.map((t) => ({
123 value: `type:${t.value}`,
124 label: `${__("Trip type", "yatra")}: ${t.label}`,
125 })),
126 ...(categories.data ?? []),
127 ...(trips.data ?? []),
128 ],
129 [trips.data, categories.data],
130 );
131
132 return { options, isLoading: trips.isLoading || categories.isLoading };
133 };
134
135 /** Human summary of a condition's targets, for the page and the popup header. */
136 export const describeTargets = (
137 t: FormConditionTargets | undefined,
138 options: MultiSelectOption[],
139 ): string => {
140 const names = encodeTargets(t).map((v) => {
141 const opt = options.find((o) => String(o.value) === v);
142 if (opt) return opt.label.replace(/^[^:]+: /, "");
143 const [kind, rest] = v.split(":", 2);
144 if (kind === "type") {
145 return TRIP_TYPE_OPTIONS.find((x) => x.value === rest)?.label || rest;
146 }
147 return kind === "trip"
148 ? sprintf(__("Trip #%s", "yatra"), rest)
149 : sprintf(__("Category #%s", "yatra"), rest);
150 });
151 return names.join(", ");
152 };
153