Skip to content

Commit 5f4f231

Browse files
committed
Adds flexible setup editor for performance center steps
Enables dynamic approach or teeShots configuration to enhance scenario customization Improves accessibility with a scrollable sidebar layout Enforces updated handicap constraints in the schema
1 parent 27ef890 commit 5f4f231

File tree

5 files changed

+462
-91
lines changed

5 files changed

+462
-91
lines changed

editor/src/components/NodeDetailsPanel.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Activity, Step, isActivity, isStep, ConditionGroup } from '../types';
33
import { ConditionEditor } from './ConditionEditor';
44
import { EditPanel } from './EditPanel';
55
import { CollapsibleSection } from './CollapsibleSection';
6+
import { SetupEditor } from './SetupEditor';
67

78
interface NodeDetailsPanelProps {
89
selectedNode: Activity | Step | null;
@@ -45,6 +46,10 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
4546
{/* Step-only logic section follows metadata */}
4647
{isStepSelected && (selectedNode as Step).logic && (
4748
<CollapsibleSection title="Logic" className="logic-block" bodyClassName="logic-inner" defaultOpen persistKey={`${(selectedNode as Step).id}-logic`}>
49+
<SetupEditor
50+
step={selectedNode as Step}
51+
onUpdateStep={updateStep}
52+
/>
4853
<ConditionEditor
4954
label="Success Condition"
5055
condition={(selectedNode as Step).logic.successCondition as ConditionGroup}
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import React from 'react';
2+
import { Step } from '../types';
3+
4+
interface SetupEditorProps {
5+
step: Step;
6+
onUpdateStep: (stepId: string, patch: Partial<Step>) => void;
7+
}
8+
9+
export const SetupEditor: React.FC<SetupEditorProps> = ({ step, onUpdateStep }) => {
10+
const setup = step.logic?.setup || {};
11+
const nodeType = setup.nodeType || '';
12+
13+
// Check if this is a Performance Center step that can have different setup types
14+
const isPerformanceCenterStep = step.nodeType === 'PerformanceCenterScriptedStep';
15+
const isApproachSetup = nodeType === 'PerformanceCenterApproachScriptedSetup';
16+
const isTeeShotsSetup = nodeType === 'PerformanceCenterTeeShotsScriptedSetup';
17+
const isRangeAnalysisStep = step.nodeType === 'RangeAnalysisScriptedStep';
18+
19+
const updateSetup = (setupPatch: any) => {
20+
const newLogic = {
21+
...step.logic,
22+
setup: {
23+
...setup,
24+
...setupPatch
25+
}
26+
};
27+
onUpdateStep(step.id, { logic: newLogic });
28+
};
29+
30+
const switchSetupType = (newNodeType: string) => {
31+
let newSetup: any = { nodeType: newNodeType };
32+
33+
if (newNodeType === 'PerformanceCenterApproachScriptedSetup') {
34+
newSetup = {
35+
...newSetup,
36+
hole: setup.hole || 1,
37+
pin: setup.pin || 1,
38+
playerCategory: setup.playerCategory || 'Handicap',
39+
hcp: setup.hcp || 0,
40+
gender: setup.gender || 'Unspecified',
41+
minDistance: setup.minDistance || 0,
42+
maxDistance: setup.maxDistance || 100
43+
};
44+
} else if (newNodeType === 'PerformanceCenterTeeShotsScriptedSetup') {
45+
newSetup = {
46+
...newSetup,
47+
hole: setup.hole || 1,
48+
playerCategory: setup.playerCategory || 'Handicap',
49+
hcp: setup.hcp || 0,
50+
gender: setup.gender || 'Unspecified',
51+
courseDistance: setup.courseDistance || 5000
52+
};
53+
}
54+
55+
const newLogic = {
56+
...step.logic,
57+
setup: newSetup
58+
};
59+
onUpdateStep(step.id, { logic: newLogic });
60+
};
61+
62+
if (isRangeAnalysisStep) {
63+
return (
64+
<div className="setup-editor">
65+
<h4>Range Analysis Setup</h4>
66+
<div className="edit-field">
67+
<label>
68+
Club
69+
<select
70+
className="cond-input"
71+
value={setup.club || 'None'}
72+
onChange={e => updateSetup({ club: e.target.value })}
73+
>
74+
<option value="None">None</option>
75+
<option value="Drv">Driver</option>
76+
<option value="_2W">2 Wood</option>
77+
<option value="_3W">3 Wood</option>
78+
<option value="_4W">4 Wood</option>
79+
<option value="_5W">5 Wood</option>
80+
<option value="_6W">6 Wood</option>
81+
<option value="_7W">7 Wood</option>
82+
<option value="_8W">8 Wood</option>
83+
<option value="_9W">9 Wood</option>
84+
<option value="_1H">1 Hybrid</option>
85+
<option value="_2H">2 Hybrid</option>
86+
<option value="_3H">3 Hybrid</option>
87+
<option value="_4H">4 Hybrid</option>
88+
<option value="_5H">5 Hybrid</option>
89+
<option value="_6H">6 Hybrid</option>
90+
<option value="_7H">7 Hybrid</option>
91+
<option value="_8H">8 Hybrid</option>
92+
<option value="_9H">9 Hybrid</option>
93+
<option value="_1I">1 Iron</option>
94+
<option value="_2I">2 Iron</option>
95+
<option value="_3I">3 Iron</option>
96+
<option value="_4I">4 Iron</option>
97+
<option value="_5I">5 Iron</option>
98+
<option value="_6I">6 Iron</option>
99+
<option value="_7I">7 Iron</option>
100+
<option value="_8I">8 Iron</option>
101+
<option value="_9I">9 Iron</option>
102+
<option value="_PW">Pitching Wedge</option>
103+
<option value="_SW">Sand Wedge</option>
104+
<option value="_LW">Lob Wedge</option>
105+
<option value="_50W">50° Wedge</option>
106+
<option value="_52W">52° Wedge</option>
107+
<option value="_54W">54° Wedge</option>
108+
<option value="_56W">56° Wedge</option>
109+
<option value="_58W">58° Wedge</option>
110+
<option value="_60W">60° Wedge</option>
111+
<option value="Putt">Putter</option>
112+
</select>
113+
</label>
114+
</div>
115+
<div className="edit-field">
116+
<label>
117+
Distance (meters)
118+
<input
119+
type="number"
120+
min="0"
121+
step="0.1"
122+
value={setup.distance || 0}
123+
onChange={e => updateSetup({ distance: parseFloat(e.target.value) || 0 })}
124+
/>
125+
</label>
126+
</div>
127+
</div>
128+
);
129+
}
130+
131+
if (isPerformanceCenterStep) {
132+
return (
133+
<div className="setup-editor">
134+
<h4>Performance Center Setup</h4>
135+
136+
{/* Setup Type Switch */}
137+
<div className="edit-field">
138+
<label>Setup Type</label>
139+
<div className="setup-type-switch">
140+
<button
141+
className={`setup-type-btn ${isApproachSetup ? 'active' : ''}`}
142+
onClick={() => switchSetupType('PerformanceCenterApproachScriptedSetup')}
143+
>
144+
Approach Shots
145+
</button>
146+
<button
147+
className={`setup-type-btn ${isTeeShotsSetup ? 'active' : ''}`}
148+
onClick={() => switchSetupType('PerformanceCenterTeeShotsScriptedSetup')}
149+
>
150+
Tee Shots
151+
</button>
152+
</div>
153+
</div>
154+
155+
{/* Common Fields */}
156+
<div className="edit-field">
157+
<label>
158+
Hole
159+
<input
160+
type="number"
161+
min="1"
162+
value={setup.hole || 1}
163+
onChange={e => updateSetup({ hole: parseInt(e.target.value) || 1 })}
164+
/>
165+
</label>
166+
</div>
167+
168+
<div className="edit-field">
169+
<label>
170+
Player Category
171+
<select
172+
className="cond-input"
173+
value={setup.playerCategory || 'Handicap'}
174+
onChange={e => updateSetup({ playerCategory: e.target.value })}
175+
>
176+
<option value="Handicap">Handicap</option>
177+
<option value="PGA">PGA</option>
178+
<option value="LPGA">LPGA</option>
179+
</select>
180+
</label>
181+
</div>
182+
183+
{/* Handicap - only show for Handicap player category */}
184+
{setup.playerCategory === 'Handicap' && (
185+
<div className="edit-field">
186+
<label>
187+
Handicap
188+
{isTeeShotsSetup ? (
189+
<select
190+
className="cond-input"
191+
value={setup.hcp || 0}
192+
onChange={e => updateSetup({ hcp: parseInt(e.target.value) || 0 })}
193+
>
194+
{Array.from({ length: 16 }, (_, i) => (
195+
<option key={i} value={i}>{i}</option>
196+
))}
197+
</select>
198+
) : (
199+
<input
200+
type="number"
201+
min="-10"
202+
max="54"
203+
value={setup.hcp || 0}
204+
onChange={e => updateSetup({ hcp: parseInt(e.target.value) || 0 })}
205+
/>
206+
)}
207+
</label>
208+
</div>
209+
)}
210+
211+
<div className="edit-field">
212+
<label>
213+
Gender
214+
<select
215+
className="cond-input"
216+
value={setup.gender || 'Unspecified'}
217+
onChange={e => updateSetup({ gender: e.target.value })}
218+
>
219+
<option value="Male">Male</option>
220+
<option value="Female">Female</option>
221+
<option value="Unspecified">Unspecified</option>
222+
</select>
223+
</label>
224+
</div>
225+
226+
{/* Approach-specific fields */}
227+
{isApproachSetup && (
228+
<>
229+
<div className="edit-field">
230+
<label>
231+
Pin
232+
<input
233+
type="number"
234+
min="1"
235+
value={setup.pin || 1}
236+
onChange={e => updateSetup({ pin: parseInt(e.target.value) || 1 })}
237+
/>
238+
</label>
239+
</div>
240+
<div className="edit-field">
241+
<label>
242+
Min Distance (meters)
243+
<input
244+
type="number"
245+
min="0"
246+
step="0.1"
247+
value={setup.minDistance || 0}
248+
onChange={e => updateSetup({ minDistance: parseFloat(e.target.value) || 0 })}
249+
/>
250+
</label>
251+
</div>
252+
<div className="edit-field">
253+
<label>
254+
Max Distance (meters)
255+
<input
256+
type="number"
257+
min="0"
258+
step="0.1"
259+
value={setup.maxDistance || 100}
260+
onChange={e => updateSetup({ maxDistance: parseFloat(e.target.value) || 100 })}
261+
/>
262+
</label>
263+
</div>
264+
</>
265+
)}
266+
267+
{/* Tee shots specific fields */}
268+
{isTeeShotsSetup && (
269+
<div className="edit-field">
270+
<label>
271+
Course Distance (meters)
272+
<input
273+
type="number"
274+
min="1000"
275+
max="9000"
276+
value={setup.courseDistance || 5000}
277+
onChange={e => updateSetup({ courseDistance: parseInt(e.target.value) || 5000 })}
278+
/>
279+
</label>
280+
</div>
281+
)}
282+
</div>
283+
);
284+
}
285+
286+
return null;
287+
};

0 commit comments

Comments
 (0)