1
0

settings.twig 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. {% extends "layout.twig" %}
  2. {% set dayLabels = constant('App\\Domain\\SprintWeek::DAY_LABELS') %}
  3. {% block content %}
  4. <section class="space-y-6"
  5. data-sprint-root
  6. data-sprint-id="{{ sprint.id }}"
  7. data-csrf="{{ csrfToken }}">
  8. <header class="flex items-end justify-between gap-4">
  9. <div>
  10. <nav class="text-xs text-slate-500 dark:text-slate-400">
  11. <a href="/" class="hover:underline">Sprints</a> /
  12. <a href="/sprints/{{ sprint.id }}" class="hover:underline">{{ sprint.name }}</a> /
  13. </nav>
  14. <h1 class="text-2xl font-semibold tracking-tight">Settings</h1>
  15. </div>
  16. <div data-status
  17. class="text-sm border rounded px-3 py-1 opacity-0 transition-opacity duration-200 border-slate-200 bg-slate-50 text-slate-700 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300">
  18. </div>
  19. </header>
  20. <section class="rounded-lg border bg-white p-5 space-y-4 dark:bg-slate-800 dark:border-slate-700">
  21. <h2 class="text-sm font-semibold text-slate-700 uppercase tracking-wider dark:text-slate-200">Sprint</h2>
  22. <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
  23. <label class="md:col-span-2 block">
  24. <span class="text-sm text-slate-700 dark:text-slate-300">Name</span>
  25. <input data-meta name="name" type="text" value="{{ sprint.name }}"
  26. class="mt-1 block w-full rounded-md border border-slate-300 shadow-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500">
  27. </label>
  28. <label class="block">
  29. <span class="text-sm text-slate-700 dark:text-slate-300">Start date</span>
  30. <input data-meta name="start_date" type="date" value="{{ sprint.startDate }}"
  31. class="mt-1 block w-full rounded-md border border-slate-300 shadow-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500">
  32. </label>
  33. <label class="block">
  34. <span class="text-sm text-slate-700 dark:text-slate-300">End date</span>
  35. <input data-meta name="end_date" type="date" value="{{ sprint.endDate }}"
  36. class="mt-1 block w-full rounded-md border border-slate-300 shadow-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500">
  37. </label>
  38. <label class="block">
  39. <span class="text-sm text-slate-700 dark:text-slate-300">Reserve (%)</span>
  40. <input data-meta name="reserve_fraction" type="number" min="0" max="100" step="1"
  41. value="{{ (sprint.reserveFraction * 100)|number_format(0) }}"
  42. class="mt-1 block w-full rounded-md border border-slate-300 shadow-sm px-3 py-2 font-mono focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500">
  43. </label>
  44. </div>
  45. <p class="text-xs text-slate-500 dark:text-slate-400">Changes save automatically.</p>
  46. </section>
  47. <section class="rounded-lg border bg-white p-5 space-y-4 dark:bg-slate-800 dark:border-slate-700">
  48. <div class="flex items-end justify-between gap-4">
  49. <div>
  50. <h2 class="text-sm font-semibold text-slate-700 uppercase tracking-wider dark:text-slate-200">Weeks</h2>
  51. <p class="text-xs text-slate-500 mt-1 dark:text-slate-400">
  52. Current: {{ weeks|length }} week{{ weeks|length == 1 ? '' : 's' }}.
  53. Tick the weekdays that are workdays for each week; the count feeds
  54. the Arbeitstage header on the sprint page.
  55. </p>
  56. </div>
  57. <form data-weeks-form class="flex items-end gap-2">
  58. <label class="block">
  59. <span class="text-xs text-slate-600 dark:text-slate-400">Set to</span>
  60. <input name="n_weeks" type="number" min="1" max="26" step="1"
  61. value="{{ weeks|length }}"
  62. class="mt-1 w-24 rounded-md border border-slate-300 shadow-sm px-3 py-2 font-mono focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500">
  63. </label>
  64. <button type="submit"
  65. class="rounded-md bg-slate-900 text-white px-3 py-2 text-sm font-medium hover:bg-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600">
  66. Apply
  67. </button>
  68. </form>
  69. </div>
  70. <div class="overflow-hidden rounded border border-slate-200 dark:border-slate-700">
  71. <table class="min-w-full text-sm" data-weeks-table>
  72. <thead class="bg-slate-50 text-slate-600 text-xs uppercase tracking-wider dark:bg-slate-700 dark:text-slate-300">
  73. <tr>
  74. <th class="text-left px-3 py-2 font-semibold">#</th>
  75. <th class="text-left px-3 py-2 font-semibold">KW</th>
  76. <th class="text-left px-3 py-2 font-semibold">Start</th>
  77. {% for label in dayLabels %}
  78. <th class="text-center px-2 py-2 font-semibold w-10">{{ label }}</th>
  79. {% endfor %}
  80. <th class="text-right px-3 py-2 font-semibold">Arbeitstage</th>
  81. </tr>
  82. </thead>
  83. <tbody class="divide-y divide-slate-100 dark:divide-slate-700">
  84. {% for w in weeks %}
  85. <tr data-week-row data-week-id="{{ w.id }}">
  86. <td class="px-3 py-2 font-mono">{{ w.sortOrder }}</td>
  87. <td class="px-3 py-2 font-mono">KW{{ w.isoWeek }}</td>
  88. <td class="px-3 py-2 font-mono">{{ w.startDate }}</td>
  89. {% for bit, label in dayLabels %}
  90. <td class="px-2 py-2 text-center">
  91. <input type="checkbox"
  92. data-day-toggle
  93. data-bit="{{ bit }}"
  94. data-label="{{ label }}"
  95. aria-label="{{ label }}"
  96. {{ w.hasDay(label) ? 'checked' : '' }}
  97. class="rounded border-slate-300 focus:ring-slate-400 dark:border-slate-600 dark:focus:ring-slate-500">
  98. </td>
  99. {% endfor %}
  100. <td class="px-3 py-2 font-mono text-right" data-week-count>
  101. {{ w.maxWorkingDays }}
  102. </td>
  103. </tr>
  104. {% endfor %}
  105. </tbody>
  106. </table>
  107. </div>
  108. <p class="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-3 py-2 dark:bg-amber-900 dark:border-amber-800 dark:text-amber-200">
  109. Reducing the week count deletes trailing weeks and any data attached to them.
  110. </p>
  111. </section>
  112. <section class="rounded-lg border bg-white p-5 space-y-4 dark:bg-slate-800 dark:border-slate-700">
  113. <h2 class="text-sm font-semibold text-slate-700 uppercase tracking-wider dark:text-slate-200">Workers</h2>
  114. <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
  115. <div>
  116. <h3 class="text-xs font-semibold text-slate-500 uppercase mb-2 dark:text-slate-400">Available</h3>
  117. <div class="rounded border border-slate-200 dark:border-slate-700">
  118. <ul data-available class="divide-y divide-slate-100 dark:divide-slate-700">
  119. {% for w in availableWorkers %}
  120. <li class="flex items-center gap-2 px-3 py-2 border-b last:border-b-0 dark:border-slate-700"
  121. data-worker-id="{{ w.id }}">
  122. <span class="flex-1">{{ w.name }}</span>
  123. <button type="button" data-add class="text-sm text-blue-700 hover:underline dark:text-blue-400 dark:hover:text-blue-300">Add →</button>
  124. </li>
  125. {% endfor %}
  126. </ul>
  127. <div data-empty-available
  128. class="p-3 text-center text-xs text-slate-500 dark:text-slate-400"
  129. {% if availableWorkers is not empty %}style="display:none"{% endif %}>
  130. No other active workers.
  131. </div>
  132. </div>
  133. </div>
  134. <div>
  135. <h3 class="text-xs font-semibold text-slate-500 uppercase mb-2 dark:text-slate-400">In sprint (drag to reorder)</h3>
  136. <div class="rounded border border-slate-200 dark:border-slate-700">
  137. <ul data-in-sprint class="divide-y divide-slate-100 dark:divide-slate-700">
  138. {% for sw in sprintWorkers %}
  139. <li class="flex items-center gap-2 px-3 py-2 border-b bg-white last:border-b-0 dark:bg-slate-800 dark:border-slate-700"
  140. data-sw-id="{{ sw.id }}"
  141. data-worker-id="{{ sw.workerId }}">
  142. <span class="handle cursor-grab text-slate-400 select-none dark:text-slate-500">&#8801;</span>
  143. <span class="flex-1">{{ sw.workerName }}</span>
  144. <input type="number" step="0.05" min="0" max="1"
  145. value="{{ fmt_rtb(sw.rtb) }}"
  146. data-rtb
  147. class="w-20 rounded border border-slate-300 px-2 py-1 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-slate-400 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100 dark:focus:ring-slate-500">
  148. <button type="button" data-remove class="text-sm text-red-600 hover:underline dark:text-red-400">Remove</button>
  149. </li>
  150. {% endfor %}
  151. </ul>
  152. <div data-empty-sprint
  153. class="p-3 text-center text-xs text-slate-500 dark:text-slate-400"
  154. {% if sprintWorkers is not empty %}style="display:none"{% endif %}>
  155. No workers assigned yet.
  156. </div>
  157. </div>
  158. </div>
  159. </div>
  160. <p class="text-xs text-slate-500 dark:text-slate-400">
  161. RTB (Run-the-Business) is informational and does not reduce computed capacity.
  162. </p>
  163. </section>
  164. </section>
  165. <script src="/assets/js/sprint-settings.js" defer></script>
  166. {% endblock %}