@olenbetong/synergi-react 2.1.2 → 2.3.0

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.
Files changed (197) hide show
  1. package/dist/esm/ob.react.css +1189 -27
  2. package/dist/esm/ob.react.js +45612 -708
  3. package/dist/esm/ob.react.min.css +24 -1
  4. package/dist/esm/ob.react.min.css.map +3 -3
  5. package/dist/esm/ob.react.min.js +253 -1
  6. package/dist/esm/ob.react.min.js.map +4 -4
  7. package/dist/iife/ob.react.css +1189 -27
  8. package/dist/iife/ob.react.js +45625 -720
  9. package/dist/iife/ob.react.min.css +24 -1
  10. package/dist/iife/ob.react.min.css.map +3 -3
  11. package/dist/iife/ob.react.min.js +253 -1
  12. package/dist/iife/ob.react.min.js.map +4 -4
  13. package/dist/styles/styles.css +1 -1
  14. package/dist/styles/styles.css.map +1 -1
  15. package/{src → es/components}/ColorCard/index.css +10 -6
  16. package/es/{DateNavigator → components/DateNavigator}/index.js +2 -0
  17. package/es/components/FilePond.d.ts +44 -0
  18. package/es/components/FilePond.js +205 -0
  19. package/es/{PageBanner → components/PageBanner}/index.js +0 -1
  20. package/es/{Sidebar → components/Sidebar}/index.d.ts +2 -2
  21. package/es/{SplitContainer → components/SplitContainer}/index.js +3 -3
  22. package/es/{ValueToggleGroup → components/ValueToggleGroup}/index.css +18 -16
  23. package/es/{ValueToggleGroup → components/ValueToggleGroup}/index.js +4 -2
  24. package/es/elements/ElementDamage/DamageFormButtons.d.ts +9 -0
  25. package/es/elements/ElementDamage/DamageFormButtons.js +7 -0
  26. package/es/elements/ElementDamage/DescriptionInput.d.ts +14 -0
  27. package/es/elements/ElementDamage/DescriptionInput.js +44 -0
  28. package/es/elements/ElementDamage/DuplicateDamage/DesktopView.d.ts +7 -0
  29. package/es/elements/ElementDamage/DuplicateDamage/DesktopView.js +16 -0
  30. package/es/elements/ElementDamage/DuplicateDamage/DublicateElementDamageDialog.d.ts +10 -0
  31. package/es/elements/ElementDamage/DuplicateDamage/DublicateElementDamageDialog.js +12 -0
  32. package/es/elements/ElementDamage/DuplicateDamage/PhoneView.d.ts +6 -0
  33. package/es/elements/ElementDamage/DuplicateDamage/PhoneView.js +8 -0
  34. package/es/elements/ElementDamage/DuplicateDamage/index.d.ts +36 -0
  35. package/es/elements/ElementDamage/DuplicateDamage/index.js +40 -0
  36. package/es/elements/ElementDamage/ElementNumberTextField.d.ts +10 -0
  37. package/es/elements/ElementDamage/ElementNumberTextField.js +18 -0
  38. package/es/elements/ElementDamage/HiddenDamageFields.d.ts +6 -0
  39. package/es/elements/ElementDamage/HiddenDamageFields.js +4 -0
  40. package/es/elements/ElementDamage/RegisterFix.d.ts +14 -0
  41. package/es/elements/ElementDamage/RegisterFix.js +45 -0
  42. package/es/elements/ElementDamage/SubmitButtons.d.ts +20 -0
  43. package/es/elements/ElementDamage/SubmitButtons.js +7 -0
  44. package/es/elements/ElementDamage/TransportElementSelect.d.ts +7 -0
  45. package/es/elements/ElementDamage/TransportElementSelect.js +16 -0
  46. package/es/elements/ElementDamage/index.css +3 -0
  47. package/es/elements/ElementLookup.d.ts +20 -0
  48. package/es/elements/ElementLookup.js +30 -0
  49. package/es/elements/ElementLookupDialog/BarcodeScanner/DeadIcon.d.ts +2 -0
  50. package/es/elements/ElementLookupDialog/BarcodeScanner/DeadIcon.js +5 -0
  51. package/es/elements/ElementLookupDialog/BarcodeScanner/Error.d.ts +4 -0
  52. package/es/elements/ElementLookupDialog/BarcodeScanner/Error.js +33 -0
  53. package/es/elements/ElementLookupDialog/BarcodeScanner/index.d.ts +10 -0
  54. package/es/elements/ElementLookupDialog/BarcodeScanner/index.js +37 -0
  55. package/es/elements/ElementLookupDialog/BarcodeScanner/useScanner.d.ts +16 -0
  56. package/es/elements/ElementLookupDialog/BarcodeScanner/useScanner.js +135 -0
  57. package/es/elements/ElementLookupDialog/HIDScanner/index.css +17 -0
  58. package/es/elements/ElementLookupDialog/HIDScanner/index.d.ts +4 -0
  59. package/es/elements/ElementLookupDialog/HIDScanner/index.js +36 -0
  60. package/es/elements/ElementLookupDialog/HIDScanner/useHIDScanner.d.ts +1 -0
  61. package/es/elements/ElementLookupDialog/HIDScanner/useHIDScanner.js +27 -0
  62. package/es/elements/ElementLookupDialog/Manual/ManualForm.d.ts +12 -0
  63. package/es/elements/ElementLookupDialog/Manual/ManualForm.js +48 -0
  64. package/es/elements/ElementLookupDialog/getElement.d.ts +23 -0
  65. package/es/elements/ElementLookupDialog/getElement.js +55 -0
  66. package/es/elements/ElementLookupDialog/index.d.ts +24 -0
  67. package/es/elements/ElementLookupDialog/index.js +97 -0
  68. package/es/elements/ElementLookupDialog/utils/Giai.d.ts +52 -0
  69. package/es/elements/ElementLookupDialog/utils/Giai.js +158 -0
  70. package/es/elements/ElementLookupDialog/utils/beep.d.ts +10 -0
  71. package/es/elements/ElementLookupDialog/utils/beep.js +20 -0
  72. package/es/elements/ProjectSelect.d.ts +23 -0
  73. package/es/elements/ProjectSelect.js +42 -0
  74. package/es/elements/data/dsAttachments.d.ts +15 -0
  75. package/es/elements/data/dsAttachments.js +85 -0
  76. package/es/elements/data/dsCurrentAttachment.d.ts +13 -0
  77. package/es/elements/data/dsCurrentAttachment.js +75 -0
  78. package/es/elements/data/dsDamages.d.ts +25 -0
  79. package/es/elements/data/dsDamages.js +127 -0
  80. package/es/elements/data/dsDamagesDuplicateCheck.d.ts +2 -0
  81. package/es/elements/data/dsDamagesDuplicateCheck.js +3 -0
  82. package/es/elements/data/dsElements.d.ts +8 -0
  83. package/es/elements/data/dsElements.js +47 -0
  84. package/es/elements/data/dsProjects.d.ts +8 -0
  85. package/es/elements/data/dsProjects.js +51 -0
  86. package/es/elements/data/dsReportProjects.d.ts +2 -0
  87. package/es/elements/data/dsReportProjects.js +3 -0
  88. package/es/elements/data/dsTransportElements.d.ts +10 -0
  89. package/es/elements/data/dsTransportElements.js +57 -0
  90. package/es/elements/data/index.d.ts +11 -0
  91. package/es/elements/data/index.js +11 -0
  92. package/es/elements/data/procApproveDamage.d.ts +2 -0
  93. package/es/elements/data/procApproveDamage.js +18 -0
  94. package/es/elements/data/procDeleteAttachment.d.ts +2 -0
  95. package/es/elements/data/procDeleteAttachment.js +18 -0
  96. package/es/elements/data/procDeleteDamage.d.ts +2 -0
  97. package/es/elements/data/procDeleteDamage.js +18 -0
  98. package/es/elements/data/procRemoveDamageFix.d.ts +2 -0
  99. package/es/elements/data/procRemoveDamageFix.js +18 -0
  100. package/es/elements/data/procRestoreAttachments.d.ts +2 -0
  101. package/es/elements/data/procRestoreAttachments.js +18 -0
  102. package/es/elements/index.d.ts +9 -0
  103. package/es/elements/index.js +9 -0
  104. package/es/index.css +1 -1
  105. package/es/index.d.ts +12 -11
  106. package/es/index.js +12 -11
  107. package/package.json +16 -40
  108. package/src/{Checkbox → components/Checkbox}/index.tsx +1 -1
  109. package/{es → src/components}/ColorCard/index.css +10 -6
  110. package/src/{DateNavigator → components/DateNavigator}/index.tsx +2 -0
  111. package/src/components/FilePond.tsx +312 -0
  112. package/src/{LinkedCardList → components/LinkedCardList}/index.scss +1 -1
  113. package/src/{PageBanner → components/PageBanner}/index.scss +1 -1
  114. package/src/{PageBanner → components/PageBanner}/index.tsx +0 -1
  115. package/src/{ProgressBar → components/ProgressBar}/index.scss +1 -1
  116. package/src/{Sidebar → components/Sidebar}/index.tsx +3 -2
  117. package/src/{SplitContainer → components/SplitContainer}/index.tsx +1 -1
  118. package/src/{ValueToggleGroup → components/ValueToggleGroup}/index.css +18 -16
  119. package/src/{ValueToggleGroup → components/ValueToggleGroup}/index.tsx +2 -1
  120. package/src/elements/ElementDamage/DamageFormButtons.tsx +35 -0
  121. package/src/elements/ElementDamage/DescriptionInput.tsx +111 -0
  122. package/src/elements/ElementDamage/DuplicateDamage/DesktopView.tsx +75 -0
  123. package/src/elements/ElementDamage/DuplicateDamage/DublicateElementDamageDialog.tsx +65 -0
  124. package/src/elements/ElementDamage/DuplicateDamage/PhoneView.tsx +42 -0
  125. package/src/elements/ElementDamage/DuplicateDamage/index.tsx +105 -0
  126. package/src/elements/ElementDamage/ElementNumberTextField.tsx +41 -0
  127. package/src/elements/ElementDamage/HiddenDamageFields.tsx +13 -0
  128. package/src/elements/ElementDamage/RegisterFix.tsx +100 -0
  129. package/src/elements/ElementDamage/SubmitButtons.tsx +40 -0
  130. package/src/elements/ElementDamage/TransportElementSelect.tsx +52 -0
  131. package/src/elements/ElementDamage/index.css +3 -0
  132. package/src/elements/ElementLookup.tsx +90 -0
  133. package/src/elements/ElementLookupDialog/BarcodeScanner/DeadIcon.tsx +12 -0
  134. package/src/elements/ElementLookupDialog/BarcodeScanner/Error.tsx +46 -0
  135. package/src/elements/ElementLookupDialog/BarcodeScanner/index.tsx +75 -0
  136. package/src/elements/ElementLookupDialog/BarcodeScanner/useScanner.ts +170 -0
  137. package/src/elements/ElementLookupDialog/HIDScanner/index.css +17 -0
  138. package/src/elements/ElementLookupDialog/HIDScanner/index.tsx +67 -0
  139. package/src/elements/ElementLookupDialog/HIDScanner/useHIDScanner.ts +30 -0
  140. package/src/elements/ElementLookupDialog/Manual/ManualForm.tsx +110 -0
  141. package/src/elements/ElementLookupDialog/getElement.ts +89 -0
  142. package/src/elements/ElementLookupDialog/index.tsx +211 -0
  143. package/src/elements/ElementLookupDialog/utils/Giai.ts +209 -0
  144. package/src/elements/ElementLookupDialog/utils/beep.ts +35 -0
  145. package/src/elements/ProjectSelect.tsx +128 -0
  146. package/src/elements/data/dsAttachments.ts +101 -0
  147. package/src/elements/data/dsCurrentAttachment.ts +89 -0
  148. package/src/elements/data/dsDamages.ts +166 -0
  149. package/src/elements/data/dsDamagesDuplicateCheck.ts +4 -0
  150. package/src/elements/data/dsElements.ts +56 -0
  151. package/src/elements/data/dsProjects.ts +60 -0
  152. package/src/elements/data/dsReportProjects.ts +4 -0
  153. package/src/elements/data/dsTransportElements.ts +68 -0
  154. package/src/elements/data/index.ts +11 -0
  155. package/src/elements/data/procApproveDamage.ts +19 -0
  156. package/src/elements/data/procDeleteAttachment.ts +19 -0
  157. package/src/elements/data/procDeleteDamage.ts +19 -0
  158. package/src/elements/data/procRemoveDamageFix.ts +19 -0
  159. package/src/elements/data/procRestoreAttachments.ts +19 -0
  160. package/src/elements/index.ts +9 -0
  161. package/src/index.scss +10 -10
  162. package/src/index.ts +13 -13
  163. /package/es/{Checkbox → components/Checkbox}/index.css +0 -0
  164. /package/es/{Checkbox → components/Checkbox}/index.d.ts +0 -0
  165. /package/es/{Checkbox → components/Checkbox}/index.js +0 -0
  166. /package/es/{ColorCard → components/ColorCard}/index.d.ts +0 -0
  167. /package/es/{ColorCard → components/ColorCard}/index.js +0 -0
  168. /package/es/{DateNavigator → components/DateNavigator}/index.css +0 -0
  169. /package/es/{DateNavigator → components/DateNavigator}/index.d.ts +0 -0
  170. /package/es/{LinkedCardList → components/LinkedCardList}/index.css +0 -0
  171. /package/es/{LinkedCardList → components/LinkedCardList}/index.d.ts +0 -0
  172. /package/es/{LinkedCardList → components/LinkedCardList}/index.js +0 -0
  173. /package/es/{PageBanner → components/PageBanner}/index.css +0 -0
  174. /package/es/{PageBanner → components/PageBanner}/index.d.ts +0 -0
  175. /package/es/{Portal → components/Portal}/index.d.ts +0 -0
  176. /package/es/{Portal → components/Portal}/index.js +0 -0
  177. /package/es/{ProgressBar → components/ProgressBar}/index.css +0 -0
  178. /package/es/{ProgressBar → components/ProgressBar}/index.d.ts +0 -0
  179. /package/es/{ProgressBar → components/ProgressBar}/index.js +0 -0
  180. /package/es/{Sidebar → components/Sidebar}/index.css +0 -0
  181. /package/es/{Sidebar → components/Sidebar}/index.js +0 -0
  182. /package/es/{Spinner → components/Spinner}/index.css +0 -0
  183. /package/es/{Spinner → components/Spinner}/index.d.ts +0 -0
  184. /package/es/{Spinner → components/Spinner}/index.js +0 -0
  185. /package/es/{SplitContainer → components/SplitContainer}/index.css +0 -0
  186. /package/es/{SplitContainer → components/SplitContainer}/index.d.ts +0 -0
  187. /package/es/{ValueToggleGroup → components/ValueToggleGroup}/index.d.ts +0 -0
  188. /package/src/{Checkbox → components/Checkbox}/index.scss +0 -0
  189. /package/src/{ColorCard → components/ColorCard}/index.tsx +0 -0
  190. /package/src/{DateNavigator → components/DateNavigator}/index.css +0 -0
  191. /package/src/{LinkedCardList → components/LinkedCardList}/index.tsx +0 -0
  192. /package/src/{Portal → components/Portal}/index.tsx +0 -0
  193. /package/src/{ProgressBar → components/ProgressBar}/index.tsx +0 -0
  194. /package/src/{Sidebar → components/Sidebar}/index.scss +0 -0
  195. /package/src/{Spinner → components/Spinner}/index.css +0 -0
  196. /package/src/{Spinner → components/Spinner}/index.tsx +0 -0
  197. /package/src/{SplitContainer → components/SplitContainer}/index.scss +0 -0
