swoop-common 2.2.206 → 2.2.208

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { DTOComponentRead, DTORegionRead, DTOTemplateRead } from "../../api/generated/core/exports";
3
3
  import { PartnerSwoop } from "../../api/generated/swoop/exports";
4
+ import { ComponentSystem } from "../consts/system";
4
5
  export interface ComponentListItemProps {
5
6
  component: DTOComponentRead;
6
7
  isSelected: boolean;
@@ -11,6 +12,7 @@ export interface ComponentListItemProps {
11
12
  };
12
13
  onSelect: (component: DTOComponentRead) => void;
13
14
  showDivider: boolean;
15
+ system?: ComponentSystem;
14
16
  }
15
17
  declare const ComponentListItem: React.FC<ComponentListItemProps>;
16
18
  export default ComponentListItem;
@@ -1,13 +1,74 @@
1
- import React from "react";
2
- import { Box, Chip, Divider, ListItem, ListItemButton, ListItemText, Typography, } from "@mui/material";
3
- import { PARTNER_REGION_READABLE } from "../consts/region";
1
+ import React, { useEffect, useState } from "react";
2
+ import { Box, Button, Chip, Divider, ListItem, ListItemButton, ListItemText, Typography, } from "@mui/material";
4
3
  import { getComponentDataByTemplateName, TEMPLATE_NAMES } from "../../lib/config/templateStructure";
