Skip to content

Commit a1b1412

Browse files
committed
Add PriorityBoundsDataContext for shared state management across priority fields. Integrate context in EditChallenge, CustomPriorityBoundsField, and BoundsSelector components to enhance polygon handling and improve map interactions.
1 parent 911bbe8 commit a1b1412

4 files changed

Lines changed: 212 additions & 67 deletions

File tree

src/components/AdminPane/Manage/ManageChallenges/EditChallenge/EditChallenge.jsx

Lines changed: 69 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
} from "./PriorityRuleGroup";
6363
import WorkflowSteps from "./WorkflowSteps";
6464
import "./EditChallenge.scss";
65+
import { PriorityBoundsDataProvider } from "../../../../Custom/RJSFFormFieldAdapter/context/PriorityBoundsDataContext";
6566

6667
/**
6768
* EditChallenge manages a simple workflow for creating/editing a Challenge. We
@@ -904,72 +905,75 @@ export class EditChallenge extends Component {
904905
isLongForm={this.isLongForm()}
905906
setIsLongForm={this.setIsLongForm}
906907
/>
907-
<Form
908-
schema={activeStep.jsSchema(
909-
this.props.intl,
910-
this.props.user,
911-
challengeData,
912-
this.state.extraErrors,
913-
{
914-
longForm: this.isLongForm(),
915-
},
916-
)}
917-
uiSchema={activeStep.uiSchema(
918-
this.props.intl,
919-
this.props.user,
920-
challengeData,
921-
this.state.extraErrors,
922-
{
923-
longForm: this.isLongForm(),
924-
collapsedGroups: this.collapsedFormGroups(),
925-
toggleCollapsed: this.toggleCollapsedFormGroup,
926-
expandedFieldGroups: this.state.expandedFieldGroups,
927-
setFieldGroupExpanded: this.setFieldGroupExpanded,
928-
},
929-
)}
930-
className="form"
931-
validate={(formData, errors) => this.validate(formData, errors, activeStep)}
932-
transformErrors={this.transformErrors(this.props.intl)}
933-
widgets={{
934-
SelectWidget: CustomSelectWidget,
935-
TextWidget: CustomTextWidget,
936-
automatedEditsCheckbox: CustomCheckboxField,
937-
}}
938-
ArrayFieldTemplate={CustomArrayFieldTemplate}
939-
FieldTemplate={CustomFieldTemplate}
940-
fields={customFields}
941-
tagType={"challenges"}
942-
noHtml5Validate
943-
showErrorList={false}
944-
formData={challengeData}
945-
formContext={_merge(this.state.formContext, {
946-
bounding: challengeData?.bounding,
947-
buttonAction: BoundsSelectorModal,
948-
})}
949-
onChange={this.changeHandler}
950-
onSubmit={(formData) => this.handleSubmit(formData, nextStep)}
951-
onError={this.errorHandler}
952-
extraErrors={this.state.extraErrors}
953-
>
954-
{this.hasTaskStyleRuleErrors() && activeStep.id === "Properties" && (
955-
<div className="mr-text-red-light mr-mb-4">
956-
<FormattedMessage {...messages.customTaskStylesError} />
957-
</div>
958-
)}
959-
960-
<StepNavigation
961-
activeStep={activeStep}
962-
prevStep={(stepName) => {
963-
this.isFinishing = false;
964-
prevStep(stepName);
908+
<PriorityBoundsDataProvider initialData={challengeData}>
909+
<Form
910+
schema={activeStep.jsSchema(
911+
this.props.intl,
912+
this.props.user,
913+
challengeData,
914+
this.state.extraErrors,
915+
{
916+
longForm: this.isLongForm(),
917+
},
918+
)}
919+
uiSchema={activeStep.uiSchema(
920+
this.props.intl,
921+
this.props.user,
922+
challengeData,
923+
this.state.extraErrors,
924+
{
925+
longForm: this.isLongForm(),
926+
collapsedGroups: this.collapsedFormGroups(),
927+
toggleCollapsed: this.toggleCollapsedFormGroup,
928+
expandedFieldGroups: this.state.expandedFieldGroups,
929+
setFieldGroupExpanded: this.setFieldGroupExpanded,
930+
},
931+
)}
932+
className="form"
933+
validate={(formData, errors) => this.validate(formData, errors, activeStep)}
934+
transformErrors={this.transformErrors(this.props.intl)}
935+
widgets={{
936+
SelectWidget: CustomSelectWidget,
937+
TextWidget: CustomTextWidget,
938+
automatedEditsCheckbox: CustomCheckboxField,
965939
}}
966-
finish={() => {
967-
this.isFinishing = true;
968-
this.handleSubmit();
969-
return false;
970-
}}
971-
/>
972-
</Form>
940+
ArrayFieldTemplate={CustomArrayFieldTemplate}
941+
FieldTemplate={CustomFieldTemplate}
942+
fields={customFields}
943+
tagType={"challenges"}
944+
noHtml5Validate
945+
showErrorList={false}
946+
formData={challengeData}
947+
formContext={_merge(this.state.formContext, {
948+
bounding: challengeData?.bounding,
949+
buttonAction: BoundsSelectorModal,
950+
formData: challengeData,
951+
})}
952+
onChange={this.changeHandler}
953+
onSubmit={(formData) => this.handleSubmit(formData, nextStep)}
954+
onError={this.errorHandler}
955+
extraErrors={this.state.extraErrors}
956+
>
957+
{this.hasTaskStyleRuleErrors() && activeStep.id === "Properties" && (
958+
<div className="mr-text-red-light mr-mb-4">
959+
<FormattedMessage {...messages.customTaskStylesError} />
960+
</div>
961+
)}
962+
963+
<StepNavigation
964+
activeStep={activeStep}
965+
prevStep={(stepName) => {
966+
this.isFinishing = false;
967+
prevStep(stepName);
968+
}}
969+
finish={() => {
970+
this.isFinishing = true;
971+
this.handleSubmit();
972+
return false;
973+
}}
974+
/>
975+
</Form>
976+
</PriorityBoundsDataProvider>
973977
</div>
974978
</div>
975979
</BreadcrumbWrapper>

src/components/Custom/RJSFFormFieldAdapter/CustomPriorityBoundsField.jsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "leaflet-lasso";
66
import BoundsSelector from "./components/BoundsSelector";
77
import { FormattedMessage } from "react-intl";
88
import messages from "./Messages";
9+
import { usePriorityBoundsData } from "./context/PriorityBoundsDataContext";
910

1011
const MapViewPreserver = ({ onViewChange }) => {
1112
const map = useMap();
@@ -47,6 +48,33 @@ const CustomPriorityBoundsField = (props) => {
4748
: "default";
4849

4950
const formData = props.formData || [];
51+
const { priorityBoundsData, updatePriorityBounds } = usePriorityBoundsData();
52+
53+
// Update context when this field's data changes
54+
useEffect(() => {
55+
updatePriorityBounds(priorityType, formData);
56+
}, [formData, priorityType, updatePriorityBounds]);
57+
58+
// Also sync with form context if available
59+
useEffect(() => {
60+
const completeFormData = props.formContext?.formData || {};
61+
if (
62+
completeFormData.highPriorityBounds ||
63+
completeFormData.mediumPriorityBounds ||
64+
completeFormData.lowPriorityBounds
65+
) {
66+
// Update all priority bounds in context
67+
if (completeFormData.highPriorityBounds && priorityType !== "high") {
68+
updatePriorityBounds("high", completeFormData.highPriorityBounds);
69+
}
70+
if (completeFormData.mediumPriorityBounds && priorityType !== "medium") {
71+
updatePriorityBounds("medium", completeFormData.mediumPriorityBounds);
72+
}
73+
if (completeFormData.lowPriorityBounds && priorityType !== "low") {
74+
updatePriorityBounds("low", completeFormData.lowPriorityBounds);
75+
}
76+
}
77+
}, [props.formContext?.formData, priorityType, updatePriorityBounds]);
5078

5179
// Show map if there are existing polygons
5280
useEffect(() => {
@@ -114,7 +142,12 @@ const CustomPriorityBoundsField = (props) => {
114142
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
115143
/>
116144

117-
<BoundsSelector value={formData} onChange={handleChange} priorityType={priorityType} />
145+
<BoundsSelector
146+
value={formData}
147+
onChange={handleChange}
148+
priorityType={priorityType}
149+
allPriorityBounds={priorityBoundsData}
150+
/>
118151
</MapContainer>
119152
</div>
120153
)}

src/components/Custom/RJSFFormFieldAdapter/components/BoundsSelector.jsx

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ const getColor = (priorityType, state = "base") => {
1515
return PRIORITY_COLORS[priorityType]?.[state] || PRIORITY_COLORS.default[state];
1616
};
1717

18-
const BoundsSelector = ({ value, onChange, priorityType }) => {
18+
const BoundsSelector = ({ value, onChange, priorityType, allPriorityBounds = {} }) => {
1919
const map = useMap();
2020
const [selecting, setSelecting] = useState(false);
2121
const [showRemoveModal, setShowRemoveModal] = useState(false);
2222
const [selectedPolygon, setSelectedPolygon] = useState(null);
2323

2424
const featureGroupRef = useRef(null);
25+
const otherPriorityGroupRef = useRef(null);
2526
const lassoRef = useRef(null);
2627
const controlRef = useRef(null);
2728

@@ -30,14 +31,19 @@ const BoundsSelector = ({ value, onChange, priorityType }) => {
3031
if (!map) return;
3132

3233
featureGroupRef.current = new L.FeatureGroup();
34+
otherPriorityGroupRef.current = new L.FeatureGroup();
3335
map.addLayer(featureGroupRef.current);
36+
map.addLayer(otherPriorityGroupRef.current);
3437

3538
// Create custom control
3639
createMapControl();
3740

3841
// Restore existing polygons
3942
restorePolygons();
4043

44+
// Display other priority polygons
45+
displayOtherPriorityPolygons();
46+
4147
return () => {
4248
cleanupLasso();
4349
if (controlRef.current && map) {
@@ -46,6 +52,9 @@ const BoundsSelector = ({ value, onChange, priorityType }) => {
4652
if (featureGroupRef.current && map) {
4753
map.removeLayer(featureGroupRef.current);
4854
}
55+
if (otherPriorityGroupRef.current && map) {
56+
map.removeLayer(otherPriorityGroupRef.current);
57+
}
4958
};
5059
}, [map]);
5160

@@ -57,6 +66,14 @@ const BoundsSelector = ({ value, onChange, priorityType }) => {
5766
}
5867
}, [value]);
5968

69+
// Update other priority polygons when allPriorityBounds changes
70+
useEffect(() => {
71+
if (otherPriorityGroupRef.current) {
72+
otherPriorityGroupRef.current.clearLayers();
73+
displayOtherPriorityPolygons();
74+
}
75+
}, [allPriorityBounds, priorityType]);
76+
6077
const restorePolygons = () => {
6178
if (!featureGroupRef.current || !Array.isArray(value)) return;
6279

@@ -101,6 +118,48 @@ const BoundsSelector = ({ value, onChange, priorityType }) => {
101118
return polygon;
102119
};
103120

121+
const displayOtherPriorityPolygons = () => {
122+
if (!otherPriorityGroupRef.current || !allPriorityBounds) {
123+
return;
124+
}
125+
126+
// Display polygons from other priority types
127+
Object.entries(allPriorityBounds).forEach(([type, bounds]) => {
128+
// Skip the current priority type
129+
const currentPriorityKey = `${priorityType}PriorityBounds`;
130+
if (type === currentPriorityKey || !Array.isArray(bounds) || bounds.length === 0) {
131+
return;
132+
}
133+
134+
// Determine the priority type for coloring
135+
let displayType = "default";
136+
if (type.includes("high")) displayType = "high";
137+
else if (type.includes("medium")) displayType = "medium";
138+
else if (type.includes("low")) displayType = "low";
139+
140+
bounds.forEach((feature) => {
141+
try {
142+
if (feature?.geometry?.coordinates?.[0]) {
143+
const coords = feature.geometry.coordinates[0].map((coord) => [coord[1], coord[0]]);
144+
145+
const polygon = L.polygon(coords, {
146+
color: getColor(displayType, "base"),
147+
weight: 2,
148+
opacity: 0.6,
149+
fillOpacity: 0.2,
150+
dashArray: "8, 8", // Dashed line to distinguish from current priority
151+
interactive: false, // Make them non-interactive
152+
});
153+
154+
otherPriorityGroupRef.current.addLayer(polygon);
155+
}
156+
} catch (error) {
157+
console.error("Error displaying other priority polygon:", error);
158+
}
159+
});
160+
});
161+
};
162+
104163
const syncWithParent = () => {
105164
if (!featureGroupRef.current) return;
106165

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { createContext, useContext, useState, useCallback } from "react";
2+
3+
// Context for sharing priority bounds data across all priority fields
4+
const PriorityBoundsDataContext = createContext({
5+
priorityBoundsData: {
6+
highPriorityBounds: [],
7+
mediumPriorityBounds: [],
8+
lowPriorityBounds: [],
9+
},
10+
updatePriorityBounds: () => {},
11+
});
12+
13+
// Provider component
14+
export const PriorityBoundsDataProvider = ({ children, initialData = {} }) => {
15+
const [priorityBoundsData, setPriorityBoundsData] = useState({
16+
highPriorityBounds: initialData.highPriorityBounds || [],
17+
mediumPriorityBounds: initialData.mediumPriorityBounds || [],
18+
lowPriorityBounds: initialData.lowPriorityBounds || [],
19+
});
20+
21+
const updatePriorityBounds = useCallback((priorityType, bounds) => {
22+
setPriorityBoundsData((prev) => ({
23+
...prev,
24+
[`${priorityType}PriorityBounds`]: bounds,
25+
}));
26+
}, []);
27+
28+
const value = {
29+
priorityBoundsData,
30+
updatePriorityBounds,
31+
};
32+
33+
return (
34+
<PriorityBoundsDataContext.Provider value={value}>
35+
{children}
36+
</PriorityBoundsDataContext.Provider>
37+
);
38+
};
39+
40+
// Hook to use the context
41+
export const usePriorityBoundsData = () => {
42+
const context = useContext(PriorityBoundsDataContext);
43+
if (!context) {
44+
throw new Error("usePriorityBoundsData must be used within a PriorityBoundsDataProvider");
45+
}
46+
return context;
47+
};
48+
49+
export default PriorityBoundsDataContext;

0 commit comments

Comments
 (0)