@@ -0,0 +1,40 @@
1
+ import Add from "@mui/icons-material/Add";
2
+ import { Box, Button, type SxProps, type Theme } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+
5
+ type SubmitButtonsProps = {
6
+ /**
7
+ * Whether the form is in edit mode. If true, delete button is visible and submit button's text changes.
8
+ */
9
+ edit?: boolean;
10
+ /**
11
+ * Set loading state for submit button
12
+ */
13
+ loading?: boolean;
14
+ /**
15
+ * Custom styles for Box wrapper around buttons
16
+ */
17
+ sx?: SxProps<Theme>;
18
+ onDelete?: () => void;
19
+ onCancel?: () => void;
20
+ onSubmit?: () => void;
21
+ };
22
+
23
+ export function SubmitButtons({ edit, loading, onDelete, onCancel, onSubmit, sx }: SubmitButtonsProps) {
24
+ return (
25
+ <Box sx={{ display: "flex", flexWrap: "wrap", gap: 2, justifyContent: "flex-end", ...sx }}>
26
+ {edit && <Button onClick={onDelete}>{getLocalizedString("Delete")}</Button>}
27
+ <Button onClick={onCancel}>{getLocalizedString("Cancel")}</Button>
28
+ <Button
29
+ loading={loading}
30
+ loadingPosition="start"
31
+ startIcon={<Add />}
32
+ type="submit"
33
+ variant="contained"
34
+ onClick={onSubmit}
35
+ >
36
+ {edit ? getLocalizedString("Save changes") : getLocalizedString("Register damage")}
37
+ </Button>
38
+ </Box>
39
+ );
40
+ }
@@ -0,0 +1,52 @@
1
+ import DropDownArrowIcon from "@mui/icons-material/KeyboardArrowDown";
2
+ import { Button, ListItemText, Menu, MenuItem } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+ import { useState } from "react";
5
+ import type { TransportElementsRecord } from "../data/dsTransportElements.js";
6
+
7
+ type TransportElementSelectProps = {
8
+ elements: TransportElementsRecord[];
9
+ onSelect: (element: TransportElementsRecord) => void;
10
+ };
11
+
12
+ export function TransportElementSelect({ elements, onSelect }: TransportElementSelectProps) {
13
+ let [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
14
+ let open = !!anchorEl;
15
+ let [selected, setSelected] = useState<TransportElementsRecord | null>(null);
16
+
17
+ function handleChange(element: TransportElementsRecord) {
18
+ setAnchorEl(null);
19
+ onSelect(element);
20
+ setSelected(element);
21
+ }
22
+
23
+ return (
24
+ <>
25
+ <Button
26
+ id="select_element"
27
+ variant="contained"
28
+ endIcon={
29
+ <DropDownArrowIcon
30
+ style={{ transform: open ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.2s" }}
31
+ />
32
+ }
33
+ sx={{ width: "165px" }}
34
+ aria-controls={open ? "basic-menu" : undefined}
35
+ aria-haspopup="true"
36
+ aria-expanded={open ? "true" : undefined}
37
+ onClick={(event) => setAnchorEl(event.currentTarget)}
38
+ >
39
+ {selected ? `${selected.ElementID} - ${selected.Name}` : getLocalizedString("Select element")}
40
+ </Button>
41
+ <Menu id="menu_element" anchorEl={anchorEl} open={open} onClose={() => setAnchorEl(null)}>
42
+ {elements.map((element) => (
43
+ <MenuItem key={element.PrimKey} onClick={() => handleChange(element)}>
44
+ <ListItemText>
45
+ {element.ElementID} - {element.Name}
46
+ </ListItemText>
47
+ </MenuItem>
48
+ ))}
49
+ </Menu>
50
+ </>
51
+ );
52
+ }
@@ -0,0 +1,3 @@
1
+ .flow > * + * {
2
+ margin-top: var(--spacing, 1rem) !important;
3
+ }
@@ -0,0 +1,90 @@
1
+ import SearchIcon from "@mui/icons-material/Search";
2
+ import { IconButton, TextField } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+ import { type RefObject, useEffect, useImperativeHandle, useState } from "react";
5
+ import { type AddElement, ElementLookupDialog } from "./ElementLookupDialog";
6
+
7
+ export type Element = {
8
+ Domain: string;
9
+ PrimKey: string;
10
+ ProjectID: string;
11
+ ProjectName: string;
12
+ ElementID: string | null;
13
+ };
14
+
15
+ export type ElementLookupHandle = {
16
+ openDialog: () => void;
17
+ closeDialog: () => void;
18
+ };
19
+
20
+ export type ElementLookupProps = {
21
+ value?: Element | null;
22
+ defaultValue?: Element | null;
23
+ ref?: RefObject<ElementLookupHandle | null>;
24
+ openLookupDialogIcon?: boolean;
25
+ onChange?: (value: Element | null) => void;
26
+ };
27
+
28
+ export function ElementLookup({ value, defaultValue, ref, openLookupDialogIcon, onChange }: ElementLookupProps) {
29
+ let [openElementLookupDialog, setOpenElementLookupDialog] = useState<boolean>(false);
30
+ let [element, setElement] = useState<Element | null>(defaultValue ?? null);
31
+ let elementValue = value === undefined ? element : value;
32
+
33
+ useEffect(() => {
34
+ if (value === undefined && defaultValue) {
35
+ setElement(defaultValue);
36
+ }
37
+ }, [defaultValue, value]);
38
+
39
+ useImperativeHandle(ref, () => ({
40
+ openDialog: () => setOpenElementLookupDialog(true),
41
+ closeDialog: () => setOpenElementLookupDialog(false),
42
+ }));
43
+
44
+ async function handleAddElement(addElement: AddElement) {
45
+ onChange?.(addElement.element);
46
+ setElement(addElement.element);
47
+ }
48
+
49
+ return (
50
+ <>
51
+ <ElementLookupDialog
52
+ open={openElementLookupDialog}
53
+ mode="single"
54
+ onClose={() => setOpenElementLookupDialog(false)}
55
+ onAdd={handleAddElement}
56
+ />
57
+
58
+ <input type="hidden" name="ProjectID" value={elementValue?.ProjectID ?? ""} />
59
+ <input type="hidden" name="ElementRef" value={elementValue?.PrimKey ?? ""} />
60
+
61
+ <TextField
62
+ id="NewElementDamge_Project"
63
+ value={elementValue ? `${elementValue.ProjectID}: ${elementValue.ProjectName}` : ""}
64
+ label={getLocalizedString("Project")}
65
+ fullWidth
66
+ required
67
+ autoComplete="off"
68
+ slotProps={{ htmlInput: { readOnly: true } }}
69
+ />
70
+ <TextField
71
+ name="ElementID"
72
+ id="NewElementDamage_ElementID"
73
+ value={elementValue ? elementValue.ElementID : ""}
74
+ label={getLocalizedString("Element ID")}
75
+ fullWidth
76
+ autoComplete="off"
77
+ slotProps={{
78
+ htmlInput: { readOnly: true },
79
+ input: {
80
+ endAdornment: openLookupDialogIcon ? (
81
+ <IconButton onClick={() => setOpenElementLookupDialog(true)}>
82
+ <SearchIcon />
83
+ </IconButton>
84
+ ) : undefined,
85
+ },
86
+ }}
87
+ />
88
+ </>
89
+ );
90
+ }
@@ -0,0 +1,12 @@
1
+ import { SvgIcon, type SvgIconProps } from "@mui/material";
2
+
3
+ export function Dead(props: SvgIconProps) {
4
+ return (
5
+ <SvgIcon {...props}>
6
+ <path
7
+ fill="currentColor"
8
+ d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2M6.76,8.82L7.82,7.76L8.88,8.82L9.94,7.76L11,8.82L9.94,9.88L11,10.94L9.94,12L8.88,10.94L7.82,12L6.76,10.94L7.82,9.88L6.76,8.82M6.89,17.5C7.69,15.46 9.67,14 12,14C14.33,14 16.31,15.46 17.11,17.5H6.89M17.24,10.94L16.18,12L15.12,10.94L14.06,12L13,10.94L14.06,9.88L13,8.82L14.06,7.76L15.12,8.82L16.18,7.76L17.24,8.82L16.18,9.88L17.24,10.94Z"
9
+ />
10
+ </SvgIcon>
11
+ );
12
+ }
@@ -0,0 +1,46 @@
1
+ import styled from "@emotion/styled";
2
+ import Camera from "@mui/icons-material/Camera";
3
+
4
+ import { Dead as DeadIcon } from "./DeadIcon";
5
+
6
+ const BarcodeError = styled.div`
7
+ position: absolute;
8
+ bottom: 0;
9
+ left: 0;
10
+ right: 0;
11
+ top: 0;
12
+
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ `;
17
+
18
+ const ErrorBox = styled.div`
19
+ background: #111111;
20
+ border: 1px solid #3e3e3e;
21
+ border-radius: 3px;
22
+
23
+ max-width: 100%;
24
+ width: 335px;
25
+
26
+ color: white;
27
+ text-align: center;
28
+ `;
29
+
30
+ export function ErrorIcon({ icon, message }: { icon?: string; message: string }) {
31
+ let Icon: React.FC<any> = DeadIcon;
32
+
33
+ if (icon === "camera") {
34
+ Icon = Camera;
35
+ }
36
+
37
+ return (
38
+ <BarcodeError>
39
+ <ErrorBox>
40
+ <Icon sx={{ color: "#eee", fontSize: "3.5rem", my: 2 }} />
41
+ <br />
42
+ <p>{message}</p>
43
+ </ErrorBox>
44
+ </BarcodeError>
45
+ );
46
+ }
@@ -0,0 +1,75 @@
1
+ import SwitchCameraOutlined from "@mui/icons-material/SwitchCameraOutlined";
2
+ import { Box, IconButton, type SxProps } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+ import type BarcodeReaderType from "dynamsoft-javascript-barcode";
5
+ import { useRef } from "react";
6
+ import { ErrorIcon } from "./Error";
7
+ import { useScanner } from "./useScanner";
8
+
9
+ declare global {
10
+ interface Window {
11
+ BarcodeReader: typeof BarcodeReaderType;
12
+ }
13
+ }
14
+
15
+ export type BarcodeScannerProps = {
16
+ onRead: (barcode: string) => void;
17
+ };
18
+
19
+ const frameSx: SxProps = {
20
+ position: "absolute",
21
+ bottom: 0,
22
+ left: 0,
23
+ right: 0,
24
+ top: 0,
25
+ height: "100%",
26
+ width: "100%",
27
+ };
28
+
29
+ export function BarcodeScanner({ onRead }: BarcodeScannerProps) {
30
+ let readerRoot = useRef<HTMLElement | null>(null);
31
+ let canvas = useRef<HTMLCanvasElement | null>(null);
32
+ let { devices, error, setNextDevice } = useScanner(readerRoot, canvas, onRead);
33
+
34
+ return (
35
+ <Box
36
+ sx={{
37
+ position: "relative",
38
+ bgcolor: "black",
39
+ height: "100%",
40
+ }}
41
+ >
42
+ <Box height="100%" ref={readerRoot}>
43
+ <Box component="video" className="dbrScanner-video" playsInline sx={frameSx} />
44
+ <Box component="canvas" sx={frameSx} width="1280" height="720" ref={canvas} />
45
+ {error && <ErrorIcon {...error} />}
46
+ </Box>
47
+ <Box
48
+ sx={{
49
+ display: "flex",
50
+ alignItems: "center",
51
+ justifyContent: "space-between",
52
+ position: "absolute",
53
+ bottom: 0,
54
+ left: 0,
55
+ right: 0,
56
+ py: 1,
57
+ px: 3,
58
+ color: "white",
59
+ }}
60
+ >
61
+ {devices.length <= 1 && <span />}
62
+ {devices.length > 1 && (
63
+ <IconButton
64
+ color="inherit"
65
+ aria-label={getLocalizedString("Switch camera")}
66
+ onClick={() => setNextDevice()}
67
+ size="large"
68
+ >
69
+ <SwitchCameraOutlined />
70
+ </IconButton>
71
+ )}
72
+ </Box>
73
+ </Box>
74
+ );
75
+ }
@@ -0,0 +1,170 @@
1
+ import { getLocalizedString } from "@olenbetong/appframe-core";
2
+
3
+ import type BarcodeReaderType from "dynamsoft-javascript-barcode";
4
+ // import BarcodeReader from "dynamsoft-javascript-barcode";
5
+ import { type RefObject, useEffect, useRef, useState } from "react";
6
+
7
+ declare global {
8
+ interface Window {
9
+ BarcodeReader: typeof BarcodeReaderType;
10
+ }
11
+ }
12
+
13
+ // (window as any).BarcodeReader = BarcodeReader;
14
+ const { BarcodeReader } = window;
15
+
16
+ export function useScanner(
17
+ rootRef: RefObject<HTMLElement | null>,
18
+ canvasRef: RefObject<HTMLCanvasElement | null>,
19
+ onRead: (barcode: string) => void,
20
+ ) {
21
+ const [isLoading, setIsLoading] = useState(true);
22
+ const [error, setError] = useState<{ message: string; icon: string } | null>(null);
23
+ const [rects, setRects] = useState<Array<BarcodeReaderType.LocalizationResult>>([]);
24
+ const [devices, setDevices] = useState<BarcodeReaderType.ScannerOpenCallbackInfo>({
25
+ all: [],
26
+ current: { deviceId: "", label: "" },
27
+ width: 0,
28
+ height: 0,
29
+ });
30
+ const currentDevice = useRef<number>(null);
31
+ const onReadRef = useRef(onRead);
32
+ const scanner = useRef<BarcodeReaderType.Scanner>(null);
33
+
34
+ useEffect(() => {
35
+ onReadRef.current = onRead;
36
+ }, [onRead]);
37
+
38
+ useEffect(() => {
39
+ if (!rootRef.current) return;
40
+
41
+ let cancel = false;
42
+ const iPhone = /iPhone|iPad/.test(navigator.userAgent);
43
+ scanner.current = new BarcodeReader.Scanner({
44
+ duplicateForgetTime: 1000,
45
+ htmlElement: rootRef.current,
46
+ videoSettings: {
47
+ video: {
48
+ width: { ideal: iPhone ? 1280 : 720 },
49
+ height: { ideal: iPhone ? 1280 : 720 },
50
+ facingMode: { ideal: "environment" },
51
+ },
52
+ },
53
+ runtimeSettings: {
54
+ mBarcodeFormatIds: BarcodeReader.EnumBarcodeFormat.DATAMATRIX,
55
+ mAntiDamageLevel: 3,
56
+ mDeblurLevel: 0,
57
+ },
58
+ onFrameRead: (results) => {
59
+ const newRects: Array<BarcodeReaderType.LocalizationResult> = [];
60
+
61
+ for (let result of results) {
62
+ newRects.push(result.LocalizationResult);
63
+ }
64
+
65
+ if (newRects.length > 0) {
66
+ setRects((rects) => [...rects, ...newRects]);
67
+
68
+ setTimeout(() => {
69
+ if (!cancel) {
70
+ setRects((rects) => rects.slice(newRects.length));
71
+ }
72
+ }, 300);
73
+ }
74
+ },
75
+
76
+ onNewCodeRead: (txt) => {
77
+ if (txt) {
78
+ onReadRef.current(txt);
79
+ }
80
+ },
81
+ });
82
+
83
+ scanner.current
84
+ .open()
85
+ .then((videoDeviceInfo) => {
86
+ if (cancel === false) {
87
+ const { all, current } = videoDeviceInfo;
88
+ currentDevice.current = 0;
89
+
90
+ if (current?.deviceId) {
91
+ for (let i = 0; i < all.length; i++) {
92
+ if (all[i].deviceId === current.deviceId) {
93
+ currentDevice.current = i;
94
+ }
95
+ }
96
+ }
97
+
98
+ setDevices(videoDeviceInfo);
99
+ setIsLoading(false);
100
+ }
101
+ })
102
+ .catch((ex) => {
103
+ if (cancel === false) {
104
+ let error = ex.message || ex;
105
+ setIsLoading(false);
106
+
107
+ if (/Webassembly/.test(error)) {
108
+ setError({
109
+ icon: "unsupported",
110
+ message: getLocalizedString(
111
+ "Your browser is not supported. A browser that supports WebAssembly is required.",
112
+ ),
113
+ });
114
+ } else if (
115
+ /Permission/i.test(error) ||
116
+ /video/i.test(error) ||
117
+ /device/i.test(error) ||
118
+ /Media/i.test(error) ||
119
+ /agent/.test(error) ||
120
+ /found/.test(error)
121
+ )
122
+ setError({
123
+ icon: "camera",
124
+ message: getLocalizedString("No camera available"),
125
+ });
126
+ else {
127
+ setError({ icon: "generic", message: error });
128
+ }
129
+ }
130
+ });
131
+
132
+ return () => {
133
+ cancel = true;
134
+
135
+ if (scanner.current && typeof scanner.current.close === "function") {
136
+ scanner.current.close();
137
+ }
138
+ };
139
+ }, [rootRef]);
140
+
141
+ useEffect(() => {
142
+ if (canvasRef.current) {
143
+ const ctx = canvasRef.current.getContext("2d") as CanvasRenderingContext2D;
144
+ ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
145
+ ctx.fillStyle = "rgba(254,180,32,0.3)";
146
+
147
+ const { width, height } = canvasRef.current;
148
+ const xRatio = width / devices.width;
149
+ const yRatio = height / devices.height;
150
+
151
+ for (let rect of rects) {
152
+ const { X1, X2, X3, X4, Y1, Y2, Y3, Y4 } = rect;
153
+
154
+ ctx.beginPath();
155
+ ctx.moveTo(X1 * xRatio, Y1 * yRatio);
156
+ ctx.lineTo(X2 * xRatio, Y2 * yRatio);
157
+ ctx.lineTo(X3 * xRatio, Y3 * yRatio);
158
+ ctx.lineTo(X4 * xRatio, Y4 * yRatio);
159
+ ctx.fill();
160
+ }
161
+ }
162
+ }, [devices, canvasRef, rects]);
163
+
164
+ function setNextDevice() {
165
+ currentDevice.current = ((currentDevice.current ?? 0) + 1) % devices.all.length;
166
+ scanner.current?.play(devices.all[currentDevice.current].deviceId);
167
+ }
168
+
169
+ return { devices: devices.all, error, isLoading, setNextDevice };
170
+ }
@@ -0,0 +1,17 @@
1
+ @property --angle {
2
+ syntax: "<angle>";
3
+ inherits: false;
4
+ initial-value: 0deg;
5
+ }
6
+
7
+ @keyframes MovingBackground {
8
+ 0% {
9
+ --angle: 0deg;
10
+ }
11
+ 50% {
12
+ --angle: 180deg;
13
+ }
14
+ 100% {
15
+ --angle: 360deg;
16
+ }
17
+ }
@@ -0,0 +1,67 @@
1
+ import "./index.css";
2
+
3
+ import { Box, Button } from "@mui/material";
4
+ import { getLocalizedString } from "@olenbetong/appframe-core";
5
+
6
+ import { useEffect, useRef, useState } from "react";
7
+
8
+ import { useHIDScanner } from "./useHIDScanner";
9
+
10
+ export function AddElementHIDScanner({ onScan }: { onScan: (id: string) => void }) {
11
+ let [enabled, setEnabled] = useState(false);
12
+ let root = useRef<HTMLDivElement>(null);
13
+
14
+ useEffect(() => {
15
+ if (!root.current) return;
16
+
17
+ let rootElem = root.current;
18
+
19
+ function handleFocus() {
20
+ setEnabled(true);
21
+ }
22
+
23
+ function handleBlur() {
24
+ setEnabled(false);
25
+ }
26
+
27
+ rootElem.addEventListener("focus", handleFocus);
28
+ rootElem.addEventListener("blur", handleBlur);
29
+ rootElem.focus();
30
+
31
+ return () => {
32
+ rootElem.removeEventListener("focus", handleFocus);
33
+ rootElem.removeEventListener("blur", handleBlur);
34
+ };
35
+ }, []);
36
+
37
+ useHIDScanner(onScan, enabled);
38
+
39
+ return (
40
+ <Box
41
+ height="100%"
42
+ bgcolor="#ddd"
43
+ display="flex"
44
+ alignItems="center"
45
+ justifyContent="center"
46
+ tabIndex={0}
47
+ ref={root}
48
+ sx={{
49
+ backgroundImage: enabled ? "linear-gradient(var(--angle), #777, #bbb, #777)" : "none",
50
+ animation: enabled ? "MovingBackground 8s linear infinite" : "none",
51
+ }}
52
+ >
53
+ {enabled ? (
54
+ <span>{getLocalizedString("Listening for scanned elements")}...</span>
55
+ ) : (
56
+ <Button
57
+ variant="contained"
58
+ onClick={() => {
59
+ if (root.current) root.current.focus();
60
+ }}
61
+ >
62
+ {getLocalizedString("Start listening")}
63
+ </Button>
64
+ )}
65
+ </Box>
66
+ );
67
+ }
@@ -0,0 +1,30 @@
1
+ import { useEffect } from "react";
2
+
3
+ export function useHIDScanner(onRead: (value: string) => void, enabled = false) {
4
+ useEffect(() => {
5
+ if (!enabled) return;
6
+
7
+ let hexKeys = "ABCDEFabcdefxX0123456789";
8
+ let buffer = "";
9
+ let clearBufferTimeout: NodeJS.Timeout;
10
+
11
+ function handleKeyDownEvent(event: KeyboardEvent) {
12
+ if (hexKeys.includes(event.key)) {
13
+ clearTimeout(clearBufferTimeout);
14
+ buffer += event.key;
15
+ clearBufferTimeout = setTimeout(() => {
16
+ buffer = "";
17
+ }, 500);
18
+ } else if (event.key === "Enter") {
19
+ onRead(buffer);
20
+ buffer = "";
21
+ }
22
+ }
23
+
24
+ document.body.addEventListener("keydown", handleKeyDownEvent);
25
+
26
+ return () => {
27
+ document.body.removeEventListener("keydown", handleKeyDownEvent);
28
+ };
29
+ }, [onRead, enabled]);
30
+ }