@nnkogift/dhis2-form-utils-dhis2-ui 0.1.0-alpha.6 → 0.1.0-alpha.7

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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolveFieldValidation, computeAgeFromDob, parseMultiTextValue, joinMultiTextValue, useOrgUnitPickerContext, useOrganisationUnitsQuery, useFieldControl, useSectionState, useFormFeedback, useFileResourceUpload } from '@nnkogift/dhis2-form-utils-hooks';
2
- import { InputField, TextAreaField, CalendarInput, Field, Radio, CheckboxField, SingleSelectField, SingleSelectOption, MultiSelectField, MultiSelectOption, Button, OrganisationUnitTree, FileInputField, NoticeBox } from '@dhis2/ui';
2
+ import { colors, InputField, TextAreaField, CalendarInput, Field, Radio, CheckboxField, SingleSelectField, SingleSelectOption, MultiSelectField, MultiSelectOption, Button, OrganisationUnitTree, FileInputField, Modal, ModalTitle, ModalContent, ModalActions, Chip, NoticeBox } from '@dhis2/ui';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useMemo, useState, useEffect, useRef } from 'react';
5
5
  import { useConfig } from '@dhis2/app-runtime';
@@ -743,123 +743,334 @@ function GeoJsonMapEditor({
743
743
  )) }) : null
744
744
  ] });
745
745
  }
746
- function D2CoordinateField({ control }) {
747
- const { fieldConfig, field, isMandatory, isDisabled } = control;
748
- const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
749
- const value = field.value;
746
+ function CoordinateLocationModal({
747
+ isOpen,
748
+ value,
749
+ label,
750
+ disabled,
751
+ onCancel,
752
+ onUpdate
753
+ }) {
754
+ const [draft, setDraft] = useState(value);
750
755
  const [lngText, setLngText] = useState(() => parseCoordinateValue(value)?.lng.toString() ?? "");
751
756
  const [latText, setLatText] = useState(() => parseCoordinateValue(value)?.lat.toString() ?? "");
752
757
  useEffect(() => {
753
- const parsed = parseCoordinateValue(value);
758
+ if (!isOpen) return;
759
+ setDraft(value);
760
+ }, [isOpen, value]);
761
+ useEffect(() => {
762
+ const parsed = parseCoordinateValue(draft);
754
763
  setLngText(parsed ? parsed.lng.toString() : "");
755
764
  setLatText(parsed ? parsed.lat.toString() : "");
756
- }, [value]);
765
+ }, [draft]);
757
766
  const commit = (lngRaw, latRaw) => {
758
767
  const lng = parseFloat(lngRaw);
759
768
  const lat = parseFloat(latRaw);
760
769
  if (Number.isFinite(lng) && Number.isFinite(lat)) {
761
- field.onChange(joinCoordinateValue(lng, lat));
770
+ setDraft(joinCoordinateValue(lng, lat));
762
771
  }
763
772
  };