5
- const ComponentListItem = ({ component, isSelected, details, onSelect, showDivider, }) => {
6
- var _a;
4
+ import { getComponentsByIds } from "../util/api";
5
+ import { COMPONENT_SYSTEMS } from "../consts/system";
6
+ import { getTemplateChipStyle } from "../consts/templateChipStyles";
7
+ /*
8
+ Content per item type from ticket:
9
+ Package: Trip ID (hide on IB), Start city, Private, Guided
10
+
11
+ Transfer: Private, Guided, Transfer type, Start location (from journey), End location (from journey)
12
+ // lookup journey from component.componentFields.0.data.journey which contains a componentID, e.g. component_5a51acc53ab0b16da19f875c8cc148f2
13
+ // journey will contain a couple more componentFields which are start and end locations. These will need looking up and outputting.
14
+
15
+ Activity: Start location, End location, Type, Guided
16
+
17
+ Cruise activity: Type
18
+
19
+ Ground accommodation: City
20
+
21
+ Ship accommodation: Vessel ID (hide on IB)
22
+
23
+ Private and Guided render as a tick (✓) when true
24
+
25
+ When Private or Guided is false or unpopulated, the field is omitted entirely
26
+ */
27
+ const INLINE_CONTENT_BY_TEMPLATE = {
28
+ [TEMPLATE_NAMES.SHIP_ACCOMMODATION]: {
29
+ render: (component, _lookups, isTemplateEditor) => {
30
+ var _a;
31
+ if (!isTemplateEditor)
32
+ return null;
33
+ const vesselId = (_a = getComponentDataByTemplateName(TEMPLATE_NAMES.SHIP_ACCOMMODATION, component.componentFields)) === null || _a === void 0 ? void 0 : _a.vesselID;
34
+ if (!vesselId)
35
+ return null;
36
+ return (React.createElement(Typography, { variant: "body2", color: "text.secondary" },
37
+ "Vessel ID: ",
38
+ vesselId));
39
+ },
40
+ },
41
+ // plus the rest in from there...
42
+ };
43
+ const getInlineContentDefinition = (templateName) => templateName ? INLINE_CONTENT_BY_TEMPLATE[templateName] : undefined;
44
+ const ComponentListItem = ({ component, isSelected, details, onSelect, showDivider, system, }) => {
45
+ var _a, _b, _c, _d, _e;
7
46
  const activityData = (_a = getComponentDataByTemplateName(TEMPLATE_NAMES.CRUISE_ACTIVITY, component.componentFields)) !== null && _a !== void 0 ? _a : getComponentDataByTemplateName(TEMPLATE_NAMES.ACTIVITY, component.componentFields);
8
47
  const displayName = (activityData === null || activityData === void 0 ? void 0 : activityData.type)
9
48
  ? `${activityData.type} - ${component.name}`
10
49
  : component.name;
50
+ const convertToTitleCase = (str) => {
51
+ return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
52
+ };
53
+ const inlineContentDefinition = getInlineContentDefinition((_b = details.template) === null || _b === void 0 ? void 0 : _b.name);
54
+ const requiredComponentIds = (_d = (_c = inlineContentDefinition === null || inlineContentDefinition === void 0 ? void 0 : inlineContentDefinition.getRequiredComponentIds) === null || _c === void 0 ? void 0 : _c.call(inlineContentDefinition, component)) !== null && _d !== void 0 ? _d : [];
55
+ const requiredComponentIdsKey = requiredComponentIds.join(",");
56
+ const [lookups, setLookups] = useState({});
57
+ const isTemplateEditor = system === COMPONENT_SYSTEMS.TEMPLATE_EDITOR;
58
+ useEffect(() => {
59
+ const missingIds = requiredComponentIds.filter((id) => !lookups[id]);
60
+ if (!missingIds.length)
61
+ return;
62
+ let cancelled = false;
63
+ getComponentsByIds(missingIds).then((fetched) => {
64
+ if (cancelled)
65
+ return;
66
+ setLookups((prev) => (Object.assign(Object.assign({}, prev), Object.fromEntries(fetched.map((c) => [c.id, c])))));
67
+ });
68
+ return () => { cancelled = true; };
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps
70
+ }, [requiredComponentIdsKey]);
71
+ const inlineContent = (_e = inlineContentDefinition === null || inlineContentDefinition === void 0 ? void 0 : inlineContentDefinition.render(component, lookups, isTemplateEditor)) !== null && _e !== void 0 ? _e : null;
11
72
  return (React.createElement(React.Fragment, null,
12
73
  React.createElement(ListItem, { disablePadding: true },
13
74
  React.createElement(ListItemButton, { onClick: () => onSelect(component), selected: isSelected, sx: {
@@ -20,21 +81,23 @@ const ComponentListItem = ({ component, isSelected, details, onSelect, showDivid
20
81
  borderColor: "primary.main",
21
82
  },
22
83
  } },
23
- React.createElement(Box, { width: "100%" },
24
- React.createElement(ListItemText, { primary: React.createElement(Typography, { variant: "subtitle1", fontWeight: "medium" }, displayName), secondary: React.createElement(Box, { mt: 1 },
25
- details.template && (React.createElement(Typography, { variant: "body2", color: "text.secondary" },
26
- "Type: ",
27
- details.template.name)),
28
- details.regions.length > 0 && (React.createElement(Box, { display: "flex", alignItems: "center", gap: 1, mt: 0.5 },
29
- React.createElement(Typography, { variant: "body2", color: "text.secondary" }, "Regions:"),
30
- details.regions.map((region) => (React.createElement(Chip, { key: region.id, label: region.name, size: "small", variant: "outlined" }))))),
31
- details.partners.length > 0 && (React.createElement(Box, { display: "flex", alignItems: "center", gap: 1, mt: 0.5 },
32
- React.createElement(Typography, { variant: "body2", color: "text.secondary" }, "Partners:"),
33
- details.partners.slice(0, 3).map((partner) => (React.createElement(Chip, { key: partner.id, label: `${partner.title} (${PARTNER_REGION_READABLE[(partner.id || "").split("-")[0]]})`, size: "small", variant: "outlined", color: "secondary" }))),
34
- details.partners.length > 3 && (React.createElement(Typography, { variant: "body2", color: "text.secondary" },
35
- "+",
36
- details.partners.length - 3,
37
- " more"))))) })))),
84
+ React.createElement(Box, { width: "100%", display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 2 },
85
+ React.createElement(ListItemText, { primary: React.createElement(Box, { display: "flex", flexDirection: "column", gap: 0.5 },
86
+ details.template && (React.createElement(Box, null,
87
+ React.createElement(Chip, { label: convertToTitleCase(details.template.name), size: "small", sx: Object.assign(Object.assign({}, getTemplateChipStyle(details.template.name)), { fontWeight: 500 }) }))),
88
+ React.createElement(Typography, { variant: "subtitle1", fontWeight: "medium" }, displayName)), secondary: React.createElement(Box, { display: "flex", flexDirection: "column", gap: 0.5, mt: 1 },
89
+ React.createElement(Box, { display: "flex", alignItems: "center", gap: 3, flexWrap: "wrap" },
90
+ details.partners.length > 0 && (React.createElement(Typography, { variant: "body2", color: "text.secondary" },
91
+ "Partner: ",
92
+ details.partners[0].title)),
93
+ (component === null || component === void 0 ? void 0 : component.destination) && (React.createElement(Typography, { variant: "body2", color: "text.secondary" },
94
+ "Destination: ",
95
+ convertToTitleCase(component.destination))),
96
+ details.regions.length > 0 && (React.createElement(Typography, { variant: "body2", color: "text.secondary" },
97
+ "Region: ",
98
+ details.regions[0].name))),
99
+ inlineContent && (React.createElement(Box, { display: "flex", alignItems: "center", gap: 3, flexWrap: "wrap" }, inlineContent))) }),
100
+ isTemplateEditor && (React.createElement(Button, { component: "a", href: `${window.location.origin}/components/${component.id}`, target: "_blank", rel: "noopener noreferrer", variant: "outlined", size: "small", onClick: (e) => e.stopPropagation(), sx: { flexShrink: 0 } }, "View"))))),
38
101
  showDivider && React.createElement(Divider, null)));
39
102
  };
