@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
@@ -67,11 +67,11 @@ type DividerProps = {
67
67
 
68
68
  const Divider = forwardRef<HTMLDivElement, DividerProps>(function Divider(props: DividerProps, ref) {
69
69
  return (
70
+ // biome-ignore lint/a11y/useSemanticElements: NO!
70
71
  <div
71
72
  className={clsx("ObSplitContainer-divider", props.className)}
72
73
  tabIndex={0}
73
74
  ref={ref}
74
- // biome-ignore lint/a11y/useSemanticElements: this is allowed according to WAI-ARIA
75
75
  role="separator"
76
76
  aria-orientation={props.direction}
77
77
  aria-valuemin={props.min}
@@ -1,3 +1,21 @@
1
+ .ObValueToggleOption {
2
+ display: flex;
3
+ align-items: center;
4
+ margin: 0;
5
+ position: relative;
6
+ }
7
+
8
+ .ObValueToggleLabel {
9
+ font-size: 0.8em;
10
+ margin: 0 0.2em;
11
+ padding: 0.3em 0.5em;
12
+ text-transform: uppercase;
13
+ transition:
14
+ background 0.2s ease,
15
+ color 0.2s ease;
16
+ border-radius: 4px;
17
+ }
18
+
1
19
  .ObValueToggleGroup {
2
20
  padding: 0.25rem;
3
21
  border: 2px solid rgb(0 0 0 / 0.2);
@@ -26,13 +44,6 @@
26
44
  }
27
45
  }
28
46
 
29
- .ObValueToggleOption {
30
- display: flex;
31
- align-items: center;
32
- margin: 0;
33
- position: relative;
34
- }
35
-
36
47
  .ObValueToggleOption input {
37
48
  position: absolute;
38
49
  width: 1px;
@@ -44,15 +55,6 @@
44
55
  border: 0;
45
56
  }
46
57
 
47
- .ObValueToggleLabel {
48
- font-size: 0.8em;
49
- margin: 0 0.2em;
50
- padding: 0.3em 0.5em;
51
- text-transform: uppercase;
52
- transition: background 0.2s ease, color 0.2s ease;
53
- border-radius: 4px;
54
- }
55
-
56
58
  .ObValueToggleOption:hover input:not(:disabled) + .ObValueToggleLabel {
57
59
  cursor: pointer;
58
60
  background: #ccc;
@@ -1,7 +1,7 @@
1
1
  import "./index.css";
2
2
 
3
- import { forwardRef, useId } from "react";
4
3
  import clsx from "clsx";
4
+ import { forwardRef, useId } from "react";
5
5
 
6
6
  export type ValueToggleOption = {
7
7
  value: string | number;
@@ -30,6 +30,7 @@ export const ValueToggleGroup = forwardRef<HTMLFieldSetElement, ValueToggleGroup
30
30
  let isControlled = value !== undefined;
31
31
 
32
32
  return (
33
+ // biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: legacy component
33
34
  <fieldset className={clsx("ObValueToggleGroup", orientation, className)} role="radiogroup" ref={ref} style={style}>
34
35
  {options.map((opt) => {
35
36
  return (
@@ -0,0 +1,35 @@
1
+ import Add from "@mui/icons-material/Add";
2
+ import { Button } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+
5
+ type DamageFormButtonsProps = {
6
+ isEdit: boolean;
7
+ loading: boolean;
8
+ uploadingFile: boolean;
9
+ handleDelete: () => void;
10
+ handleCancel: () => void;
11
+ };
12
+
13
+ export function DamageFormButtonsProps({
14
+ isEdit,
15
+ loading,
16
+ uploadingFile,
17
+ handleDelete,
18
+ handleCancel,
19
+ }: DamageFormButtonsProps) {
20
+ return (
21
+ <>
22
+ {isEdit && <Button onClick={handleDelete}>{getLocalizedString("Delete")}</Button>}
23
+ <Button onClick={handleCancel}>{getLocalizedString("Cancel")}</Button>
24
+ <Button
25
+ loading={loading || uploadingFile}
26
+ loadingPosition="start"
27
+ startIcon={<Add />}
28
+ type="submit"
29
+ variant="contained"
30
+ >
31
+ {isEdit ? getLocalizedString("Save changes") : getLocalizedString("Register damage")}
32
+ </Button>
33
+ </>
34
+ );
35
+ }
@@ -0,0 +1,111 @@
1
+ import { Autocomplete, Button, TextField } from "@mui/material";
2
+ import { getLocalizedString } from "@olenbetong/appframe-core";
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ export type DescriptionInputType = {
6
+ Description?: string | null;
7
+ Cause?: string | null;
8
+ Action?: string | null;
9
+ Date?: Date;
10
+ };
11
+
12
+ type DescriptionInputProps = {
13
+ value?: DescriptionInputType | null;
14
+ defaultValue?: DescriptionInputType | null;
15
+ onCopyPrevDamage?: (value: DescriptionInputType) => void;
16
+ onChange?: (value: DescriptionInputType) => void;
17
+ };
18
+
19
+ export function DescriptionInput({ value, defaultValue, onCopyPrevDamage, onChange }: DescriptionInputProps) {
20
+ let initializedRef = useRef<boolean>(false);
21
+
22
+ let [input, setInput] = useState<DescriptionInputType | null>(defaultValue ?? null);
23
+ let inputValue = value === undefined ? input : value;
24
+
25
+ // biome-ignore lint: intentionally skipping "value" dependency as this should never run when "value" changes
26
+ useEffect(() => {
27
+ if (initializedRef.current) {
28
+ return;
29
+ }
30
+
31
+ if (value === undefined && defaultValue) {
32
+ setInput(defaultValue);
33
+ initializedRef.current = true;
34
+ }
35
+ }, [defaultValue]);
36
+
37
+ function handleCopyPrevDamage() {
38
+ let description = localStorage.getItem("ElementDamage_Description") ?? "";
39
+ let cause = localStorage.getItem("ElementDamage_Cause") ?? "";
40
+ let action = localStorage.getItem("ElementDamage_Action") ?? "";
41
+
42
+ onCopyPrevDamage?.({
43
+ Description: description,
44
+ Cause: cause,
45
+ Action: action,
46
+ });
47
+ setInput({
48
+ ...inputValue,
49
+ Description: description,
50
+ Cause: cause,
51
+ Action: action,
52
+ });
53
+ }
54
+
55
+ function handleOnChange(value: DescriptionInputType) {
56
+ onChange?.(value);
57
+ setInput(value);
58
+ }
59
+
60
+ return (
61
+ <>
62
+ <TextField
63
+ name="Date"
64
+ type="date"
65
+ value={(inputValue?.Date ?? new Date()).toISOString().split("T")[0]}
66
+ onChange={(event) => handleOnChange({ ...inputValue, Date: new Date(event.target.value) })}
67
+ id="NewElementDamage_Date"
68
+ label={getLocalizedString("Date")}
69
+ fullWidth
70
+ required
71
+ slotProps={{ inputLabel: { shrink: true } }}
72
+ />
73
+
74
+ <Button variant="contained" onClick={handleCopyPrevDamage}>
75
+ {getLocalizedString("Copy previous damage")}
76
+ </Button>
77
+
78
+ <Autocomplete
79
+ value={inputValue?.Description ?? ""}
80
+ onChange={(_event, value) => handleOnChange({ ...inputValue, Description: value ?? "" })}
81
+ options={[
82
+ getLocalizedString("Tear in element"),
83
+ getLocalizedString("Damage around lifting anchor"),
84
+ getLocalizedString("Damage on underside"),
85
+ ]}
86
+ freeSolo
87
+ renderInput={(params) => (
88
+ <TextField name="Description" required {...(params as any)} label={getLocalizedString("Description")} />
89
+ )}
90
+ />
91
+
92
+ <TextField
93
+ name="Cause"
94
+ id="NewElementDamage_Cause"
95
+ value={inputValue?.Cause ?? ""}
96
+ onChange={(event) => handleOnChange({ ...inputValue, Cause: event.currentTarget.value })}
97
+ label={getLocalizedString("Cause")}
98
+ fullWidth
99
+ />
100
+
101
+ <TextField
102
+ name="Action"
103
+ id="NewElementDamage_Action"
104
+ value={inputValue?.Action ?? ""}
105
+ onChange={(event) => handleOnChange({ ...inputValue, Action: event.currentTarget.value })}
106
+ label={getLocalizedString("Action")}
107
+ fullWidth
108
+ />
109
+ </>
110
+ );
111
+ }
@@ -0,0 +1,75 @@
1
+ import { Grid, type GridSize, Typography } from "@mui/material";
2
+ import { getLocalizedString } from "@olenbetong/appframe-core";
3
+ import { Link } from "@olenbetong/appframe-mui";
4
+ import type { DamagesRecord } from "../../data";
5
+
6
+ type SummaryItemProps = {
7
+ col: GridSize;
8
+ label?: string;
9
+ children?: React.ReactNode;
10
+ em?: boolean;
11
+ align?: "center" | "left" | "right";
12
+ padding?: string | number;
13
+ };
14
+
15
+ function SummaryItem({ col, label, children, em, align, padding }: SummaryItemProps) {
16
+ return (
17
+ <Grid size={col} sx={{ p: padding ?? 2, borderBottom: (t) => `1px solid ${t.palette.divider}` }}>
18
+ {label && (
19
+ <Typography variant="body1" fontSize="0.9rem" fontWeight={500} mb={1} textAlign={align}>
20
+ {getLocalizedString(label)}
21
+ </Typography>
22
+ )}
23
+ {children && (
24
+ <Typography
25
+ fontSize={em ? "1.4rem" : "0.9rem"}
26
+ whiteSpace="pre-line"
27
+ fontWeight={em ? 600 : undefined}
28
+ textAlign={align}
29
+ >
30
+ {children}
31
+ </Typography>
32
+ )}
33
+ </Grid>
34
+ );
35
+ }
36
+
37
+ type ElementDamagesItemProps = {
38
+ record: DamagesRecord;
39
+ };
40
+
41
+ export function ElementDamagesItem({ record }: ElementDamagesItemProps) {
42
+ return (
43
+ <>
44
+ <SummaryItem col={1.5}>
45
+ <Link
46
+ href="#"
47
+ onClick={(e) => {
48
+ e.preventDefault();
49
+ window.location.href = `${record.ID}`;
50
+ }}
51
+ >
52
+ {record.ID}
53
+ </Link>
54
+ </SummaryItem>
55
+ <SummaryItem col={2.5} align="right">
56
+ {record.Date.toISOString().split("T")[0]}
57
+ </SummaryItem>
58
+ <SummaryItem col={3}>{record.Description}</SummaryItem>
59
+ <SummaryItem col={2.5}>{record.Cause}</SummaryItem>
60
+ <SummaryItem col={2.5}>{record.Action}</SummaryItem>
61
+ </>
62
+ );
63
+ }
64
+
65
+ export function ElementDamagesHeader() {
66
+ return (
67
+ <>
68
+ <SummaryItem col={1.5} label="ID" />
69
+ <SummaryItem col={2.5} label="Date" align="right" />
70
+ <SummaryItem col={3} label="Description" />
71
+ <SummaryItem col={2.5} label="Cause" />
72
+ <SummaryItem col={2.5} label="Action" />
73
+ </>
74
+ );
75
+ }
@@ -0,0 +1,65 @@
1
+ import Add from "@mui/icons-material/Add";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Grid, useMediaQuery } from "@mui/material";
3
+ import { getLocalizedString } from "@olenbetong/appframe-core";
4
+ import { theme } from "@olenbetong/appframe-mui";
5
+ import type { DamagesRecord } from "../../data";
6
+ import { ElementDamagesHeader, ElementDamagesItem } from "./DesktopView";
7
+ import { DuplicatePhoneViewItem } from "./PhoneView";
8
+
9
+ type DuplicateElementDamageDialogProps = {
10
+ open: boolean;
11
+ onClose: () => void;
12
+ record: DamagesRecord[];
13
+ onRegisterAnyway: () => void;
14
+ loading: boolean;
15
+ };
16
+
17
+ export function DuplicateElementDamageDialog({
18
+ open,
19
+ onClose,
20
+ record,
21
+ onRegisterAnyway,
22
+ loading,
23
+ }: DuplicateElementDamageDialogProps) {
24
+ let matches = useMediaQuery(theme.breakpoints.up("lg"));
25
+ let backgroundColor = "#e9ebee";
26
+
27
+ return (
28
+ <Dialog open={open} onClose={onClose} fullWidth fullScreen={!matches}>
29
+ <DialogTitle sx={{ backgroundColor: backgroundColor }}>
30
+ {getLocalizedString("This element has one or more unfixed registered damage")}
31
+ </DialogTitle>
32
+ <DialogContent sx={{ backgroundColor: backgroundColor }}>
33
+ <Grid container>
34
+ {matches ? (
35
+ <>
36
+ <ElementDamagesHeader />
37
+ {record.map((record) => (
38
+ <ElementDamagesItem key={record.PrimKey} record={record} />
39
+ ))}
40
+ </>
41
+ ) : (
42
+ <div className="flow" style={{ width: "100%" }}>
43
+ {record.map((record) => (
44
+ <DuplicatePhoneViewItem key={record.PrimKey} record={record} />
45
+ ))}
46
+ </div>
47
+ )}
48
+ </Grid>
49
+ </DialogContent>
50
+ <DialogActions sx={{ backgroundColor: backgroundColor }}>
51
+ <Button onClick={onClose}>{getLocalizedString("Cancel")}</Button>
52
+ <Button
53
+ startIcon={<Add />}
54
+ variant="contained"
55
+ type="submit"
56
+ onClick={onRegisterAnyway}
57
+ loading={loading}
58
+ loadingPosition="start"
59
+ >
60
+ {getLocalizedString("Register damage anyway")}
61
+ </Button>
62
+ </DialogActions>
63
+ </Dialog>
64
+ );
65
+ }
@@ -0,0 +1,42 @@
1
+ import { Card, CardContent, Typography } from "@mui/material";
2
+ import { getLocalizedString } from "@olenbetong/appframe-core";
3
+ import type { DamagesRecord } from "../../data";
4
+
5
+ type DuplicatePhoneViewItemProps = {
6
+ record: DamagesRecord;
7
+ };
8
+
9
+ export function DuplicatePhoneViewItem({ record }: DuplicatePhoneViewItemProps) {
10
+ return (
11
+ <Card
12
+ variant="outlined"
13
+ onClick={() => {
14
+ window.location.href = `${record.ID}`;
15
+ }}
16
+ >
17
+ <CardContent>
18
+ <Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
19
+ {record.Date.toLocaleDateString()}
20
+ </Typography>
21
+ <Typography variant="h5" mb={1.5}>
22
+ {record.ElementID} - {record.DisplayName}
23
+ </Typography>
24
+
25
+ <Typography>{getLocalizedString("Description")}:</Typography>
26
+ <Typography mb={1.5} color="text.secondary">
27
+ {record.Description}
28
+ </Typography>
29
+
30
+ <Typography>{getLocalizedString("Cause")}: </Typography>
31
+ <Typography mb={1.5} color="text.secondary">
32
+ {record.Cause}
33
+ </Typography>
34
+
35
+ <Typography>{getLocalizedString("Action")}: </Typography>
36
+ <Typography mb={1.5} color="text.secondary">
37
+ {record.Action}
38
+ </Typography>
39
+ </CardContent>
40
+ </Card>
41
+ );
42
+ }
@@ -0,0 +1,105 @@
1
+ import { useDataWithFilter } from "@olenbetong/appframe-react";
2
+ import { forwardRef, useImperativeHandle, useMemo, useState } from "react";
3
+ import { type DamagesRecord, dsDamagesDuplicateCheck } from "../../data";
4
+ import type { Element } from "../../ElementLookup";
5
+ import { DuplicateElementDamageDialog } from "./DublicateElementDamageDialog";
6
+
7
+ export { DuplicateElementDamageDialog } from "./DublicateElementDamageDialog";
8
+
9
+ export type DuplicateElementDamageHandle = {
10
+ onClose: () => void;
11
+ checkDuplicate: () => Promise<DamagesRecord[] | null>;
12
+ record: DamagesRecord[];
13
+ };
14
+
15
+ type DuplicateElementDamageProps = {
16
+ element: Element | null;
17
+ /**
18
+ * External control of dialog visibility
19
+ */
20
+ open?: boolean;
21
+ onOpenChange?: (value: boolean) => void;
22
+ /**
23
+ * Skip duplicate checking (used in edit mode)
24
+ */
25
+ disableCheck?: boolean;
26
+ /**
27
+ * Loading state for submit button
28
+ */
29
+ loading?: boolean;
30
+ /**
31
+ * Called when duplicate or duplicates are found
32
+ * @param record Array with all of the duplicate damages
33
+ * @returns void
34
+ */
35
+ onDuplicateFound?: (record: DamagesRecord[]) => void;
36
+ /**
37
+ * Called when user presses register anyway in dialog
38
+ * @returns void
39
+ */
40
+ onRegisterAnyway?: () => void;
41
+ };
42
+
43
+ export const DuplicateElementDamage = forwardRef<DuplicateElementDamageHandle, DuplicateElementDamageProps>(
44
+ function DuplicateElementDamage(
45
+ {
46
+ element,
47
+ open: controlledOpen,
48
+ onOpenChange,
49
+ disableCheck = false,
50
+ loading = false,
51
+ onRegisterAnyway,
52
+ }: DuplicateElementDamageProps,
53
+ ref,
54
+ ) {
55
+ let [record, setRecord] = useState<DamagesRecord[]>([]);
56
+ let [uncontrolledOpen, setUncontrolledOpen] = useState<boolean>(false);
57
+ let open = controlledOpen === undefined ? uncontrolledOpen : controlledOpen;
58
+
59
+ let whereClause = useMemo(() => {
60
+ if (!element) {
61
+ return "1=0";
62
+ }
63
+ return `Domain = '${element.Domain}' AND ElementID = '${element.ElementID}' AND ProjectID = '${element.ProjectID}'`;
64
+ }, [element]);
65
+
66
+ let duplicateRecord = useDataWithFilter(dsDamagesDuplicateCheck, whereClause, "whereClause");
67
+
68
+ async function runDuplicateCheck(): Promise<DamagesRecord[] | null> {
69
+ // If we are editing a record, we don't need to check for duplicates
70
+ if (disableCheck) {
71
+ return null;
72
+ }
73
+
74
+ if (duplicateRecord.length > 0) {
75
+ setRecord(duplicateRecord);
76
+ setUncontrolledOpen(true);
77
+
78
+ return duplicateRecord;
79
+ }
80
+
81
+ return null;
82
+ }
83
+
84
+ function handleOpenChange(value: boolean) {
85
+ setUncontrolledOpen(value);
86
+ onOpenChange?.(value);
87
+ }
88
+
89
+ useImperativeHandle(ref, () => ({
90
+ onClose: () => setUncontrolledOpen(false),
91
+ checkDuplicate: () => runDuplicateCheck(),
92
+ record: record,
93
+ }));
94
+
95
+ return (
96
+ <DuplicateElementDamageDialog
97
+ open={open}
98
+ onClose={() => handleOpenChange(false)}
99
+ record={record}
100
+ onRegisterAnyway={onRegisterAnyway ?? (() => {})}
101
+ loading={loading ?? false}
102
+ />
103
+ );
104
+ },
105
+ );
@@ -0,0 +1,41 @@
1
+ import { type SxProps, TextField, type Theme } from "@mui/material";
2
+ import { getLocalizedString } from "@olenbetong/appframe-core";
3
+ import { useEffect, useState } from "react";
4
+
5
+ type ElementNumberTextFieldProps = {
6
+ value?: string | null;
7
+ defaultValue?: string | null;
8
+ onChange?: (value: string) => void;
9
+ disabled?: boolean;
10
+ sx?: SxProps<Theme>;
11
+ };
12
+
13
+ export function ElementNumberTextField({ value, defaultValue, onChange, disabled, sx }: ElementNumberTextFieldProps) {
14
+ let [elementNumber, setElementNumber] = useState<string>(defaultValue ?? "");
15
+ let elementNumberValue = value === undefined ? elementNumber : value;
16
+
17
+ useEffect(() => {
18
+ if (value === undefined && defaultValue) {
19
+ setElementNumber(defaultValue);
20
+ }
21
+ }, [defaultValue, value]);
22
+
23
+ function handleChange(value: string) {
24
+ onChange?.(value);
25
+ setElementNumber(value);
26
+ }
27
+
28
+ return (
29
+ <TextField
30
+ name="ElementNumber"
31
+ id="NewElementDamage_ElementNr"
32
+ value={elementNumberValue}
33
+ onChange={(event) => handleChange(event.currentTarget.value)}
34
+ label={getLocalizedString("Element number")}
35
+ fullWidth
36
+ autoComplete="off"
37
+ disabled={disabled ?? false}
38
+ sx={{ ...sx }}
39
+ />
40
+ );
41
+ }
@@ -0,0 +1,13 @@
1
+ type HiddenDamageFieldsProps = {
2
+ primKey: string;
3
+ ID: number;
4
+ };
5
+
6
+ export function HiddenDamageFields({ primKey, ID }: HiddenDamageFieldsProps) {
7
+ return (
8
+ <>
9
+ <input type="hidden" name="PrimKey" value={primKey} />
10
+ <input type="hidden" name="ID" value={ID} />
11
+ </>
12
+ );
13
+ }
@@ -0,0 +1,100 @@
1
+ import CheckIcon from "@mui/icons-material/Check";
2
+ import { Button, Checkbox, FormControlLabel, Typography } from "@mui/material";
3
+ import { getLocalizedString, localize } from "@olenbetong/appframe-core";
4
+
5
+ import { useEffect, useRef, useState } from "react";
6
+
7
+ import { procApproveDamage } from "../data";
8
+
9
+ const formatter = new Intl.DateTimeFormat(af.userSession.culture, {
10
+ day: "numeric",
11
+ month: "long",
12
+ year: "numeric",
13
+ hour: "2-digit",
14
+ minute: "2-digit",
15
+ timeZone: "UTC",
16
+ });
17
+
18
+ type RegisterFixType = {
19
+ ID: number;
20
+ RegisteredFixFullName: string | null;
21
+ RegisteredFix: Date | null;
22
+ RegisteredFixBy: string | null;
23
+ };
24
+
25
+ type ApproveDamageProcedureRecord = {
26
+ Table: Array<Required<RegisterFixType>>;
27
+ };
28
+
29
+ type RegisterFixProps = {
30
+ value?: RegisterFixType | null;
31
+ defaultValue?: RegisterFixType | null;
32
+ onFix?: (ID: number) => void;
33
+ edit?: boolean;
34
+ };
35
+
36
+ export function RegisterFix({ value, defaultValue, onFix, edit }: RegisterFixProps) {
37
+ let [loading, setLoading] = useState<boolean>(false);
38
+ let initializedRef = useRef<boolean>(false);
39
+
40
+ let [fix, setFix] = useState<RegisterFixType | null>(defaultValue ?? null);
41
+ let fixValue = value === undefined ? fix : value;
42
+
43
+ // biome-ignore lint: intentionally skipping "value" dependency as this should never run when "value" changes
44
+ useEffect(() => {
45
+ if (initializedRef.current) {
46
+ return;
47
+ }
48
+
49
+ if (value === undefined && defaultValue) {
50
+ setFix(defaultValue);
51
+ initializedRef.current = true;
52
+ }
53
+ }, [defaultValue]);
54
+
55
+ async function handleRegisterFix() {
56
+ try {
57
+ if (confirm(getLocalizedString("Are you sure you want to register a fix for this damage?"))) {
58
+ setLoading(true);
59
+ let record = (await procApproveDamage.execute({ ID: fixValue?.ID })) as ApproveDamageProcedureRecord;
60
+
61
+ setFix({ ...record.Table[0] });
62
+
63
+ setLoading(false);
64
+ onFix?.(fixValue?.ID ?? -1);
65
+ }
66
+ } catch (error) {
67
+ alert((error as Error).message);
68
+ }
69
+ }
70
+
71
+ return (
72
+ <>
73
+ {edit ? (
74
+ <Button
75
+ loading={loading}
76
+ loadingPosition="end"
77
+ endIcon={<CheckIcon />}
78
+ onClick={handleRegisterFix}
79
+ disabled={!!fixValue?.RegisteredFix}
80
+ variant="contained"
81
+ >
82
+ {getLocalizedString("Register fix")}
83
+ </Button>
84
+ ) : (
85
+ <FormControlLabel
86
+ control={<Checkbox />}
87
+ label={getLocalizedString("Register fixed")}
88
+ name="RegisteredFix"
89
+ id="NewElementDamage_RegisteredFix"
90
+ />
91
+ )}
92
+
93
+ {fixValue?.RegisteredFix && (
94
+ <Typography mb={1.5} color="text.secondary">
95
+ {localize`Registered fix by ${fixValue.RegisteredFixFullName ?? fixValue.RegisteredFixBy} on the ${formatter.format(fixValue.RegisteredFix)}`}
96
+ </Typography>
97
+ )}
98
+ </>
99
+ );
100
+ }