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

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