form_fields.templ 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. package components
  2. import "fmt"
  3. // FormFieldData represents common form field data
  4. type FormFieldData struct {
  5. Name string
  6. Label string
  7. Description string
  8. Required bool
  9. }
  10. // TextFieldData represents text input field data
  11. type TextFieldData struct {
  12. FormFieldData
  13. Value string
  14. Placeholder string
  15. }
  16. // NumberFieldData represents number input field data
  17. type NumberFieldData struct {
  18. FormFieldData
  19. Value float64
  20. Step string
  21. Min *float64
  22. Max *float64
  23. }
  24. // CheckboxFieldData represents checkbox field data
  25. type CheckboxFieldData struct {
  26. FormFieldData
  27. Checked bool
  28. }
  29. // SelectFieldData represents select field data
  30. type SelectFieldData struct {
  31. FormFieldData
  32. Value string
  33. Options []SelectOption
  34. }
  35. type SelectOption struct {
  36. Value string
  37. Label string
  38. }
  39. // DurationFieldData represents duration input field data
  40. type DurationFieldData struct {
  41. FormFieldData
  42. Value string
  43. Placeholder string
  44. }
  45. // DurationInputFieldData represents duration input with number + unit dropdown
  46. type DurationInputFieldData struct {
  47. FormFieldData
  48. Seconds int // The duration value in seconds
  49. }
  50. // TextField renders a Bootstrap text input field
  51. templ TextField(data TextFieldData) {
  52. <div class="mb-3">
  53. <label for={ data.Name } class="form-label">
  54. { data.Label }
  55. if data.Required {
  56. <span class="text-danger">*</span>
  57. }
  58. </label>
  59. <input
  60. type="text"
  61. class="form-control"
  62. id={ data.Name }
  63. name={ data.Name }
  64. value={ data.Value }
  65. if data.Placeholder != "" {
  66. placeholder={ data.Placeholder }
  67. }
  68. if data.Required {
  69. required
  70. }
  71. />
  72. if data.Description != "" {
  73. <div class="form-text text-muted">{ data.Description }</div>
  74. }
  75. </div>
  76. }
  77. // NumberField renders a Bootstrap number input field
  78. templ NumberField(data NumberFieldData) {
  79. <div class="mb-3">
  80. <label for={ data.Name } class="form-label">
  81. { data.Label }
  82. if data.Required {
  83. <span class="text-danger">*</span>
  84. }
  85. </label>
  86. <input
  87. type="number"
  88. class="form-control"
  89. id={ data.Name }
  90. name={ data.Name }
  91. value={ fmt.Sprintf("%.6g", data.Value) }
  92. if data.Step != "" {
  93. step={ data.Step }
  94. } else {
  95. step="any"
  96. }
  97. if data.Min != nil {
  98. min={ fmt.Sprintf("%.6g", *data.Min) }
  99. }
  100. if data.Max != nil {
  101. max={ fmt.Sprintf("%.6g", *data.Max) }
  102. }
  103. if data.Required {
  104. required
  105. }
  106. />
  107. if data.Description != "" {
  108. <div class="form-text text-muted">{ data.Description }</div>
  109. }
  110. </div>
  111. }
  112. // CheckboxField renders a Bootstrap checkbox field
  113. templ CheckboxField(data CheckboxFieldData) {
  114. <div class="mb-3">
  115. <div class="form-check">
  116. <input
  117. type="checkbox"
  118. class="form-check-input"
  119. id={ data.Name }
  120. name={ data.Name }
  121. if data.Checked {
  122. checked
  123. }
  124. />
  125. <label class="form-check-label" for={ data.Name }>
  126. { data.Label }
  127. </label>
  128. </div>
  129. if data.Description != "" {
  130. <div class="form-text text-muted">{ data.Description }</div>
  131. }
  132. </div>
  133. }
  134. // SelectField renders a Bootstrap select field
  135. templ SelectField(data SelectFieldData) {
  136. <div class="mb-3">
  137. <label for={ data.Name } class="form-label">
  138. { data.Label }
  139. if data.Required {
  140. <span class="text-danger">*</span>
  141. }
  142. </label>
  143. <select
  144. class="form-select"
  145. id={ data.Name }
  146. name={ data.Name }
  147. if data.Required {
  148. required
  149. }
  150. >
  151. for _, option := range data.Options {
  152. <option
  153. value={ option.Value }
  154. if option.Value == data.Value {
  155. selected
  156. }
  157. >
  158. { option.Label }
  159. </option>
  160. }
  161. </select>
  162. if data.Description != "" {
  163. <div class="form-text text-muted">{ data.Description }</div>
  164. }
  165. </div>
  166. }
  167. // DurationField renders a Bootstrap duration input field
  168. templ DurationField(data DurationFieldData) {
  169. <div class="mb-3">
  170. <label for={ data.Name } class="form-label">
  171. { data.Label }
  172. if data.Required {
  173. <span class="text-danger">*</span>
  174. }
  175. </label>
  176. <input
  177. type="text"
  178. class="form-control"
  179. id={ data.Name }
  180. name={ data.Name }
  181. value={ data.Value }
  182. if data.Placeholder != "" {
  183. placeholder={ data.Placeholder }
  184. } else {
  185. placeholder="e.g., 30m, 2h, 24h"
  186. }
  187. if data.Required {
  188. required
  189. }
  190. />
  191. if data.Description != "" {
  192. <div class="form-text text-muted">{ data.Description }</div>
  193. }
  194. </div>
  195. }
  196. // DurationInputField renders a Bootstrap duration input with number + unit dropdown
  197. templ DurationInputField(data DurationInputFieldData) {
  198. <div class="mb-3">
  199. <label for={ data.Name } class="form-label">
  200. { data.Label }
  201. if data.Required {
  202. <span class="text-danger">*</span>
  203. }
  204. </label>
  205. <div class="input-group">
  206. <input
  207. type="number"
  208. class="form-control"
  209. id={ data.Name }
  210. name={ data.Name }
  211. value={ fmt.Sprintf("%.0f", convertSecondsToValue(data.Seconds, convertSecondsToUnit(data.Seconds))) }
  212. step="1"
  213. min="1"
  214. if data.Required {
  215. required
  216. }
  217. />
  218. <select
  219. class="form-select"
  220. id={ data.Name + "_unit" }
  221. name={ data.Name + "_unit" }
  222. style="max-width: 120px;"
  223. >
  224. <option
  225. value="minutes"
  226. if convertSecondsToUnit(data.Seconds) == "minutes" {
  227. selected
  228. }
  229. >
  230. Minutes
  231. </option>
  232. <option
  233. value="hours"
  234. if convertSecondsToUnit(data.Seconds) == "hours" {
  235. selected
  236. }
  237. >
  238. Hours
  239. </option>
  240. <option
  241. value="days"
  242. if convertSecondsToUnit(data.Seconds) == "days" {
  243. selected
  244. }
  245. >
  246. Days
  247. </option>
  248. </select>
  249. </div>
  250. if data.Description != "" {
  251. <div class="form-text text-muted">{ data.Description }</div>
  252. }
  253. </div>
  254. }
  255. // Helper functions for duration conversion (used by DurationInputField)
  256. // Typed conversion functions for protobuf int32 (most common) - EXPORTED
  257. func ConvertInt32SecondsToDisplayValue(seconds int32) float64 {
  258. return convertIntSecondsToDisplayValue(int(seconds))
  259. }
  260. func GetInt32DisplayUnit(seconds int32) string {
  261. return getIntDisplayUnit(int(seconds))
  262. }
  263. // Typed conversion functions for regular int
  264. func convertIntSecondsToDisplayValue(seconds int) float64 {
  265. if seconds == 0 {
  266. return 0
  267. }
  268. // Check if it's evenly divisible by days
  269. if seconds%(24*3600) == 0 {
  270. return float64(seconds / (24 * 3600))
  271. }
  272. // Check if it's evenly divisible by hours
  273. if seconds%3600 == 0 {
  274. return float64(seconds / 3600)
  275. }
  276. // Default to minutes
  277. return float64(seconds / 60)
  278. }
  279. func getIntDisplayUnit(seconds int) string {
  280. if seconds == 0 {
  281. return "minutes"
  282. }
  283. // Check if it's evenly divisible by days
  284. if seconds%(24*3600) == 0 {
  285. return "days"
  286. }
  287. // Check if it's evenly divisible by hours
  288. if seconds%3600 == 0 {
  289. return "hours"
  290. }
  291. // Default to minutes
  292. return "minutes"
  293. }
  294. func convertSecondsToUnit(seconds int) string {
  295. if seconds == 0 {
  296. return "minutes"
  297. }
  298. // Try days first
  299. if seconds%(24*3600) == 0 && seconds >= 24*3600 {
  300. return "days"
  301. }
  302. // Try hours
  303. if seconds%3600 == 0 && seconds >= 3600 {
  304. return "hours"
  305. }
  306. // Default to minutes
  307. return "minutes"
  308. }
  309. func convertSecondsToValue(seconds int, unit string) float64 {
  310. if seconds == 0 {
  311. return 0
  312. }
  313. switch unit {
  314. case "days":
  315. return float64(seconds / (24 * 3600))
  316. case "hours":
  317. return float64(seconds / 3600)
  318. case "minutes":
  319. return float64(seconds / 60)
  320. default:
  321. return float64(seconds / 60) // Default to minutes
  322. }
  323. }
  324. // IntervalFieldData represents interval input field data with separate value and unit
  325. type IntervalFieldData struct {
  326. FormFieldData
  327. Seconds int // The interval value in seconds
  328. }
  329. // IntervalField renders a Bootstrap interval input with number + unit dropdown (like task config)
  330. templ IntervalField(data IntervalFieldData) {
  331. <div class="mb-3">
  332. <label for={ data.Name } class="form-label">
  333. { data.Label }
  334. if data.Required {
  335. <span class="text-danger">*</span>
  336. }
  337. </label>
  338. <div class="input-group">
  339. <input
  340. type="number"
  341. class="form-control"
  342. id={ data.Name + "_value" }
  343. name={ data.Name + "_value" }
  344. value={ fmt.Sprintf("%.0f", convertSecondsToValue(data.Seconds, convertSecondsToUnit(data.Seconds))) }
  345. step="1"
  346. min="1"
  347. if data.Required {
  348. required
  349. }
  350. />
  351. <select
  352. class="form-select"
  353. id={ data.Name + "_unit" }
  354. name={ data.Name + "_unit" }
  355. style="max-width: 120px;"
  356. if data.Required {
  357. required
  358. }
  359. >
  360. <option
  361. value="minutes"
  362. if convertSecondsToUnit(data.Seconds) == "minutes" {
  363. selected
  364. }
  365. >
  366. Minutes
  367. </option>
  368. <option
  369. value="hours"
  370. if convertSecondsToUnit(data.Seconds) == "hours" {
  371. selected
  372. }
  373. >
  374. Hours
  375. </option>
  376. <option
  377. value="days"
  378. if convertSecondsToUnit(data.Seconds) == "days" {
  379. selected
  380. }
  381. >
  382. Days
  383. </option>
  384. </select>
  385. </div>
  386. if data.Description != "" {
  387. <div class="form-text text-muted">{ data.Description }</div>
  388. }
  389. </div>
  390. }