40
103
  export default ComponentListItem;
@@ -1,11 +1,13 @@
1
1
  import React from "react";
2
2
  import { DTOComponentRead } from "../../api/generated/core/exports";
3
+ import { ComponentSystem } from "../consts/system";
3
4
  interface ComponentPickerModalProps {
4
5
  open: boolean;
5
6
  onClose: () => void;
6
7
  onSelect: (component: DTOComponentRead) => void;
7
8
  selectedComponent?: DTOComponentRead | null;
8
9
  parentIdsFilter?: Array<string>;
10
+ system?: ComponentSystem;
9
11
  }
10
- export default function ComponentPickerModal({ open, onClose, onSelect, selectedComponent, parentIdsFilter, }: ComponentPickerModalProps): React.JSX.Element;
12
+ export default function ComponentPickerModal({ open, onClose, onSelect, selectedComponent, parentIdsFilter, system, }: ComponentPickerModalProps): React.JSX.Element;
11
13
  export {};
@@ -15,7 +15,7 @@ import { fetchComponents, getAllPartners } from "../util/api";
15
15
  import { InternalServices } from "../../api/init";
16
16
  import ComponentListItem from "./ComponentListItem";
17
17
  import { TEMPLATE_NAMES, templateStructure, } from "../../lib/config/templateStructure";
