@olenbetong/synergi-react 2.2.3 → 2.3.1

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 (116) hide show
  1. package/es/components/FilePond.d.ts +8 -5
  2. package/es/components/FilePond.js +47 -20
  3. package/es/elements/ElementDamage/DamageFormButtons.d.ts +9 -0
  4. package/es/elements/ElementDamage/DamageFormButtons.js +7 -0
  5. package/es/elements/ElementDamage/DescriptionInput.d.ts +14 -0
  6. package/es/elements/ElementDamage/DescriptionInput.js +44 -0
  7. package/es/elements/{ElementDamageForm/DuplicateDamage/DuplicateElementDamageDialog.d.ts → ElementDamage/DuplicateDamage/DublicateElementDamageDialog.d.ts} +4 -3
  8. package/es/elements/ElementDamage/DuplicateDamage/DublicateElementDamageDialog.js +12 -0
  9. package/es/elements/ElementDamage/DuplicateDamage/index.d.ts +36 -0
  10. package/es/elements/ElementDamage/DuplicateDamage/index.js +40 -0
  11. package/es/elements/ElementDamage/ElementNumberTextField.d.ts +10 -0
  12. package/es/elements/ElementDamage/ElementNumberTextField.js +18 -0
  13. package/es/elements/ElementDamage/HiddenDamageFields.d.ts +6 -0
  14. package/es/elements/ElementDamage/HiddenDamageFields.js +4 -0
  15. package/es/elements/ElementDamage/RegisterFix.d.ts +14 -0
  16. package/es/elements/ElementDamage/RegisterFix.js +45 -0
  17. package/es/elements/ElementDamage/SubmitButtons.d.ts +20 -0
  18. package/es/elements/ElementDamage/SubmitButtons.js +7 -0
  19. package/es/elements/ElementDamage/TransportElementSelect.d.ts +7 -0
  20. package/es/elements/ElementDamage/TransportElementSelect.js +16 -0
  21. package/es/elements/ElementLookup.d.ts +20 -0
  22. package/es/elements/ElementLookup.js +30 -0
  23. package/es/elements/{AddElementDialog → ElementLookupDialog}/Manual/ManualForm.js +4 -2
  24. package/es/elements/{AddElementDialog → ElementLookupDialog}/getElement.d.ts +1 -0
  25. package/es/elements/{AddElementDialog → ElementLookupDialog}/getElement.js +1 -0
  26. package/es/elements/{AddElementDialog → ElementLookupDialog}/index.d.ts +5 -5
  27. package/es/elements/{AddElementDialog → ElementLookupDialog}/index.js +4 -1
  28. package/es/elements/ProjectSelect.js +9 -2
  29. package/es/elements/data/dsDamages.d.ts +1 -1
  30. package/es/elements/data/dsDamages.js +2 -2
  31. package/es/elements/data/dsElements.d.ts +2 -0
  32. package/es/elements/data/dsElements.js +11 -1
  33. package/es/elements/data/dsTransportElements.d.ts +10 -0
  34. package/es/elements/data/dsTransportElements.js +57 -0
  35. package/es/elements/data/index.d.ts +5 -1
  36. package/es/elements/data/index.js +5 -1
  37. package/es/elements/data/procDeleteAttachment.d.ts +2 -0
  38. package/es/elements/data/procDeleteAttachment.js +18 -0
  39. package/es/elements/data/procDeleteDamage.d.ts +2 -0
  40. package/es/elements/data/procDeleteDamage.js +18 -0
  41. package/es/elements/data/procRemoveDamageFix.d.ts +2 -0
  42. package/es/elements/data/procRemoveDamageFix.js +18 -0
  43. package/es/elements/data/procRestoreAttachments.d.ts +2 -0
  44. package/es/elements/data/procRestoreAttachments.js +18 -0
  45. package/es/elements/index.d.ts +8 -2
  46. package/es/elements/index.js +8 -2
  47. package/es/index.d.ts +1 -1
  48. package/es/index.js +1 -1
  49. package/package.json +4 -4
  50. package/src/components/FilePond.tsx +73 -26
  51. package/src/elements/ElementDamage/DamageFormButtons.tsx +35 -0
  52. package/src/elements/ElementDamage/DescriptionInput.tsx +111 -0
  53. package/src/elements/{ElementDamageForm/DuplicateDamage/DuplicateElementDamageDialog.tsx → ElementDamage/DuplicateDamage/DublicateElementDamageDialog.tsx} +24 -9
  54. package/src/elements/ElementDamage/DuplicateDamage/index.tsx +105 -0
  55. package/src/elements/ElementDamage/ElementNumberTextField.tsx +41 -0
  56. package/src/elements/ElementDamage/HiddenDamageFields.tsx +13 -0
  57. package/src/elements/ElementDamage/RegisterFix.tsx +100 -0
  58. package/src/elements/ElementDamage/SubmitButtons.tsx +40 -0
  59. package/src/elements/ElementDamage/TransportElementSelect.tsx +52 -0
  60. package/src/elements/ElementLookup.tsx +90 -0
  61. package/src/elements/{AddElementDialog → ElementLookupDialog}/Manual/ManualForm.tsx +5 -2
  62. package/src/elements/{AddElementDialog → ElementLookupDialog}/getElement.ts +2 -0
  63. package/src/elements/{AddElementDialog → ElementLookupDialog}/index.tsx +9 -5
  64. package/src/elements/ProjectSelect.tsx +13 -3
  65. package/src/elements/data/dsDamages.ts +3 -3
  66. package/src/elements/data/dsElements.ts +13 -1
  67. package/src/elements/data/dsTransportElements.ts +68 -0
  68. package/src/elements/data/index.ts +5 -1
  69. package/src/elements/data/procDeleteAttachment.ts +19 -0
  70. package/src/elements/data/procDeleteDamage.ts +19 -0
  71. package/src/elements/data/procRemoveDamageFix.ts +19 -0
  72. package/src/elements/data/procRestoreAttachments.ts +19 -0
  73. package/src/elements/index.ts +8 -3
  74. package/src/index.ts +1 -1
  75. package/es/elements/ElementDamageForm/DamageFormFields.d.ts +0 -12
  76. package/es/elements/ElementDamageForm/DamageFormFields.js +0 -39
  77. package/es/elements/ElementDamageForm/DuplicateDamage/DuplicateElementDamageDialog.js +0 -11
  78. package/es/elements/ElementDamageForm/index.d.ts +0 -13
  79. package/es/elements/ElementDamageForm/index.js +0 -219
  80. package/src/elements/ElementDamageForm/DamageFormFields.tsx +0 -149
  81. package/src/elements/ElementDamageForm/index.tsx +0 -397
  82. /package/es/elements/{ElementDamageForm → ElementDamage}/DuplicateDamage/DesktopView.d.ts +0 -0
  83. /package/es/elements/{ElementDamageForm → ElementDamage}/DuplicateDamage/DesktopView.js +0 -0
  84. /package/es/elements/{ElementDamageForm → ElementDamage}/DuplicateDamage/PhoneView.d.ts +0 -0
  85. /package/es/elements/{ElementDamageForm → ElementDamage}/DuplicateDamage/PhoneView.js +0 -0
  86. /package/es/elements/{ElementDamageForm → ElementDamage}/index.css +0 -0
  87. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/DeadIcon.d.ts +0 -0
  88. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/DeadIcon.js +0 -0
  89. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/Error.d.ts +0 -0
  90. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/Error.js +0 -0
  91. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/index.d.ts +0 -0
  92. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/index.js +0 -0
  93. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/useScanner.d.ts +0 -0
  94. /package/es/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/useScanner.js +0 -0
  95. /package/es/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/index.css +0 -0
  96. /package/es/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/index.d.ts +0 -0
  97. /package/es/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/index.js +0 -0
  98. /package/es/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/useHIDScanner.d.ts +0 -0
  99. /package/es/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/useHIDScanner.js +0 -0
  100. /package/es/elements/{AddElementDialog → ElementLookupDialog}/Manual/ManualForm.d.ts +0 -0
  101. /package/es/elements/{AddElementDialog → ElementLookupDialog}/utils/Giai.d.ts +0 -0
  102. /package/es/elements/{AddElementDialog → ElementLookupDialog}/utils/Giai.js +0 -0
  103. /package/es/elements/{AddElementDialog → ElementLookupDialog}/utils/beep.d.ts +0 -0
  104. /package/es/elements/{AddElementDialog → ElementLookupDialog}/utils/beep.js +0 -0
  105. /package/src/elements/{ElementDamageForm → ElementDamage}/DuplicateDamage/DesktopView.tsx +0 -0
  106. /package/src/elements/{ElementDamageForm → ElementDamage}/DuplicateDamage/PhoneView.tsx +0 -0
  107. /package/src/elements/{ElementDamageForm → ElementDamage}/index.css +0 -0
  108. /package/src/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/DeadIcon.tsx +0 -0
  109. /package/src/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/Error.tsx +0 -0
  110. /package/src/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/index.tsx +0 -0
  111. /package/src/elements/{AddElementDialog → ElementLookupDialog}/BarcodeScanner/useScanner.ts +0 -0
  112. /package/src/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/index.css +0 -0
  113. /package/src/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/index.tsx +0 -0
  114. /package/src/elements/{AddElementDialog → ElementLookupDialog}/HIDScanner/useHIDScanner.ts +0 -0
  115. /package/src/elements/{AddElementDialog → ElementLookupDialog}/utils/Giai.ts +0 -0
  116. /package/src/elements/{AddElementDialog → ElementLookupDialog}/utils/beep.ts +0 -0
