@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,27 @@
1
+ import { useEffect } from "react";
2
+ export function useHIDScanner(onRead, enabled = false) {
3
+ useEffect(() => {
4
+ if (!enabled)
5
+ return;
6
+ let hexKeys = "ABCDEFabcdefxX0123456789";
7
+ let buffer = "";
8
+ let clearBufferTimeout;
9
+ function handleKeyDownEvent(event) {
10
+ if (hexKeys.includes(event.key)) {
11
+ clearTimeout(clearBufferTimeout);
12
+ buffer += event.key;
13
+ clearBufferTimeout = setTimeout(() => {
14
+ buffer = "";
15
+ }, 500);
16
+ }
17
+ else if (event.key === "Enter") {
18
+ onRead(buffer);
19
+ buffer = "";
20
+ }
21
+ }
22
+ document.body.addEventListener("keydown", handleKeyDownEvent);
23
+ return () => {
24
+ document.body.removeEventListener("keydown", handleKeyDownEvent);
25
+ };
26
+ }, [onRead, enabled]);
27
+ }
@@ -0,0 +1,12 @@
1
+ import { type ElementDetails } from "../getElement";
2
+ export type ProjectRecordBase = {
3
+ Domain: string;
4
+ ProjectID: string;
5
+ ProjectName?: string;
6
+ };
7
+ export type AddElementManualFormProps = {
8
+ onAdd: (element: string | ElementDetails) => Promise<void>;
9
+ mode?: "single" | "multi";
10
+ defaultProject?: ProjectRecordBase;
11
+ };
12
+ export declare function AddElementManualForm({ onAdd, mode, defaultProject }: AddElementManualFormProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Add from "@mui/icons-material/Add";
3
+ import { Alert, Button, TextField } from "@mui/material";
4
+ import { getLocalizedString } from "@olenbetong/appframe-core";
5
+ import { useId, useRef, useState } from "react";
6
+ import { ProjectSelect } from "../../ProjectSelect";
7
+ import { getElement } from "../getElement";
8
+ const isElementsOnly = /^([a-z]{2,4}[\s]*[0-9]{4,8}[\s,]*)+$/i;
9
+ const matchElements = /([a-z]{2,4}[\s]*[0-9]{4,8})/gi;
10
+ export function AddElementManualForm({ onAdd, mode, defaultProject }) {
11
+ let id = useId();
12
+ let buttonRef = useRef(null);
13
+ let [working, setWorking] = useState(false);
14
+ let [elementId, setElementId] = useState("");
15
+ let [project, setProject] = useState(defaultProject ?? null);
16
+ async function handleSubmit(evt) {
17
+ try {
18
+ evt.preventDefault();
19
+ evt.stopPropagation();
20
+ let formData = new FormData(evt.target);
21
+ let elements = formData.get("ElementID");
22
+ let domain = formData.get("Domain");
23
+ let projectId = formData.get("ProjectID");
24
+ if (!projectId || !domain || !elements) {
25
+ let warning = getLocalizedString(mode === "multi" ? "Project and elements are required" : "Project and element are required");
26
+ alert(warning);
27
+ return;
28
+ }
29
+ setWorking(true);
30
+ let record = elements
31
+ .match(matchElements)
32
+ ?.map((elementId) => elementId.replace(/\s+/g, ""))
33
+ .map((elementId) => getElement({ projectId: projectId, elementId: elementId }, { mode: "manual" }));
34
+ if (!record) {
35
+ alert(getLocalizedString("Invalid elements"));
36
+ return;
37
+ }
38
+ let loaded = (await Promise.all(record)).map((record) => onAdd(record));
39
+ await Promise.all(loaded);
40
+ setWorking(false);
41
+ }
42
+ catch (error) {
43
+ setWorking(false);
44
+ alert(error.message);
45
+ }
46
+ }
47
+ return (_jsxs("form", { id: id, onSubmit: handleSubmit, method: "post", children: [_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: getLocalizedString("Manually entering elements is a major source of errors. Only use this as a last resort. If the camera or scanner is not working, please contact the IT department.") }), _jsx(ProjectSelect, { onChange: (project) => (project ? setProject(project) : setProject(null)), value: project, id: "AddElement_Project" }), _jsx(TextField, { variant: "filled", fullWidth: true, autoComplete: "off", margin: "normal", name: "ElementID", id: "AddElement_ElementID", label: getLocalizedString("Element ID"), value: elementId, onChange: (evt) => setElementId(evt.target.value) }), _jsx(Button, { form: id, ref: buttonRef, color: "primary", variant: "contained", size: "large", type: "submit", disabled: !isElementsOnly.test(elementId) || working, loading: working, loadingPosition: "end", endIcon: _jsx(Add, {}), children: getLocalizedString("Add") })] }));
48
+ }
@@ -0,0 +1,23 @@
1
+ export type AddElementSource = "manual" | "camera-barcode" | "loading-list" | "hid-barcode" | "hid-rfid";
2
+ export type ElementDetails = {
3
+ Domain: string;
4
+ PrimKey: string;
5
+ Created: Date;
6
+ ProjectID: string;
7
+ ProjectName: string;
8
+ ProjectDomain: string;
9
+ ElementID: string;
10
+ DisplayName: string;
11
+ GiaiUri: string;
12
+ };
13
+ export type ElementFilter = {
14
+ projectId: string;
15
+ elementId: string;
16
+ };
17
+ export type GetElementContext = {
18
+ filter?: string | ElementFilter;
19
+ mode?: AddElementSource;
20
+ method?: "cache" | "database";
21
+ result?: null | ElementDetails;
22
+ };
23
+ export declare function getElement(id: string | ElementFilter, context: GetElementContext): Promise<ElementDetails>;
@@ -0,0 +1,55 @@
1
+ import { logActivity, startActivity } from "@olenbetong/appframe-core";
2
+ import { DataProviderHandlerAPI } from "@olenbetong/appframe-data";
3
+ import Giai from "./utils/Giai";
4
+ const elementsDataHandler = new DataProviderHandlerAPI({
5
+ dataSourceId: "aviw_Fleet_DeliveryTickets_ElementsLookup",
6
+ fields: [
7
+ { name: "PrimKey", type: "string" },
8
+ { name: "Created", type: "datetime" },
9
+ { name: "Domain", type: "string" },
10
+ { name: "ProjectID", type: "string" },
11
+ { name: "ProjectName", type: "string" },
12
+ { name: "ProjectDomain", type: "string" },
13
+ { name: "ElementID", type: "string" },
14
+ { name: "DisplayName", type: "string" },
15
+ { name: "GiaiUri", type: "string" },
16
+ ],
17
+ });
18
+ const elementDetailsCache = new Map();
19
+ export async function getElement(id, context) {
20
+ let details;
21
+ context.filter = id;
22
+ if (typeof id === "string" && elementDetailsCache.has(id)) {
23
+ details = elementDetailsCache.get(id);
24
+ context.method = "cache";
25
+ context.result = details;
26
+ logActivity("GetElement", context);
27
+ }
28
+ else {
29
+ context.method = "database";
30
+ let logActivity = startActivity("GetElement", context);
31
+ let whereClause;
32
+ if (typeof id === "string") {
33
+ let uri = Giai.fromString(id);
34
+ whereClause = `[GiaiUri] = '${uri.toEpcPureIdentityUri()}'`;
35
+ }
36
+ else {
37
+ whereClause = `[ProjectID] = '${id.projectId}' AND [ElementID] = '${id.elementId}'`;
38
+ }
39
+ let result = await elementsDataHandler.retrieve({
40
+ whereClause,
41
+ });
42
+ if (!result.length) {
43
+ context.result = null;
44
+ logActivity();
45
+ throw Error(`Element not found`);
46
+ }
47
+ details = result[0];
48
+ context.result = details;
49
+ logActivity();
50
+ if (typeof id === "string") {
51
+ elementDetailsCache.set(id, details);
52
+ }
53
+ }
54
+ return details;
55
+ }
@@ -0,0 +1,24 @@
1
+ import { type AddElementSource, type ElementDetails } from "./getElement";
2
+ export type AddElement = {
3
+ element: ElementDetails;
4
+ mode: AddElementSource;
5
+ scanTime?: number;
6
+ };
7
+ type InitialTabProps = "camera" | "scanner" | "manual";
8
+ export type ElementLookupDialogSharedProps = {
9
+ open: boolean;
10
+ onClose: () => void;
11
+ initialTab?: InitialTabProps | null;
12
+ useSearch?: boolean;
13
+ };
14
+ export type ElementLookupDialogSingleModeProps = ElementLookupDialogSharedProps & {
15
+ mode: "single" | "search";
16
+ onAdd: (element: AddElement) => Promise<void>;
17
+ };
18
+ export type ElementLookupDialogMultiModeProps = ElementLookupDialogSharedProps & {
19
+ mode: "multi";
20
+ onAdd: (element: AddElement[]) => Promise<void>;
21
+ };
22
+ export type ElementLookupDialogProps = ElementLookupDialogSingleModeProps | ElementLookupDialogMultiModeProps;
23
+ export declare function ElementLookupDialog(props: ElementLookupDialogProps): import("react/jsx-runtime").JSX.Element;
24
+ export {};
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Add from "@mui/icons-material/Add";
3
+ import DeleteForeverOutlined from "@mui/icons-material/DeleteForeverOutlined";
4
+ import Edit from "@mui/icons-material/Edit";
5
+ import QrCodeScanner from "@mui/icons-material/QrCodeScanner";
6
+ import TapAndPlay from "@mui/icons-material/TapAndPlay";
7
+ import { Alert, Box, Button, DialogActions, List, ListItem, ListItemText, Tab, Tabs } from "@mui/material";
8
+ import { getLocalizedString, localize } from "@olenbetong/appframe-core";
9
+ import { ResponsiveDialog } from "@olenbetong/appframe-mui";
10
+ import { useEffect, useState } from "react";
11
+ import { flushSync } from "react-dom";
12
+ import { BarcodeScanner } from "./BarcodeScanner";
13
+ import { getElement } from "./getElement";
14
+ import { AddElementHIDScanner } from "./HIDScanner";
15
+ import { AddElementManualForm } from "./Manual/ManualForm";
16
+ import { useBeeper } from "./utils/beep";
17
+ import Giai from "./utils/Giai";
18
+ export function ElementLookupDialog(props) {
19
+ let { open, onClose, initialTab } = props;
20
+ let [tab, setTab] = useState("camera");
21
+ let [elements, setElements] = useState([]);
22
+ let [adding, setAdding] = useState(false);
23
+ let beeper = useBeeper();
24
+ useEffect(() => {
25
+ if (open && initialTab) {
26
+ setTab(initialTab);
27
+ }
28
+ }, [open, initialTab]);
29
+ async function handleElementAdd(element, mode) {
30
+ try {
31
+ let record = typeof element === "string" ? await getElement(element.replace("\u001d", ""), { mode }) : element;
32
+ // Flush changes to DOM to be able to query newly added elements and animate their background
33
+ flushSync(() => {
34
+ setElements((current) => {
35
+ let now = Date.now();
36
+ let exists = current.some((e) => e.element.ElementID === record.ElementID);
37
+ let updated;
38
+ if (exists) {
39
+ updated = current.map((e) => (e.element.ElementID === record.ElementID ? { ...e, scanTime: now } : e));
40
+ }
41
+ else {
42
+ beeper();
43
+ updated =
44
+ props.mode === "multi"
45
+ ? [{ element: record, mode, scanTime: now }, ...current]
46
+ : [{ element: record, mode, scanTime: now }];
47
+ }
48
+ if (props.mode === "multi") {
49
+ updated.sort((a, b) => (b.scanTime ?? 0) - (a.scanTime ?? 0));
50
+ }
51
+ return updated;
52
+ });
53
+ });
54
+ let addedDomElement = document.querySelector(`[data-elementid="${record.ElementID}"]`);
55
+ if (addedDomElement) {
56
+ addedDomElement.animate([
57
+ { backgroundColor: "var(--brand-light)", offset: 0 },
58
+ { backgroundColor: "var(--brand-light)", offset: 0.2 },
59
+ { backgroundColor: "transparent" },
60
+ ], {
61
+ duration: 1000,
62
+ easing: "ease-out",
63
+ });
64
+ }
65
+ }
66
+ catch (error) {
67
+ alert(error.message);
68
+ }
69
+ }
70
+ if (tab === "manual" && props.mode === "search") {
71
+ tab = "camera";
72
+ }
73
+ return (_jsx(ResponsiveDialog, { open: open, onClose: onClose, slotProps: {
74
+ paper: { sx: { overflow: "hidden" } },
75
+ }, children: _jsxs(Box, { display: "flex", flexDirection: "column", maxHeight: "100%", overflow: "hidden", flexGrow: 1, children: [_jsxs(Tabs, { value: tab, onChange: (_, newTab) => setTab(newTab), variant: "fullWidth", sx: { flexShrink: 0 }, children: [props.mode !== "search" && _jsx(Tab, { icon: _jsx(Edit, {}), label: getLocalizedString("Manual"), value: "manual" }), _jsx(Tab, { icon: _jsx(TapAndPlay, {}), label: getLocalizedString("Scanner"), value: "scanner" }), _jsx(Tab, { icon: _jsx(QrCodeScanner, {}), label: getLocalizedString("Camera"), value: "camera" })] }), tab === "camera" && (_jsx(Box, { flexBasis: "12rem", flexGrow: 1, flexShrink: 2, children: _jsx(BarcodeScanner, { onRead: (element) => handleElementAdd(element, "camera-barcode") }) })), tab === "manual" && (_jsx(Box, { p: 2, children: _jsx(AddElementManualForm, { onAdd: (element) => handleElementAdd(element, "manual") }) })), tab === "scanner" && (_jsx(Box, { flexBasis: "12rem", flexGrow: 1, flexShrink: 2, children: _jsx(AddElementHIDScanner, { onScan: (element) => handleElementAdd(element, element.startsWith(Giai.BARCODE_PREFIX) ? "hid-barcode" : "hid-rfid") }) })), _jsxs(Box, { flex: "3 1 14rem", overflow: "auto", children: [props.mode === "multi" && (_jsx(Alert, { icon: false, severity: "info", sx: { justifyContent: "center", py: 0, position: "sticky", top: 0, zIndex: 1 }, slotProps: {
76
+ message: { sx: { py: 0.5 } },
77
+ }, children: localize `${elements.length} elements selected` })), _jsx(List, { disablePadding: true, children: elements.map(({ element }) => (_jsx(ListItem, { "data-elementid": element.ElementID, secondaryAction: _jsx(Button, { color: "error", variant: "outlined", onClick: () => {
78
+ setElements((current) => props.mode === "multi" ? current.filter((e) => e.element.ElementID !== element.ElementID) : []);
79
+ }, children: _jsx(DeleteForeverOutlined, {}) }), children: _jsx(ListItemText, { primary: element.DisplayName, secondary: element.ElementID }) }, element.ElementID))) })] }), _jsxs(DialogActions, { sx: { flexShrink: 0 }, children: [_jsx(Button, { onClick: onClose, children: getLocalizedString("Cancel") }), _jsx(Button, { variant: "contained", loading: adding, onClick: async () => {
80
+ try {
81
+ setAdding(true);
82
+ if (props.mode === "multi") {
83
+ await props.onAdd(elements);
84
+ }
85
+ else {
86
+ await props.onAdd(elements[0]);
87
+ }
88
+ setElements([]);
89
+ setAdding(false);
90
+ onClose();
91
+ }
92
+ catch (error) {
93
+ setAdding(false);
94
+ alert(error.message);
95
+ }
96
+ }, startIcon: _jsx(Add, {}), loadingPosition: "start", disabled: !elements.length, children: props.mode === "search" ? getLocalizedString("Search for element") : getLocalizedString("Add element") })] })] }) }));
97
+ }
@@ -0,0 +1,52 @@
1
+ export type GiaiProps = {
2
+ assetReference: string | number | bigint;
3
+ companyPrefix: string | number | bigint;
4
+ filter?: string | number | undefined;
5
+ };
6
+ /**
7
+ * GIAI-96 encoder and decoder as defined by the GS1 EPC Tag data standard
8
+ */
9
+ export default class Giai {
10
+ #private;
11
+ static PARTITION_TABLE: number[];
12
+ static EPC_HEADER: string;
13
+ static BARCODE_PREFIX: string;
14
+ static GROUP_SEPARATOR: string;
15
+ static URN_PREFIX: string;
16
+ static fromHex: (hexIdentifier: string) => Giai;
17
+ /**
18
+ *
19
+ * @param {string | null | undefined} barcodeString
20
+ * @returns
21
+ */
22
+ static fromBarcode: (barcodeString: string) => Giai;
23
+ /**
24
+ * Creates a GIAI instance from a purse identity URI (urn:epc:giai:[companyprefix].[assetreference])
25
+ * @param {string} uri EPC GIAI pure identity URI
26
+ */
27
+ static fromEpcPureIdentityUri(uri: string): Giai;
28
+ /**
29
+ * Attempt to create a GIAI instance from a string with unknown format. Could be either a URN, barcode value
30
+ * or EPC value.
31
+ * @param {string} value
32
+ * @returns
33
+ */
34
+ static fromString(value: string, ignoreError?: false): Giai;
35
+ static fromString(value: string, ignoreError: true): Giai | undefined;
36
+ /**
37
+ *
38
+ * @param {GiaiProps} options
39
+ */
40
+ constructor({ assetReference, companyPrefix }: GiaiProps);
41
+ get assetReference(): string | number | bigint;
42
+ get companyPrefix(): string | number | bigint;
43
+ toEpcPureIdentityUri(): string;
44
+ toGiaiBarcode(): string;
45
+ toEpcHex(): string;
46
+ }
47
+ export declare function hexToBitString(hexString: string): string;
48
+ declare global {
49
+ interface Window {
50
+ Giai: typeof Giai | undefined;
51
+ }
52
+ }
@@ -0,0 +1,158 @@
1
+ import { getLocalizedString } from "@olenbetong/appframe-core";
2
+ /**
3
+ * GIAI-96 encoder and decoder as defined by the GS1 EPC Tag data standard
4
+ */
5
+ export default class Giai {
6
+ /// Bit lengths for company prefix given partition equal to index.
7
+ ///
8
+ /// Partition Company prefix Individual asset reference
9
+ /// Bits Digits Bits Digits
10
+ /// 0 40 12 42 13
11
+ /// 1 37 11 45 14
12
+ /// 2 34 10 48 15
13
+ /// 3 30 9 52 16
14
+ /// 4 27 8 55 17
15
+ /// 5 24 7 58 18
16
+ /// 6 20 6 62 19
17
+ static PARTITION_TABLE = [40, 37, 34, 30, 27, 24, 20];
18
+ // EPC header for GIAI-96 coding scheme
19
+ static EPC_HEADER = "00110100";
20
+ // Prefix for GS1 key used in barcodes;
21
+ static BARCODE_PREFIX = "8004";
22
+ // Group separator used to separate company prefix and asset reference (FNC1 in GS1-128 barcodes)
23
+ static GROUP_SEPARATOR = String.fromCodePoint(0x001d);
24
+ static URN_PREFIX = "urn:epc:id:giai:";
25
+ static fromHex = (hexIdentifier) => {
26
+ const bitIdentifier = hexToBitString(hexIdentifier);
27
+ if (bitIdentifier.substring(0, 8) !== Giai.EPC_HEADER) {
28
+ throw new Error("EPC value is not a valid GIAI value.");
29
+ }
30
+ const partition = Number.parseInt(bitIdentifier.substr(11, 3), 2);
31
+ const companyPrefixLength = Giai.PARTITION_TABLE[partition];
32
+ const filter = Number.parseInt(bitIdentifier.substring(8, 11), 2);
33
+ const companyPrefix = BigInt(`0b${bitIdentifier.substring(14, 14 + companyPrefixLength)}`);
34
+ const assetReference = BigInt(`0b${bitIdentifier.substring(14 + companyPrefixLength)}`);
35
+ return new Giai({ assetReference, companyPrefix, filter });
36
+ };
37
+ /**
38
+ *
39
+ * @param {string | null | undefined} barcodeString
40
+ * @returns
41
+ */
42
+ static fromBarcode = (barcodeString) => {
43
+ const prefixLength = Giai.BARCODE_PREFIX.length;
44
+ if (barcodeString[0] === Giai.GROUP_SEPARATOR) {
45
+ barcodeString = barcodeString.substr(1);
46
+ }
47
+ if (barcodeString.substr(0, prefixLength) !== Giai.BARCODE_PREFIX) {
48
+ throw new Error(`Barcode is not a valid GIAI barcode. (${Giai.BARCODE_PREFIX} prefix expected)`);
49
+ }
50
+ let giaiString = barcodeString.substring(prefixLength);
51
+ const gsIndex = giaiString.indexOf(Giai.GROUP_SEPARATOR);
52
+ if (gsIndex >= 0) {
53
+ return new Giai({
54
+ assetReference: giaiString.substring(gsIndex + 1),
55
+ companyPrefix: giaiString.substring(0, gsIndex),
56
+ });
57
+ }
58
+ // If no separator is found, assume a company prefix length of 7
59
+ return new Giai({
60
+ assetReference: giaiString.substring(7),
61
+ companyPrefix: giaiString.substring(0, 7),
62
+ });
63
+ };
64
+ /**
65
+ * Creates a GIAI instance from a purse identity URI (urn:epc:giai:[companyprefix].[assetreference])
66
+ * @param {string} uri EPC GIAI pure identity URI
67
+ */
68
+ static fromEpcPureIdentityUri(uri) {
69
+ if (!uri.startsWith(Giai.URN_PREFIX)) {
70
+ throw Error("URI is not a valid GIAI URI");
71
+ }
72
+ let [companyPrefix, assetReference] = uri.substring(Giai.URN_PREFIX.length).split(".");
73
+ return new Giai({
74
+ assetReference,
75
+ companyPrefix,
76
+ });
77
+ }
78
+ static fromString(value, ignoreError = false) {
79
+ try {
80
+ if (value.startsWith("0x")) {
81
+ value = value.replace("0x", "");
82
+ return Giai.fromHex(value.substring(0, 24));
83
+ // return Giai.fromHex(value.substring(6, value.length - 4));
84
+ }
85
+ else if (value.startsWith(Giai.URN_PREFIX)) {
86
+ return Giai.fromEpcPureIdentityUri(value);
87
+ }
88
+ else if (value.startsWith(Giai.BARCODE_PREFIX)) {
89
+ return Giai.fromBarcode(value);
90
+ }
91
+ else if (value.length === 24) {
92
+ return Giai.fromHex(value);
93
+ }
94
+ }
95
+ catch (ex) {
96
+ if (!ignoreError) {
97
+ throw ex;
98
+ }
99
+ }
100
+ if (!ignoreError) {
101
+ throw Error(getLocalizedString("Can't create GIAI from string. Failed to match value '{value}' with known input formats.").replace("{value}", value));
102
+ }
103
+ return undefined;
104
+ }
105
+ /**
106
+ * @type {string | number | bigint}
107
+ */
108
+ #assetReference;
109
+ /**
110
+ * @type {string | number | bigint}
111
+ */
112
+ #companyPrefix;
113
+ /**
114
+ *
115
+ * @param {GiaiProps} options
116
+ */
117
+ constructor({ assetReference, companyPrefix }) {
118
+ this.#assetReference = assetReference;
119
+ this.#companyPrefix = companyPrefix;
120
+ }
121
+ get assetReference() {
122
+ return this.#assetReference;
123
+ }
124
+ get companyPrefix() {
125
+ return this.#companyPrefix;
126
+ }
127
+ // Returns the EPC pure identity uri for this identifier - as used by EPCIS.
128
+ toEpcPureIdentityUri() {
129
+ return `${Giai.URN_PREFIX}${this.companyPrefix}.${this.assetReference}`;
130
+ }
131
+ toGiaiBarcode() {
132
+ return Giai.BARCODE_PREFIX + this.companyPrefix + Giai.GROUP_SEPARATOR + this.assetReference;
133
+ }
134
+ toEpcHex() {
135
+ let bitIdentifier = Giai.EPC_HEADER;
136
+ bitIdentifier += "000"; // Filter
137
+ bitIdentifier += (5).toString(2).padStart(3, "0"); // Partition
138
+ let companyPrefix = ["bigint", "number"].includes(typeof this.#companyPrefix)
139
+ ? this.#companyPrefix
140
+ : BigInt(this.#companyPrefix);
141
+ let assetReference = ["bigint", "number"].includes(typeof this.#assetReference)
142
+ ? this.#assetReference
143
+ : BigInt(this.#assetReference);
144
+ bitIdentifier += companyPrefix.toString(2).padStart(24, "0");
145
+ bitIdentifier += assetReference.toString(2).padStart(58, "0");
146
+ return BigInt(`0b${bitIdentifier}`).toString(16).toUpperCase();
147
+ }
148
+ }
149
+ export function hexToBitString(hexString) {
150
+ let bitString = "";
151
+ for (let i = 0; i < hexString.length; i += 2) {
152
+ const byteString = hexString.substr(i, 2);
153
+ const byteBits = Number.parseInt(byteString, 16).toString(2);
154
+ bitString += byteBits.padStart(8, "0");
155
+ }
156
+ return bitString;
157
+ }
158
+ window.Giai = Giai;
@@ -0,0 +1,10 @@
1
+ export type Beeper = (callback?: () => void) => void;
2
+ declare global {
3
+ interface Window {
4
+ webkitAudioContext: {
5
+ new (): AudioContext;
6
+ };
7
+ }
8
+ }
9
+ export declare function createBeeper(duration?: number, type?: OscillatorType): Beeper;
10
+ export declare function useBeeper(duration?: number, type?: OscillatorType): Beeper;
@@ -0,0 +1,20 @@
1
+ import { useState } from "react";
2
+ export function createBeeper(duration = 200, type = "sine") {
3
+ const ctxClass = window.AudioContext || window.webkitAudioContext;
4
+ const ctx = new ctxClass();
5
+ return (finishedCallback) => {
6
+ duration = +duration;
7
+ const osc = ctx.createOscillator();
8
+ osc.type = type;
9
+ osc.connect(ctx.destination);
10
+ osc.start();
11
+ setTimeout(() => {
12
+ osc.stop();
13
+ finishedCallback?.();
14
+ }, duration);
15
+ };
16
+ }
17
+ export function useBeeper(duration = 200, type = "sine") {
18
+ const [beeper] = useState(() => createBeeper(duration, type));
19
+ return beeper;
20
+ }
@@ -0,0 +1,23 @@
1
+ import { type SxProps, type Theme } from "@mui/material";
2
+ import { type ProjectsRecord } from "./data/dsProjects";
3
+ type ProjectSelectProps = {
4
+ className?: string;
5
+ disabled?: boolean;
6
+ id?: string;
7
+ names?: {
8
+ domain?: string;
9
+ projectId?: string;
10
+ name?: string;
11
+ };
12
+ nullable?: boolean;
13
+ readOnly?: boolean;
14
+ required?: boolean;
15
+ onChange?: (project: Partial<ProjectsRecord> | null) => void;
16
+ defaultValue?: Partial<ProjectsRecord> | null;
17
+ value?: Partial<ProjectsRecord> | null;
18
+ label?: string;
19
+ sx?: SxProps<Theme> | undefined;
20
+ report?: boolean;
21
+ };
22
+ export declare function ProjectSelect({ className, disabled, id, names, onChange, readOnly, required, defaultValue, value, label, sx, report, }: ProjectSelectProps): import("react/jsx-runtime").JSX.Element;
23
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Autocomplete, ListItem, ListItemText, TextField } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+ import { useDataWithFilter, useDebounce } from "@olenbetong/appframe-react";
5
+ import { useMemo, useRef, useState } from "react";
6
+ import { dsProjects } from "./data/dsProjects";
7
+ import { dsReportProjects } from "./data/dsReportProjects";
8
+ export function ProjectSelect({ className, disabled, id, names, onChange, readOnly, required, defaultValue, value, label, sx, report, }) {
9
+ let [selected, setSelected] = useState(defaultValue ?? null);
10
+ let [query, setQuery] = useState("");
11
+ let search = useDebounce(query);
12
+ let projects = useDataWithFilter(report ? dsReportProjects : dsProjects, search ? `[SearchColumn] LIKE '%${search}%'` : "");
13
+ let highlightRef = useRef(null);
14
+ function handleSelect(value) {
15
+ highlightRef.current = null;
16
+ if (!disabled && !readOnly) {
17
+ if (onChange) {
18
+ onChange(value);
19
+ }
20
+ else {
21
+ setSelected(value);
22
+ }
23
+ }
24
+ }
25
+ let current = value === undefined ? selected : value;
26
+ const duplicatedNames = useMemo(() => {
27
+ const nameCount = new Map();
28
+ projects.forEach((opt) => {
29
+ nameCount.set(opt.ProjectName, (nameCount.get(opt.ProjectName) || 0) + 1);
30
+ });
31
+ return new Set([...nameCount.entries()].filter(([, count]) => count > 1).map(([name]) => name));
32
+ }, [projects]);
33
+ return (_jsxs(_Fragment, { children: [_jsx(Autocomplete, { sx: sx, autoHighlight: true, autoSelect: true, clearOnEscape: true, className: className, options: projects, disabled: disabled, getOptionLabel: (option) => option?.ProjectID !== undefined ? `${option.ProjectID}: ${option.ProjectName}` : "", isOptionEqualToValue: (option, value) => option.Domain === value.Domain && option.ProjectID === value.ProjectID, onChange: (_, value) => handleSelect(value), onClose: () => setQuery(""), value: value !== undefined ? value : selected, renderInput: (params) => (_jsx(TextField, { ...params, label: label ? label : getLocalizedString("Project ID"), id: id ?? "ProjectLookup", fullWidth: true, slotProps: {
34
+ htmlInput: {
35
+ readOnly,
36
+ ...params.inputProps,
37
+ },
38
+ }, autoComplete: "off", variant: "filled", required: required, onChange: (evt) => setQuery(evt.target.value), value: query })), renderOption: (props, option) => {
39
+ let isDuplicated = option.ProjectName && duplicatedNames.has(option.ProjectName);
40
+ return (_jsx(ListItem, { ...props, children: _jsx(ListItemText, { primary: isDuplicated ? `${option.ProjectName} (${option.Domain})` : option.ProjectName, secondary: option.ProjectID }) }));
41
+ } }), _jsx("input", { name: names?.domain ?? "Domain", type: "hidden", value: current?.Domain ?? "" }), _jsx("input", { name: names?.name ?? "ProjectName", type: "hidden", value: current?.ProjectName ?? "" }), _jsx("input", { name: names?.projectId ?? "ProjectID", type: "hidden", value: current?.ProjectID ?? "" })] }));
42
+ }
@@ -0,0 +1,15 @@
1
+ export type AttachmentsRecord = {
2
+ PrimKey: string;
3
+ Domain: string;
4
+ FileName: string | null;
5
+ FileSize: number | null;
6
+ FileRef: string | null;
7
+ ProjectID: string | null;
8
+ ElementID: string | null;
9
+ Title: string | null;
10
+ ElementRef: string | null;
11
+ Damage: boolean;
12
+ DamageID: number | null;
13
+ ElementNumber: string | null;
14
+ };
15
+ export declare const dsAttachments: import("@olenbetong/appframe-data").DataObject<AttachmentsRecord>;