18
- export default function ComponentPickerModal({ open, onClose, onSelect, selectedComponent, parentIdsFilter, }) {
18
+ export default function ComponentPickerModal({ open, onClose, onSelect, selectedComponent, parentIdsFilter, system, }) {
19
19
  const [searchTerm, setSearchTerm] = useState("");
20
20
  const [selectedRegion, setSelectedRegion] = useState("");
21
21
  const [selectedPartner, setSelectedPartner] = useState("");
@@ -240,7 +240,7 @@ export default function ComponentPickerModal({ open, onClose, onSelect, selected
240
240
  React.createElement(Typography, { variant: "body1", color: "text.secondary" }, "Loading components..."))) : components.length === 0 ? (React.createElement(Box, { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", py: 4 },
241
241
  React.createElement(FilterIcon, { sx: { fontSize: 48, color: "text.secondary", mb: 2 } }),
242
242
  React.createElement(Typography, { variant: "body1", color: "text.secondary" }, "No components match your search criteria"),
243
- React.createElement(Typography, { variant: "body2", color: "text.secondary" }, "Try adjusting your filters or search term"))) : (components.map((component, index) => (React.createElement(ComponentListItem, { key: component.id, component: component, isSelected: (selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id) === component.id, details: getComponentDetails(component), onSelect: handleSelect, showDivider: index < components.length - 1 }))))),
243
+ React.createElement(Typography, { variant: "body2", color: "text.secondary" }, "Try adjusting your filters or search term"))) : (components.map((component, index) => (React.createElement(ComponentListItem, { key: component.id, component: component, isSelected: (selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id) === component.id, details: getComponentDetails(component), onSelect: handleSelect, showDivider: index < components.length - 1, system: system }))))),
244
244
  components.length > 0 && (React.createElement(Box, { display: "flex", justifyContent: "center", mt: 2 },
245
245
  React.createElement(Pagination, { count: Math.ceil(totalCount / pageSize), page: page, onChange: (_, newPage) => setPage(newPage), color: "primary" })))),
246
246
  React.createElement(DialogActions, null,
@@ -0,0 +1,5 @@
1
+ export declare const COMPONENT_SYSTEMS: {
2
+ readonly TEMPLATE_EDITOR: "template-editor";
3
+ readonly ITINERARY_BUILDER: "itinerary-builder";
4
+ };
5
+ export type ComponentSystem = typeof COMPONENT_SYSTEMS[keyof typeof COMPONENT_SYSTEMS];
@@ -0,0 +1,4 @@
1
+ export const COMPONENT_SYSTEMS = {
2
+ TEMPLATE_EDITOR: "template-editor",
3
+ ITINERARY_BUILDER: "itinerary-builder",
4
+ };
@@ -0,0 +1,8 @@
1
+ export declare const TEMPLATE_CHIP_STYLES: Partial<Record<string, {
2
+ backgroundColor: string;
3
+ color: string;
4
+ }>>;
5
+ export declare const getTemplateChipStyle: (templateName: string) => {
6
+ backgroundColor: string;
7
+ color: string;
8
+ };
@@ -0,0 +1,11 @@
1
+ import { TEMPLATE_NAMES } from "../../lib/config/templateStructure";
2
+ export const TEMPLATE_CHIP_STYLES = {
3
+ [TEMPLATE_NAMES.PACKAGE]: { backgroundColor: "#DCEEFB", color: "#1565C0" }, // blue
4
+ [TEMPLATE_NAMES.PACKAGES]: { backgroundColor: "#DCEEFB", color: "#1565C0" }, // blue
5
+ [TEMPLATE_NAMES.TRANSFER]: { backgroundColor: "#ECE5FF", color: "#6D4AFF" }, // purple
6
+ [TEMPLATE_NAMES.ACTIVITY]: { backgroundColor: "#E8F0D9", color: "#5B7F3D" }, // green
7
+ [TEMPLATE_NAMES.CRUISE_ACTIVITY]: { backgroundColor: "#D7F5EA", color: "#1F9074" }, // different shade of green
8
+ [TEMPLATE_NAMES.GROUND_ACCOMMODATION]: { backgroundColor: "#F7E8C8", color: "#9C6B1F" }, // tan
9
+ [TEMPLATE_NAMES.SHIP_ACCOMMODATION]: { backgroundColor: "#FBDCE9", color: "#B23A6B" }, // pink
10
+ };
11
+ export const getTemplateChipStyle = (templateName) => { var _a; return (_a = TEMPLATE_CHIP_STYLES[templateName]) !== null && _a !== void 0 ? _a : { backgroundColor: "grey.200", color: "text.secondary" }; };
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import { DTOComponentRead } from '../../api/generated/core/exports';
3
+ import { ComponentSystem } from '../consts/system';
3
4
  interface ComponentPickerExports {
4
5
  get: (parentIds?: Array<string>) => Promise<DTOComponentRead>;
5
6
  }
6
7
  interface Props {
7
8
  children?: React.ReactNode;
9
+ system?: ComponentSystem;
8
10
  }
9
11
  export declare const useComponentPicker: () => ComponentPickerExports;
10
12
  export declare const ComponentPickerProvider: React.FC<Props>;
@@ -16,7 +16,7 @@ export const useComponentPicker = () => {
16
16
  throw new Error("useComponentPicker used from outside selector context");
17
17
  return picker;
18
18
  };
19
- export const ComponentPickerProvider = ({ children }) => {
19
+ export const ComponentPickerProvider = ({ children, system }) => {
20
20
  const [open, setOpen] = useState(false);
21
21
  const changeProp = useRef(() => { });
22
22
  const closeProp = useRef(() => { });
@@ -44,6 +44,6 @@ export const ComponentPickerProvider = ({ children }) => {
44
44
  });
45
45
  });