@@ -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
+ }
@@ -8,20 +8,28 @@ import { DuplicatePhoneViewItem } from "./PhoneView";
8
8
 
9
9
  type DuplicateElementDamageDialogProps = {
10
10
  open: boolean;
11
- onClose: (value: boolean) => void;
11
+ onClose: () => void;
12
12
  record: DamagesRecord[];
13
- fallBack: () => void;
13
+ onRegisterAnyway: () => void;
14
+ loading: boolean;
14
15
  };
15
16
 
16
- export function DuplicateElementDamageDialog({ open, onClose, record, fallBack }: DuplicateElementDamageDialogProps) {
17
+ export function DuplicateElementDamageDialog({
18
+ open,
19
+ onClose,
20
+ record,
21
+ onRegisterAnyway,
22
+ loading,
23
+ }: DuplicateElementDamageDialogProps) {
17
24
  let matches = useMediaQuery(theme.breakpoints.up("lg"));
25
+ let backgroundColor = "#e9ebee";
18
26
 
19
27
  return (
20
- <Dialog open={open} onClose={() => onClose(false)} fullWidth fullScreen={!matches}>
21
- <DialogTitle sx={{ backgroundColor: "#e9ebee" }}>
28
+ <Dialog open={open} onClose={onClose} fullWidth fullScreen={!matches}>
29
+ <DialogTitle sx={{ backgroundColor: backgroundColor }}>
22
30
  {getLocalizedString("This element has one or more unfixed registered damage")}
23
31
  </DialogTitle>
24
- <DialogContent sx={{ backgroundColor: "#e9ebee" }}>
32
+ <DialogContent sx={{ backgroundColor: backgroundColor }}>
25
33
  <Grid container>
26
34
  {matches ? (
27
35
  <>
@@ -39,9 +47,16 @@ export function DuplicateElementDamageDialog({ open, onClose, record, fallBack }
39
47
  )}
40
48
  </Grid>
41
49
  </DialogContent>
42
- <DialogActions sx={{ backgroundColor: "#e9ebee" }}>
43
- <Button onClick={fallBack}>{getLocalizedString("Cancel")}</Button>
44
- <Button startIcon={<Add />} variant="contained" type="submit">
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
+ >
45
60
  {getLocalizedString("Register damage anyway")}
46
61
  </Button>
47
62
  </DialogActions>
@@ -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
+ }
@@ -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,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
+ }
@@ -1,7 +1,7 @@
1
1
  import Add from "@mui/icons-material/Add";
2
2
  import { Alert, Button, TextField } from "@mui/material";
3
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
4
- import { useRef, useState } from "react";
4
+ import { useId, useRef, useState } from "react";
5
5
  import type { ProjectsRecord } from "../../data/dsProjects";
6
6
  import { ProjectSelect } from "../../ProjectSelect";
7
7
  import { type ElementDetails, getElement } from "../getElement";
@@ -22,6 +22,7 @@ export type AddElementManualFormProps = {
22
22
  };
23
23
 
24
24
  export function AddElementManualForm({ onAdd, mode, defaultProject }: AddElementManualFormProps) {
25
+ let id = useId();
25
26
  let buttonRef = useRef<HTMLButtonElement>(null);
26
27
  let [working, setWorking] = useState(false);
27
28
  let [elementId, setElementId] = useState<string>("");
@@ -30,6 +31,7 @@ export function AddElementManualForm({ onAdd, mode, defaultProject }: AddElement
30
31
  async function handleSubmit(evt: React.FormEvent<HTMLFormElement>) {
31
32
  try {
32
33
  evt.preventDefault();
34
+ evt.stopPropagation();
33
35
  let formData = new FormData(evt.target as HTMLFormElement);
34
36
  let elements = formData.get("ElementID");
35
37
  let domain = formData.get("Domain");
@@ -64,7 +66,7 @@ export function AddElementManualForm({ onAdd, mode, defaultProject }: AddElement
64
66
  }
65
67
 
66
68
  return (
67
- <form onSubmit={handleSubmit} method="post">
69
+ <form id={id} onSubmit={handleSubmit} method="post">
68
70
  <Alert severity="warning" sx={{ mb: 2 }}>
69
71
  {getLocalizedString(
70
72
  "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.",
@@ -90,6 +92,7 @@ export function AddElementManualForm({ onAdd, mode, defaultProject }: AddElement
90
92
  />
91
93
 
92
94
  <Button
95
+ form={id}
93
96
  ref={buttonRef}
94
97
  color="primary"
95
98
  variant="contained"
@@ -5,6 +5,7 @@ import Giai from "./utils/Giai";
5
5
  export type AddElementSource = "manual" | "camera-barcode" | "loading-list" | "hid-barcode" | "hid-rfid";
6
6
 
7
7
  export type ElementDetails = {
8
+ Domain: string;
8
9
  PrimKey: string;
9
10
  Created: Date;
10
11
  ProjectID: string;
@@ -20,6 +21,7 @@ const elementsDataHandler = new DataProviderHandlerAPI<ElementDetails>({
20
21
  fields: [
21
22
  { name: "PrimKey", type: "string" },
22
23
  { name: "Created", type: "datetime" },
24
+ { name: "Domain", type: "string" },
23
25
  { name: "ProjectID", type: "string" },
24
26
  { name: "ProjectName", type: "string" },
25
27
  { name: "ProjectDomain", type: "string" },