lovUtils.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React, { CSSProperties, useMemo, MouseEvent } from "react";
  14. import Avatar from "@mui/material/Avatar";
  15. import CardHeader from "@mui/material/CardHeader";
  16. import ListItemButton from "@mui/material/ListItemButton";
  17. import ListItemText from "@mui/material/ListItemText";
  18. import ListItemAvatar from "@mui/material/ListItemAvatar";
  19. import Tooltip from "@mui/material/Tooltip";
  20. import { TypographyProps } from "@mui/material";
  21. import { SxProps } from "@mui/system";
  22. import { TaipyActiveProps, TaipyChangeProps, TaipyLabelProps } from "./utils";
  23. import { getInitials } from "../../utils";
  24. import { LovItem } from "../../utils/lov";
  25. import { stringIcon, Icon, IconAvatar, avatarSx } from "../../utils/icon";
  26. export interface SelTreeProps extends LovProps, TaipyLabelProps {
  27. filter?: boolean;
  28. multiple?: boolean;
  29. width?: string | number;
  30. }
  31. export interface LovProps<T = string | string[], U = string> extends TaipyActiveProps, TaipyChangeProps {
  32. defaultLov?: string;
  33. lov?: LoV;
  34. value?: T;
  35. defaultValue?: U;
  36. height?: string | number;
  37. valueById?: boolean;
  38. }
  39. /**
  40. * A LoV (list of value) element.
  41. *
  42. * Each `LoVElt` holds:
  43. *
  44. * - Its identifier as a string;
  45. * - Its label (or icon) as a `stringIcon`;
  46. * - Potential child elements as an array of `LoVElt`s.
  47. */
  48. export type LoVElt = [string, stringIcon, LoVElt[]?];
  49. /**
  50. * A series of LoV elements.
  51. */
  52. export type LoV = LoVElt[];
  53. const getLovItem = (elt: LoVElt | string, tree = false): LovItem => {
  54. const it: LovItem = Array.isArray(elt)
  55. ? {
  56. id: elt[0],
  57. item: elt[1] || elt[0],
  58. }
  59. : { id: "" + elt, item: "" + elt };
  60. if (tree) {
  61. it.children = Array.isArray(elt) && elt.length > 2 ? elt[2]?.map((e) => getLovItem(e, true)) : [];
  62. }
  63. return it;
  64. };
  65. export const paperBaseSx = { width: "100%", mb: 2, display: "grid", gridTemplateRows: "auto 1fr" } as CSSProperties;
  66. /**
  67. * A React hook that returns a LoV list from the LoV default value and the LoV bound value.
  68. * @param lov - The bound lov value.
  69. * @param defaultLov - The JSON-stringified default LoV value.
  70. * @param tree - This flag indicates if the LoV list is a tree or a flat list (default is false).
  71. * @returns A list of LoV items.
  72. */
  73. export const useLovListMemo = (lov: LoV | undefined, defaultLov: string, tree = false): LovItem[] =>
  74. useMemo(() => {
  75. if (lov) {
  76. if (!Array.isArray(lov)) {
  77. console.debug("lov wrong format ", lov);
  78. return [];
  79. }
  80. return lov.map((elt) => getLovItem(elt, tree));
  81. } else if (defaultLov) {
  82. let parsedLov;
  83. try {
  84. parsedLov = JSON.parse(defaultLov);
  85. } catch {
  86. parsedLov = [];
  87. }
  88. return parsedLov.map((elt: LoVElt) => getLovItem(elt, tree));
  89. }
  90. return [];
  91. }, [lov, defaultLov, tree]);
  92. const cardSx = { p: 0 } as CSSProperties;
  93. export const LovImage = ({
  94. item,
  95. disableTypo,
  96. height,
  97. titleTypographyProps,
  98. }: {
  99. item: Icon;
  100. disableTypo?: boolean;
  101. height?: string;
  102. titleTypographyProps?: TypographyProps<"span", { component?: "span" }>;
  103. }) => {
  104. const sx = useMemo(
  105. () => (height ? { height: height, "& .MuiAvatar-img": { objectFit: "contain" } } : undefined) as SxProps,
  106. [height]
  107. );
  108. return (
  109. <CardHeader
  110. sx={cardSx}
  111. avatar={<IconAvatar img={item} sx={sx} />}
  112. title={item.text}
  113. disableTypography={disableTypo}
  114. titleTypographyProps={titleTypographyProps}
  115. />
  116. );
  117. };
  118. export const showItem = (elt: LovItem, searchValue: string) => {
  119. return (
  120. !searchValue ||
  121. ((typeof elt.item === "string" ? (elt.item as string) : (elt.item as Icon).text) || elt.id)
  122. .toLowerCase()
  123. .indexOf(searchValue.toLowerCase()) > -1
  124. );
  125. };
  126. export interface ItemProps {
  127. value: string;
  128. clickHandler: (evt: MouseEvent<HTMLElement>) => void;
  129. selectedValue: string[] | string;
  130. item: stringIcon;
  131. disabled: boolean;
  132. withAvatar?: boolean;
  133. titleTypographyProps?: TypographyProps<"span", { component?: "span" }>;
  134. }
  135. export const SingleItem = ({
  136. value,
  137. clickHandler,
  138. selectedValue,
  139. item,
  140. disabled,
  141. withAvatar = false,
  142. titleTypographyProps,
  143. }: ItemProps) => (
  144. <ListItemButton
  145. onClick={clickHandler}
  146. data-id={value}
  147. selected={Array.isArray(selectedValue) ? selectedValue.indexOf(value) !== -1 : selectedValue === value}
  148. disabled={disabled}
  149. >
  150. {typeof item === "string" ? (
  151. withAvatar ? (
  152. <ListItemAvatar>
  153. <CardHeader
  154. sx={cardSx}
  155. avatar={
  156. <Tooltip title={item}>
  157. <Avatar sx={avatarSx}>{getInitials(item)}</Avatar>
  158. </Tooltip>
  159. }
  160. title={item}
  161. titleTypographyProps={titleTypographyProps}
  162. />
  163. </ListItemAvatar>
  164. ) : (
  165. <ListItemText primary={item} />
  166. )
  167. ) : (
  168. <ListItemAvatar>
  169. <LovImage item={item} titleTypographyProps={titleTypographyProps} />
  170. </ListItemAvatar>
  171. )}
  172. </ListItemButton>
  173. );
  174. export const isLovParent = (lov: LovItem[] | undefined, id: string, childId: string, path: string[] = []): boolean => {
  175. if (!lov) {
  176. return false;
  177. }
  178. for (let i = 0; i < lov.length; i++) {
  179. if (lov[i].id === id && !(lov[i].children || []).length) {
  180. return false;
  181. } else if (lov[i].id === childId) {
  182. return path.includes(id);
  183. } else if (isLovParent(lov[i].children, id, childId, [...path, lov[i].id])) {
  184. return true;
  185. }
  186. }
  187. return false;
  188. };