46
46
  return (React.createElement(ComponentSelectorContext.Provider, { value: { get: getSelectedComponent } },
47
- React.createElement(ComponentPickerModal, { parentIdsFilter: parentIds.length ? parentIds : undefined, open: open, onClose: closeProp.current, onSelect: changeProp.current }),
47
+ React.createElement(ComponentPickerModal, { parentIdsFilter: parentIds.length ? parentIds : undefined, open: open, onClose: closeProp.current, onSelect: changeProp.current, system: system }),
48
48
  children));
49
49
  };
@@ -10,3 +10,5 @@ export { MasterSchema, Stage } from "./schema/formSchemaTypes";
10
10
  export { FORM_BUILDER_JSON_SCHEMA } from "./schema/formBuilders/formBuilderJsonSchema";
11
11
  export { FORM_BUILDER_UI_SCHEMA } from "./schema/formBuilders/formBuilderUiSchema";
12
12
  export * from "./contexts/ComponentPickerContext";
13
+ export * from "./consts/system";
14
+ export * from "./consts/templateChipStyles";
@@ -10,3 +10,5 @@ export { Stage } from "./schema/formSchemaTypes";
10
10
  export { FORM_BUILDER_JSON_SCHEMA } from "./schema/formBuilders/formBuilderJsonSchema";
11
11
  export { FORM_BUILDER_UI_SCHEMA } from "./schema/formBuilders/formBuilderUiSchema";
12
12
  export * from "./contexts/ComponentPickerContext";
13
+ export * from "./consts/system";
14
+ export * from "./consts/templateChipStyles";
@@ -6,3 +6,4 @@ export declare const fetchComponents: (filters?: Record<string, string>) => Prom
6
6
  }>;
7
7
  pagination: Pagination;
8
8
  }>;
9
+ export declare const getComponentsByIds: (componentIds: string[]) => Promise<DTOComponentRead[]>;
@@ -50,3 +50,18 @@ export const fetchComponents = (filters) => __awaiter(void 0, void 0, void 0, fu
50
50
  });
51
51
  return { data: latestComponents, pagination: data.pagination };
52
52
  });
53
+ export const getComponentsByIds = (componentIds) => __awaiter(void 0, void 0, void 0, function* () {
54
+ const uniqueIds = Array.from(new Set(componentIds.filter(Boolean)));
55
+ if (!uniqueIds.length)
56
+ return [];
57
+ try {
58
+ const response = yield InternalServices.CoreService.componentBatchGet({
59
+ componentIds: uniqueIds,
60
+ });
61
+ return response.data || [];
62
+ }
63
+ catch (error) {
64
+ console.error("Failed to fetch components by id:", error);
65
+ return [];
66
+ }
67
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swoop-common",
3
- "version": "2.2.206",
3
+ "version": "2.2.208",
4
4
  "main": "dist/api/index.js",
5
5
  "types": "dist/api/index.d.ts",
6
6
  "exports": {