1import { Controller } from "@hotwired/stimulus" 2 3// Connects to data-controller="working-day" 4// 5// One controller instance per weekday row on the practitioner working-hours 6// form. Handles: 7// - toggling the whole day open/closed (enables / disables every input) 8// - adding a new time-range row (start/end pair) 9// - removing a range row (always keeps at least one when the day is active) 10// - re-indexing the range input names so Rails sees a clean array 11// (availabilities[<day>][ranges][0][start_time], [1][start_time], ...) 12export default class extends Controller { 13 // statusLabel is optional: the practitioner form shows an "Open / Closed" 14 // caption beside the toggle, the admin form does not. 15 static targets = ["activeToggle", "rangesContainer", "range", "addButton", "statusLabel"] 16 static values = { dayOfWeek: Number, active: Boolean } 17 18 toggleDay() { 19 const active = this.activeToggleTarget.checked 20 this.activeValue = active 21 this.element.querySelectorAll("input[type='time'], button[data-action*='working-day#']").forEach(el => { 22 el.disabled = !active 23 }) 24 if (this.hasStatusLabelTarget) { 25 this.statusLabelTarget.textContent = active ? "Open" : "Closed" 26 } 27 } 28 29 addRange(event) { 30 event.preventDefault() 31 if (!this.activeToggleTarget.checked) return 32 33 const template = this.rangeTargets[0] 34 if (!template) return 35 36 const clone = template.cloneNode(true) 37 clone.querySelectorAll("input[type='time']").forEach(input => { 38 input.disabled = false 39 // Cleared so the new range looks blank; admin fills it in. 40 input.value = "" 41 }) 42 clone.querySelectorAll("button").forEach(btn => { btn.disabled = false }) 43 44 this.rangesContainerTarget.insertBefore(clone, this.addButtonTarget) 45 this.reindexRanges() 46 } 47 48 removeRange(event) { 49 event.preventDefault() 50 const row = event.currentTarget.closest("[data-working-day-target='range']") 51 if (!row) return 52 53 // Keep at least one row visible so the day always has a UI starting point. 54 if (this.rangeTargets.length <= 1) { 55 row.querySelectorAll("input[type='time']").forEach(input => { input.value = "" }) 56 return 57 } 58 row.remove() 59 this.reindexRanges() 60 } 61 62 reindexRanges() { 63 const day = this.dayOfWeekValue 64 this.rangeTargets.forEach((row, idx) => { 65 row.querySelectorAll("input[type='time']").forEach(input => { 66 const field = input.name.match(/\[(start_time|end_time)\]$/)?.[1] 67 if (field) { 68 input.name = `availabilities[${day}][ranges][${idx}][${field}]` 69 } 70 }) 71 }) 72 } 73}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.