settings.php 8.9 KB

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