lovUtils.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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. dropdown?: boolean;
  31. mode?: string;
  32. }
  33. export interface LovProps<T = string | string[], U = string> extends TaipyActiveProps, TaipyChangeProps {
  34. defaultLov?: string;
  35. lov?: LoV;
  36. value?: T;
  37. defaultValue?: U;
  38. height?: string | number;
  39. valueById?: boolean;
  40. }
  41. /**
  42. * A LoV (list of value) element.
  43. *
  44. * Each `LoVElt` holds:
  45. *
  46. * - Its identifier as a string;
  47. * - Its label (or icon) as a `stringIcon`;
  48. * - Potential child elements as an array of `LoVElt`s.
  49. */
  50. export type LoVElt = [string, stringIcon, LoVElt[]?];
  51. /**
  52. * A series of LoV elements.
  53. */
  54. export type LoV = LoVElt[];
  55. const getLovItem = (elt: LoVElt | string, tree = false): LovItem => {
  56. const it: LovItem = Array.isArray(elt)
  57. ? {
  58. id: elt[0],
  59. item: elt[1] || elt[0],
  60. }
  61. : { id: "" + elt, item: "" + elt };
  62. if (tree) {
  63. it.children = Array.isArray(elt) && elt.length > 2 ? elt[2]?.map((e) => getLovItem(e, true)) : [];
  64. }
  65. return it;
  66. };
  67. export const paperBaseSx = { width: "100%", mb: 2, display: "grid", gridTemplateRows: "auto 1fr" } as CSSProperties;
  68. /**
  69. * A React hook that returns a LoV list from the LoV default value and the LoV bound value.
  70. * @param lov - The bound lov value.
  71. * @param defaultLov - The JSON-stringified default LoV value.
  72. * @param tree - This flag indicates if the LoV list is a tree or a flat list (default is false).
  73. * @returns A list of LoV items.
  74. */
  75. export const useLovListMemo = (lov: LoV | undefined, defaultLov: string, tree = false): LovItem[] =>
  76. useMemo(() => {
  77. if (lov) {
  78. if (!Array.isArray(lov)) {
  79. console.debug("lov wrong format ", lov);
  80. return [];
  81. }
  82. return lov.map((elt) => getLovItem(elt, tree));
  83. } else if (defaultLov) {
  84. let parsedLov;
  85. try {
  86. parsedLov = JSON.parse(defaultLov);
  87. } catch {
  88. parsedLov = [];
  89. }
  90. return parsedLov.map((elt: LoVElt) => getLovItem(elt, tree));
  91. }
  92. return [];
  93. }, [lov, defaultLov, tree]);
  94. const cardSx = { p: 0 } as CSSProperties;
  95. export const LovImage = ({
  96. item,
  97. disableTypo,
  98. height,
  99. titleTypographyProps,
  100. }: {
  101. item: Icon;
  102. disableTypo?: boolean;
  103. height?: string;
  104. titleTypographyProps?: TypographyProps<"span", { component?: "span" }>;
  105. }) => {
  106. const sx = useMemo(
  107. () => (height ? { height: height, "& .MuiAvatar-img": { objectFit: "contain" } } : undefined) as SxProps,
  108. [height]
  109. );
  110. return (
  111. <CardHeader
  112. sx={cardSx}
  113. avatar={<IconAvatar img={item} sx={sx} />}
  114. title={item.text}
  115. disableTypography={disableTypo}
  116. titleTypographyProps={titleTypographyProps}
  117. />
  118. );
  119. };
  120. export const showItem = (elt: LovItem, searchValue: string) => {
  121. return (
  122. !searchValue ||
  123. ((typeof elt.item === "string" ? (elt.item as string) : (elt.item as Icon).text) || elt.id)
  124. .toLowerCase()
  125. .indexOf(searchValue.toLowerCase()) > -1
  126. );
  127. };
  128. export interface ItemProps {
  129. value: string;
  130. clickHandler: (evt: MouseEvent<HTMLElement>) => void;
  131. selectedValue: string[] | string;
  132. item: stringIcon;
  133. disabled: boolean;
  134. withAvatar?: boolean;
  135. titleTypographyProps?: TypographyProps<"span", { component?: "span" }>;
  136. }
  137. export const SingleItem = ({
  138. value,
  139. clickHandler,
  140. selectedValue,
  141. item,
  142. disabled,
  143. withAvatar = false,
  144. titleTypographyProps,
  145. }: ItemProps) => (
  146. <ListItemButton
  147. onClick={clickHandler}
  148. data-id={value}
  149. selected={Array.isArray(selectedValue) ? selectedValue.indexOf(value) !== -1 : selectedValue === value}
  150. disabled={disabled}
  151. >
  152. {typeof item === "string" ? (
  153. withAvatar ? (
  154. <ListItemAvatar>
  155. <CardHeader
  156. sx={cardSx}
  157. avatar={
  158. <Tooltip title={item}>
  159. <Avatar sx={avatarSx}>{getInitials(item)}</Avatar>
  160. </Tooltip>
  161. }
  162. title={item}
  163. titleTypographyProps={titleTypographyProps}
  164. />
  165. </ListItemAvatar>
  166. ) : (
  167. <ListItemText primary={item} />
  168. )
  169. ) : (
  170. <ListItemAvatar>
  171. <LovImage item={item} titleTypographyProps={titleTypographyProps} />
  172. </ListItemAvatar>
  173. )}
  174. </ListItemButton>
  175. );
  176. export const isLovParent = (lov: LovItem[] | undefined, id: string, childId: string, path: string[] = []): boolean => {
  177. if (!lov) {
  178. return false;
  179. }
  180. for (let i = 0; i < lov.length; i++) {
  181. if (lov[i].id === id && !(lov[i].children || []).length) {
  182. return false;
  183. } else if (lov[i].id === childId) {
  184. return path.includes(id);
  185. } else if (isLovParent(lov[i].children, id, childId, [...path, lov[i].id])) {
  186. return true;
  187. }
  188. }
  189. return false;
  190. };