764
- return /* @__PURE__ */ jsxs("fieldset", { children: [
765
- /* @__PURE__ */ jsxs("legend", { children: [
766
- fieldConfig.label,
767
- isMandatory ? " *" : ""
773
+ if (!isOpen) return null;
774
+ return /* @__PURE__ */ jsxs(Modal, { onClose: onCancel, children: [
775
+ /* @__PURE__ */ jsx(ModalTitle, { children: label }),
776
+ /* @__PURE__ */ jsxs(ModalContent, { children: [
777
+ /* @__PURE__ */ jsx(CoordinateMapPicker, { value: draft, onChange: setDraft, disabled }),
778
+ /* @__PURE__ */ jsx(
779
+ InputField,
780
+ {
781
+ name: "coordinate-modal-lng",
782
+ type: "number",
783
+ label: "Longitude",
784
+ value: lngText,
785
+ disabled,
786
+ onChange: ({ value: next }) => {
787
+ const text = next ?? "";
788
+ setLngText(text);
789
+ commit(text, latText);
790
+ }
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsx(
794
+ InputField,
795
+ {
796
+ name: "coordinate-modal-lat",
797
+ type: "number",
798
+ label: "Latitude",
799
+ value: latText,
800
+ disabled,
801
+ onChange: ({ value: next }) => {
802
+ const text = next ?? "";
803
+ setLatText(text);
804
+ commit(lngText, text);
805
+ }
806
+ }
807
+ )
768
808
  ] }),
769
- fieldConfig.description ? /* @__PURE__ */ jsx("p", { children: fieldConfig.description }) : null,
770
- /* @__PURE__ */ jsx(CoordinateMapPicker, { value, onChange: field.onChange, disabled: isDisabled }),
771
- /* @__PURE__ */ jsx(
772
- InputField,
773
- {
774
- name: `${field.name}-lng`,
775
- type: "number",
776
- label: "Longitude",
777
- value: lngText,
778
- disabled: isDisabled,
779
- onChange: ({ value: next }) => {
780
- const text = next ?? "";
781
- setLngText(text);
782
- commit(text, latText);
783
- },
784
- onBlur: field.onBlur
785
- }
786
- ),
787
- /* @__PURE__ */ jsx(
788
- InputField,
789
- {
790
- name: `${field.name}-lat`,
791
- type: "number",
792
- label: "Latitude",
793
- value: latText,
794
- disabled: isDisabled,
795
- onChange: ({ value: next }) => {
796
- const text = next ?? "";
797
- setLatText(text);
798
- commit(lngText, text);
799
- },
800
- onBlur: field.onBlur
801
- }
802
- ),
803
- validationText ? /* @__PURE__ */ jsx("p", { "data-error": hasError || void 0, "data-warning": hasWarning || void 0, children: validationText }) : null
809
+ /* @__PURE__ */ jsxs(ModalActions, { children: [
810
+ /* @__PURE__ */ jsx(Button, { onClick: onCancel, children: "Cancel" }),
811
+ /* @__PURE__ */ jsx(
812
+ Button,
813
+ {
814
+ primary: true,
815
+ disabled,
816
+ onClick: () => {
817
+ onUpdate(draft);
818
+ },
819
+ children: "Update location"
820
+ }
821
+ )
822
+ ] })
804
823
  ] });
805
824
  }
806
- function D2GeoJsonField({ control }) {
825
+ var labelStyle = {
826
+ margin: 0,
827
+ fontSize: 11,
828
+ fontWeight: 600,
829
+ textTransform: "uppercase",
830
+ letterSpacing: 0.4,
831
+ color: colors.grey700
832
+ };
833
+ var valueStyle = {
834
+ margin: 0,
835
+ fontSize: 14,
836
+ fontWeight: 500,
837
+ fontVariantNumeric: "tabular-nums"
838
+ };
839
+ function CoordinateSummary({ value }) {
840
+ const parsed = parseCoordinateValue(value);
841
+ if (!parsed) {
842
+ return /* @__PURE__ */ jsx("span", { style: { color: colors.grey700 }, children: "No location set" });
843
+ }
844
+ return /* @__PURE__ */ jsxs("dl", { style: { display: "flex", gap: 16, margin: 0 }, children: [
845
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4, alignItems: "baseline" }, children: [
846
+ /* @__PURE__ */ jsx("dt", { style: labelStyle, children: "Lat" }),
847
+ /* @__PURE__ */ jsx("dd", { style: valueStyle, children: parsed.lat.toFixed(5) })
848
+ ] }),
849
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4, alignItems: "baseline" }, children: [
850
+ /* @__PURE__ */ jsx("dt", { style: labelStyle, children: "Lng" }),
851
+ /* @__PURE__ */ jsx("dd", { style: valueStyle, children: parsed.lng.toFixed(5) })
852
+ ] })
853
+ ] });
854
+ }
855
+ function D2CoordinateField({ control }) {
807
856
  const { fieldConfig, field, isMandatory, isDisabled } = control;
808
857
  const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
809
858
  const value = field.value;
859
+ const [isOpen, setIsOpen] = useState(false);
860
+ return /* @__PURE__ */ jsxs(
861
+ Field,
862
+ {
863
+ label: fieldConfig.label,
864
+ helpText: fieldConfig.description,
865
+ warning: hasWarning,
866
+ error: hasError,
867
+ required: isMandatory,
868
+ disabled: isDisabled,
869
+ validationText,
870
+ children: [
871
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
872
+ /* @__PURE__ */ jsx(CoordinateSummary, { value }),
873
+ /* @__PURE__ */ jsx(
874
+ Button,
875
+ {
876
+ small: true,
877
+ disabled: isDisabled,
878
+ onClick: () => {
879
+ setIsOpen(true);
880
+ },
881
+ children: value ? "Change location" : "Set location"
882
+ }
883
+ )
884
+ ] }),
885
+ /* @__PURE__ */ jsx(
886
+ CoordinateLocationModal,
887
+ {
888
+ isOpen,
889
+ value,
890
+ label: fieldConfig.label,
891
+ disabled: isDisabled,
892
+ onCancel: () => {
893
+ setIsOpen(false);
894
+ },
895
+ onUpdate: (next) => {
896
+ field.onChange(next);
897
+ field.onBlur();
898
+ setIsOpen(false);
899
+ }
900
+ }
901
+ )
902
+ ]
903
+ }
904
+ );
905
+ }
906
+ function GeoJsonGeometryModal({
907
+ isOpen,
908
+ value,
909
+ label,
910
+ disabled,
911
+ onCancel,
912
+ onUpdate
913
+ }) {
914
+ const [draft, setDraft] = useState(value);
810
915
  const [textValue, setTextValue] = useState(value);
811
916
  const [textError, setTextError] = useState(void 0);
812
- return /* @__PURE__ */ jsxs("fieldset", { children: [
813
- /* @__PURE__ */ jsxs("legend", { children: [
814
- fieldConfig.label,
815
- isMandatory ? " *" : ""
816
- ] }),
817
- fieldConfig.description ? /* @__PURE__ */ jsx("p", { children: fieldConfig.description }) : null,
818
- /* @__PURE__ */ jsx(
819
- GeoJsonMapEditor,
820
- {
821
- value,
822
- onChange: (next) => {
823
- field.onChange(next);
824
- const geometry = parseGeojsonGeometry(next);
825
- setTextValue(geometry ? JSON.stringify(geometry, null, 2) : next);
826
- setTextError(void 0);
827
- },
828
- disabled: isDisabled
829
- }
830
- ),
831
- /* @__PURE__ */ jsx(
832
- TextAreaField,
833
- {
834
- name: `${field.name}-geometry`,
835
- label: "Geometry (JSON)",
836
- value: textValue,
837
- disabled: isDisabled,
838
- warning: Boolean(textError),
839
- validationText: textError,
840
- onChange: ({ value: next }) => {
841
- setTextValue(next ?? "");
842
- },
843
- onBlur: () => {
844
- if (!textValue) {
845
- field.onChange("");
846
- setTextError(void 0);
847
- return;
848
- }
849
- const geometry = parseGeojsonGeometry(textValue);
850
- if (geometry) {
851
- field.onChange(stringifyGeojsonGeometry(geometry));
917
+ useEffect(() => {
918
+ if (!isOpen) return;
919
+ setDraft(value);
920
+ setTextValue(value);
921
+ setTextError(void 0);
922
+ }, [isOpen, value]);
923
+ if (!isOpen) return null;
924
+ return /* @__PURE__ */ jsxs(Modal, { onClose: onCancel, children: [
925
+ /* @__PURE__ */ jsx(ModalTitle, { children: label }),
926
+ /* @__PURE__ */ jsxs(ModalContent, { children: [
927
+ /* @__PURE__ */ jsx(
928
+ GeoJsonMapEditor,
929
+ {
930
+ value: draft,
931
+ onChange: (next) => {
932
+ setDraft(next);
933
+ const geometry = parseGeojsonGeometry(next);
934
+ setTextValue(geometry ? JSON.stringify(geometry, null, 2) : next);
852
935
  setTextError(void 0);
853
- } else {
854
- setTextError("Not a valid GeoJSON geometry");
936
+ },
937
+ disabled
938
+ }
939
+ ),
940
+ /* @__PURE__ */ jsx(
941
+ TextAreaField,
942
+ {
943
+ name: "geojson-modal-geometry",
944
+ label: "Geometry (JSON)",
945
+ value: textValue,
946
+ disabled,
947
+ warning: Boolean(textError),
948
+ validationText: textError,
949
+ onChange: ({ value: next }) => {
950
+ setTextValue(next ?? "");
951
+ },
952
+ onBlur: () => {
953
+ if (!textValue) {
954
+ setDraft("");
955
+ setTextError(void 0);
956
+ return;
957
+ }
958
+ const geometry = parseGeojsonGeometry(textValue);
959
+ if (geometry) {
960
+ setDraft(stringifyGeojsonGeometry(geometry));
961
+ setTextError(void 0);
962
+ } else {
963
+ setTextError("Not a valid GeoJSON geometry");
964
+ }
855
965
  }
856
- field.onBlur();
857
966
  }
858
- }
859
- ),
860
- validationText ? /* @__PURE__ */ jsx("p", { "data-error": hasError || void 0, "data-warning": hasWarning || void 0, children: validationText }) : null
967
+ )
968
+ ] }),
969
+ /* @__PURE__ */ jsxs(ModalActions, { children: [
970
+ /* @__PURE__ */ jsx(Button, { onClick: onCancel, children: "Cancel" }),
971
+ /* @__PURE__ */ jsx(
972
+ Button,
973
+ {
974
+ primary: true,
975
+ disabled: disabled || Boolean(textError),
976
+ onClick: () => {
977
+ onUpdate(draft);
978
+ },
979
+ children: "Update geometry"
980
+ }
981
+ )
982
+ ] })
983
+ ] });
984
+ }
985
+ function countPositions(geometry) {
986
+ switch (geometry.type) {
987
+ case "Point":
988
+ return 1;
989
+ case "MultiPoint":
990
+ case "LineString":
991
+ return geometry.coordinates.length;
992
+ case "MultiLineString":
993
+ case "Polygon":
994
+ return geometry.coordinates.reduce((sum, ring) => sum + ring.length, 0);
995
+ case "MultiPolygon":
996
+ return geometry.coordinates.reduce(
997
+ (sum, polygon) => sum + polygon.reduce((ringSum, ring) => ringSum + ring.length, 0),
998
+ 0
999
+ );
1000
+ case "GeometryCollection":
1001
+ return geometry.geometries.reduce((sum, member) => sum + countPositions(member), 0);
1002
+ }
1003
+ }
1004
+ function GeometrySummary({ value }) {
1005
+ const geometry = parseGeojsonGeometry(value);
1006
+ if (!geometry) {
1007
+ return /* @__PURE__ */ jsx("span", { style: { color: colors.grey700 }, children: "No geometry set" });
1008
+ }
1009
+ if (geometry.type === "Point") {
1010
+ return /* @__PURE__ */ jsx(Chip, { dense: true, children: geometry.type });
1011
+ }
1012
+ const count = geometry.type === "GeometryCollection" ? geometry.geometries.length : countPositions(geometry);
1013
+ const noun = geometry.type === "GeometryCollection" ? count === 1 ? "geometry" : "geometries" : "points";
1014
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1015
+ /* @__PURE__ */ jsx(Chip, { dense: true, children: geometry.type }),
1016
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: colors.grey700 }, children: [
1017
+ count,
1018
+ " ",
1019
+ noun
1020
+ ] })
861
1021
  ] });
