Skip to content

Commit c1ab2a3

Browse files
authored
Add WaterRoutine UI (#193)
1 parent 76c29ee commit c1ab2a3

File tree

7 files changed

+416
-2
lines changed

7 files changed

+416
-2
lines changed

garden-app/server/templates.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const (
3535
weatherClientsPageTemplate html.Template = "WeatherClientsPage"
3636
weatherClientsTemplate html.Template = "WeatherClients"
3737
weatherClientModalTemplate html.Template = "WeatherClientModal"
38+
waterRoutinesPageTemplate html.Template = "WaterRoutinesPage"
39+
waterRoutinesTemplate html.Template = "WaterRoutines"
40+
waterRoutineModalTemplate html.Template = "WaterRoutineModal"
3841
)
3942

4043
func templateFuncs(r *http.Request) map[string]any {
@@ -53,6 +56,9 @@ func templateFuncs(r *http.Request) map[string]any {
5356

5457
return result
5558
},
59+
"add": func(a, b int) int {
60+
return a + b
61+
},
5662
"ToLower": strings.ToLower,
5763
"FormatUpcomingDate": func(date *time.Time) string {
5864
now := clock.Now()

garden-app/server/templates/base.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
</li>
3333
<li {{ if URLContains "/water_schedules" }}class="uk-active" {{ end }}><a
3434
href="/water_schedules?exclude_weather_data=true">Water Schedules</a></li>
35+
<li {{ if URLContains "/water_routines" }}class="uk-active" {{ end }}><a
36+
href="/water_routines">Water Routines</a></li>
3537
<li {{ if URLContains "/weather_clients" }}class="uk-active" {{ end }}><a
3638
href="/weather_clients">Weather Clients</a></li>
3739
<li>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
{{ define "WaterRoutineModal" }}
2+
<div id="modal" class="uk-modal" style="display: block">
3+
<div class="uk-modal-dialog uk-modal-body">
4+
<h3 class="uk-modal-title">
5+
{{ if .WaterRoutine.Name }}{{ .WaterRoutine.Name }}{{ else }}Create
6+
Water Routine{{ end }}
7+
</h3>
8+
9+
<form
10+
_="on submit take .uk-open from #modal"
11+
hx-put="/water_routines/{{ .WaterRoutine.ID }}"
12+
hx-headers='{"Accept": "text/html"}'
13+
hx-swap="none"
14+
>
15+
<input type="hidden" value="{{ .WaterRoutine.ID }}" name="ID" />
16+
<div class="uk-margin">
17+
<label class="uk-form-label" for="water-routine-name"
18+
>Name</label
19+
>
20+
<input
21+
id="water-routine-name"
22+
class="uk-input"
23+
value="{{ .WaterRoutine.Name }}"
24+
placeholder="Name"
25+
name="Name"
26+
/>
27+
</div>
28+
29+
<div class="uk-margin">
30+
<label class="uk-form-label">Steps</label>
31+
<div id="water-routine-steps">
32+
{{ range $index, $step := .WaterRoutine.Steps }}
33+
<div
34+
class="uk-grid-small uk-margin-small-bottom step-row"
35+
uk-grid
36+
>
37+
<div class="uk-width-1-2@s">
38+
<select
39+
class="uk-select"
40+
name="Steps.{{ $index }}.ZoneID"
41+
>
42+
{{ $selectedZone := $step.ZoneID.String }} {{
43+
range $gardenID, $gardenZones := $.GroupedZones
44+
}}
45+
<optgroup label="{{ $gardenZones.GardenName }}">
46+
{{ range $gardenZones.Zones }}
47+
<option
48+
value="{{ .ID }}"
49+
{{
50+
if
51+
eq
52+
.GetID
53+
$selectedZone
54+
}}selected{{
55+
end
56+
}}
57+
>
58+
{{ .Name }}
59+
</option>
60+
{{ end }}
61+
</optgroup>
62+
{{ end }}
63+
</select>
64+
</div>
65+
<div class="uk-width-1-3@s">
66+
<input
67+
class="uk-input"
68+
type="text"
69+
placeholder="Duration (e.g., 5m)"
70+
name="Steps.{{ $index }}.Duration"
71+
value="{{ FormatDuration $step.Duration }}"
72+
/>
73+
</div>
74+
<div class="uk-width-auto@s">
75+
<button
76+
type="button"
77+
class="uk-button uk-button-danger uk-button-small"
78+
onclick="this.closest('.step-row').remove()"
79+
>
80+
<span uk-icon="icon: trash; ratio: 0.75"></span>
81+
</button>
82+
</div>
83+
</div>
84+
{{ else }}
85+
<div
86+
class="uk-grid-small uk-margin-small-bottom step-row"
87+
uk-grid
88+
>
89+
<div class="uk-width-1-2@s">
90+
<select class="uk-select" name="Steps.0.ZoneID">
91+
{{ range $gardenID, $gardenZones :=
92+
$.GroupedZones }}
93+
<optgroup label="{{ $gardenZones.GardenName }}">
94+
{{ range $gardenZones.Zones }}
95+
<option value="{{ .ID }}">
96+
{{ .Name }}
97+
</option>
98+
{{ end }}
99+
</optgroup>
100+
{{ end }}
101+
</select>
102+
</div>
103+
<div class="uk-width-1-3@s">
104+
<input
105+
class="uk-input"
106+
type="text"
107+
placeholder="Duration (e.g., 5m)"
108+
name="Steps.0.Duration"
109+
/>
110+
</div>
111+
<div class="uk-width-auto@s">
112+
<button
113+
type="button"
114+
class="uk-button uk-button-danger uk-button-small"
115+
onclick="this.closest('.step-row').remove()"
116+
>
117+
<span uk-icon="icon: trash; ratio: 0.75"></span>
118+
</button>
119+
</div>
120+
</div>
121+
{{ end }}
122+
</div>
123+
<button
124+
type="button"
125+
class="uk-button uk-button-default uk-button-small"
126+
onclick="addStep()"
127+
>
128+
<span uk-icon="icon: plus; ratio: 0.75"></span> Add Step
129+
</button>
130+
</div>
131+
132+
{{ template "modalSubmitButton" }} {{ if .WaterRoutine.Name }} {{
133+
template "deleteButton" ( args "HXDelete" (print "/water_routines/"
134+
.WaterRoutine.ID) "HXTarget" (print "#water-routine-card-"
135+
.WaterRoutine.ID) ) }} {{ end }} {{ template "modalCloseButton" }}
136+
</form>
137+
</div>
138+
</div>
139+
140+
<script>
141+
function addStep() {
142+
const container = document.getElementById("water-routine-steps");
143+
const stepCount = container.querySelectorAll(".step-row").length;
144+
const newStep = document.createElement("div");
145+
newStep.className = "uk-grid-small uk-margin-small-bottom step-row";
146+
newStep.setAttribute("uk-grid", "");
147+
newStep.innerHTML = `
148+
<div class="uk-width-1-2@s">
149+
<select class="uk-select" name="Steps.${stepCount}.ZoneID">
150+
{{ range $gardenID, $gardenZones := .GroupedZones }}
151+
<optgroup label="{{ $gardenZones.GardenName }}">
152+
{{ range $gardenZones.Zones }}
153+
<option value="{{ .ID }}">{{ .Name }}</option>
154+
{{ end }}
155+
</optgroup>
156+
{{ end }}
157+
</select>
158+
</div>
159+
<div class="uk-width-1-3@s">
160+
<input class="uk-input" type="text" placeholder="Duration (e.g., 5m)" name="Steps.${stepCount}.Duration">
161+
</div>
162+
<div class="uk-width-auto@s">
163+
<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="this.closest('.step-row').remove()">
164+
<span uk-icon="icon: trash; ratio: 0.75"></span>
165+
</button>
166+
</div>
167+
`;
168+
container.appendChild(newStep);
169+
}
170+
</script>
171+
{{ end }}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{{ define "WaterRoutinesPage" }}
2+
{{ template "start" }}
3+
{{ template "WaterRoutines" . }}
4+
{{ template "end" }}
5+
{{ end }}
6+
7+
{{ define "WaterRoutines" }}
8+
<div hx-swap="outerHTML" hx-get="/water_routines?refresh=true" hx-headers='{"Accept": "text/html"}'
9+
hx-trigger="newWaterRoutine from:body" uk-grid>
10+
{{ range .Items }}
11+
{{ template "WaterRoutineCard" . }}
12+
{{ end }}
13+
</div>
14+
{{ end }}
15+
16+
{{ define "WaterRoutineCard" }}
17+
<div class="uk-width-1-2@m" id="water-routine-card-{{ .ID }}">
18+
<div id="edit-modal-here"></div>
19+
<div id="run-modal-here-{{ .ID }}"></div>
20+
<div class="uk-card uk-card-default" style="margin: 5%;">
21+
<div class="uk-card-header uk-text-center">
22+
<h3 class="uk-card-title uk-margin-remove-bottom">
23+
{{ .Name }}
24+
</h3>
25+
{{ template "cardEditButton" (print "/water_routines/" .ID "/components?type=edit_modal") }}
26+
</div>
27+
<div class="uk-card-body">
28+
<span class="uk-label">
29+
{{ len .Steps }} Steps <i class="bi-list-task"></i>
30+
</span>
31+
32+
<div class="uk-margin-small-top">
33+
{{ range $index, $step := .Steps }}
34+
<div class="uk-text-small uk-text-muted">
35+
<span uk-icon="icon: location; ratio: 0.75"></span>
36+
Zone {{ $step.ZoneID }} - {{ FormatDuration $step.Duration }}
37+
</div>
38+
{{ end }}
39+
</div>
40+
</div>
41+
<div class="uk-card-footer">
42+
<div class="uk-clearfix">
43+
<div class="uk-float-left">
44+
{{ template "waterRoutineActionButton" . }}
45+
</div>
46+
</div>
47+
</div>
48+
</div>
49+
</div>
50+
{{ end }}
51+
52+
{{ define "waterRoutineActionButton" }}
53+
<div class="uk-inline">
54+
<button class="uk-button uk-button-default" type="button">Actions</button>
55+
<div uk-dropdown>
56+
<ul class="uk-nav uk-dropdown-nav">
57+
<li>
58+
<a href="#run-confirm-{{ .ID }}" uk-toggle>
59+
<span uk-icon="play"></span> Run Routine
60+
</a>
61+
</li>
62+
</ul>
63+
</div>
64+
</div>
65+
66+
{{ template "runConfirmationModal" . }}
67+
{{ end }}
68+
69+
{{ define "runConfirmationModal" }}
70+
<div id="run-confirm-{{ .ID }}" class="uk-modal" uk-modal>
71+
<div class="uk-modal-dialog uk-modal-body">
72+
<h3 class="uk-modal-title">Confirm Run</h3>
73+
<p>Are you sure you want to run <strong>{{ .Name }}</strong>?</p>
74+
<p class="uk-text-meta">This will water {{ len .Steps }} zone(s) sequentially.</p>
75+
76+
<div class="uk-margin">
77+
<h5>Steps:</h5>
78+
{{ range $index, $step := .Steps }}
79+
<div class="uk-text-small">
80+
<span uk-icon="icon: location; ratio: 0.75"></span>
81+
Step {{ add $index 1 }}: Zone {{ $step.ZoneID }} - {{ FormatDuration $step.Duration }}
82+
</div>
83+
{{ end }}
84+
</div>
85+
86+
<p class="uk-text-right">
87+
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
88+
<button class="uk-button uk-button-primary" type="button"
89+
hx-post="/water_routines/{{ .ID }}/run"
90+
hx-swap="none"
91+
{{ template "actionFeedback" "primary" }}
92+
onclick="UIkit.modal('#run-confirm-{{ .ID }}').hide()">
93+
<span uk-icon="play"></span> Run Now
94+
</button>
95+
</p>
96+
</div>
97+
</div>
98+
{{ end }}

0 commit comments

Comments
 (0)