862
1022
  }
1023
+ function D2GeoJsonField({ control }) {
1024
+ const { fieldConfig, field, isMandatory, isDisabled } = control;
1025
+ const { validationText, hasError, hasWarning } = resolveFieldValidation(control);
1026
+ const value = field.value;
1027
+ const [isOpen, setIsOpen] = useState(false);
1028
+ return /* @__PURE__ */ jsxs(
1029
+ Field,
1030
+ {
1031
+ label: fieldConfig.label,
1032
+ helpText: fieldConfig.description,
1033
+ warning: hasWarning,
1034
+ error: hasError,
1035
+ required: isMandatory,
1036
+ disabled: isDisabled,
1037
+ validationText,
1038
+ children: [
1039
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1040
+ /* @__PURE__ */ jsx(GeometrySummary, { value }),
1041
+ /* @__PURE__ */ jsx(
1042
+ Button,
1043
+ {
1044
+ small: true,
1045
+ disabled: isDisabled,
1046
+ onClick: () => {
1047
+ setIsOpen(true);
1048
+ },
1049
+ children: value ? "Edit geometry" : "Set geometry"
1050
+ }
1051
+ )
1052
+ ] }),
1053
+ /* @__PURE__ */ jsx(
1054
+ GeoJsonGeometryModal,
1055
+ {
1056
+ isOpen,
1057
+ value,
1058
+ label: fieldConfig.label,
1059
+ disabled: isDisabled,
1060
+ onCancel: () => {
1061
+ setIsOpen(false);
1062
+ },
1063
+ onUpdate: (next) => {
1064
+ field.onChange(next);
1065
+ field.onBlur();
1066
+ setIsOpen(false);
1067
+ }
1068
+ }
1069
+ )
1070
+ ]
1071
+ }
1072
+ );
1073
+ }
863
1074
  function D2UnsupportedField({ control }) {
864
1075
  const { fieldConfig, field, widgetKind, isMandatory } = control;
865
1076
  const { validationText, hasError, hasWarning } = resolveFieldValidation(control);