1
0

import_preview.twig 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. {% extends "layout.twig" %}
  2. {% set errorMessages = {
  3. 'commit': 'A sheet failed to import; see the message below. Earlier sheets in the run did commit.',
  4. 'expired': 'Your previous import session expired. Please upload again.',
  5. 'nothing_selected': 'No sheets were selected to import.',
  6. } %}
  7. {% block content %}
  8. <section class="space-y-6">
  9. <header class="flex items-baseline gap-4 flex-wrap">
  10. <h1 class="text-2xl font-semibold tracking-tight">Import preview</h1>
  11. <span class="text-sm text-slate-500 dark:text-slate-400">{{ fileName }}</span>
  12. <span class="text-sm text-slate-500 dark:text-slate-400">{{ sheets|length }} sheet(s) found</span>
  13. </header>
  14. {% if error != '' and errorMessages[error] is defined %}
  15. <div class="rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800 dark:bg-red-900 dark:border-red-800 dark:text-red-200">
  16. {{ errorMessages[error] }}
  17. </div>
  18. {% endif %}
  19. <form method="post" action="/sprints/import/{{ token }}" class="space-y-6">
  20. <input type="hidden" name="_csrf" value="{{ csrfToken }}">
  21. {% for sheet in sheets %}
  22. {% set s = summaries[loop.index0] %}
  23. {% set idx = loop.index0 %}
  24. <section class="rounded-lg border bg-white p-5 dark:bg-slate-800 dark:border-slate-700">
  25. <header class="flex items-baseline gap-3 flex-wrap">
  26. <h2 class="text-lg font-semibold">{{ sheet.sheetName }}</h2>
  27. <span class="text-xs text-slate-500 dark:text-slate-400">
  28. {{ s.weekCount }} weeks · {{ s.workerCount }} workers · {{ s.taskCount }} tasks · {{ s.cellCount }} day cells
  29. </span>
  30. </header>
  31. {% if sheet.warnings is not empty %}
  32. <ul class="mt-3 space-y-1 text-xs text-amber-700 dark:text-amber-300">
  33. {% for w in sheet.warnings %}<li>· {{ w }}</li>{% endfor %}
  34. </ul>
  35. {% endif %}
  36. <div class="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-4">
  37. <label class="block">
  38. <span class="text-sm text-slate-700 dark:text-slate-300">Sprint name</span>
  39. <input type="text" name="name_{{ idx }}" value="{{ sheet.sheetName }}"
  40. class="mt-1 block w-full rounded-md border-slate-300 border 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">
  41. </label>
  42. <label class="block">
  43. <span class="text-sm text-slate-700 dark:text-slate-300">Reserve fraction (read-only — taken from sheet)</span>
  44. <input type="text" disabled value="{{ (sheet.reserveFraction * 100)|round(0) }}%"
  45. class="mt-1 block w-full rounded-md border-slate-300 border bg-slate-50 px-3 py-2 text-slate-500 dark:bg-slate-900 dark:border-slate-700 dark:text-slate-400">
  46. </label>
  47. <label class="block">
  48. <span class="text-sm text-slate-700 dark:text-slate-300">Start date</span>
  49. <input type="date" name="start_{{ idx }}" value="{{ sheet.inferredStartDate }}"
  50. class="mt-1 block w-full rounded-md border-slate-300 border 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">
  51. <span class="text-xs text-slate-500 dark:text-slate-400">Inferred from KW {{ sheet.weeks[0].kw }}; check the year.</span>
  52. </label>
  53. <label class="block">
  54. <span class="text-sm text-slate-700 dark:text-slate-300">End date</span>
  55. <input type="date" name="end_{{ idx }}" value="{{ sheet.inferredEndDate }}"
  56. class="mt-1 block w-full rounded-md border-slate-300 border 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">
  57. </label>
  58. </div>
  59. <fieldset class="mt-5">
  60. <legend class="text-sm font-medium text-slate-700 dark:text-slate-300">Target sprint</legend>
  61. <div class="mt-2 space-y-1 text-sm">
  62. <label class="flex items-center gap-2">
  63. <input type="radio" name="target_{{ idx }}" value="new" checked>
  64. <span>Create new sprint</span>
  65. </label>
  66. <label class="flex items-center gap-2 {% if emptySprints is empty %}text-slate-400 dark:text-slate-600{% endif %}">
  67. <input type="radio" name="target_{{ idx }}" value="existing"
  68. {% if emptySprints is empty %}disabled{% endif %}>
  69. <span>Merge into empty existing sprint:</span>
  70. <select name="existing_{{ idx }}"
  71. {% if emptySprints is empty %}disabled{% endif %}
  72. class="rounded-md border-slate-300 border px-2 py-1 text-sm dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100">
  73. <option value="">— pick —</option>
  74. {% for sp in emptySprints %}
  75. <option value="{{ sp.id }}">{{ sp.name }} ({{ sp.startDate }} → {{ sp.endDate }})</option>
  76. {% endfor %}
  77. </select>
  78. </label>
  79. </div>
  80. {% if emptySprints is empty %}
  81. <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
  82. No empty sprints exist. Only sprints with no weeks, workers, or tasks can be merge targets.
  83. </p>
  84. {% endif %}
  85. </fieldset>
  86. <div class="mt-5 grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
  87. <div class="rounded-md border border-slate-200 p-3 dark:border-slate-700">
  88. <div class="font-medium text-slate-700 dark:text-slate-300">Workers</div>
  89. {% if s.newWorkers is empty %}
  90. <p class="mt-1 text-slate-500 dark:text-slate-400">All {{ s.workerCount }} workers already exist.</p>
  91. {% else %}
  92. <p class="mt-1 text-slate-600 dark:text-slate-400">
  93. Will create {{ s.newWorkers|length }} new worker(s):
  94. <span class="text-slate-800 dark:text-slate-100">{{ s.newWorkers|join(', ') }}</span>
  95. </p>
  96. {% endif %}
  97. </div>
  98. <div class="rounded-md border border-slate-200 p-3 dark:border-slate-700">
  99. <div class="font-medium text-slate-700 dark:text-slate-300">Status colours</div>
  100. <ul class="mt-1 space-y-0.5 text-slate-600 dark:text-slate-400">
  101. <li><span class="inline-block w-3 h-3 align-middle rounded-sm bg-slate-300 dark:bg-slate-600 mr-1"></span> zugewiesen: {{ s.statusCounts.zugewiesen }}</li>
  102. <li><span class="inline-block w-3 h-3 align-middle rounded-sm bg-yellow-300 mr-1"></span> gestartet: {{ s.statusCounts.gestartet }}</li>
  103. <li><span class="inline-block w-3 h-3 align-middle rounded-sm bg-green-500 mr-1"></span> abgeschlossen: {{ s.statusCounts.abgeschlossen }}</li>
  104. <li><span class="inline-block w-3 h-3 align-middle rounded-sm bg-red-500 mr-1"></span> abgebrochen: {{ s.statusCounts.abgebrochen }}</li>
  105. </ul>
  106. </div>
  107. </div>
  108. <label class="mt-4 flex items-center gap-2 text-sm">
  109. <input type="checkbox" name="skip_{{ idx }}" value="1">
  110. <span>Skip this sheet</span>
  111. </label>
  112. </section>
  113. {% endfor %}
  114. <div class="flex gap-3">
  115. <button type="submit"
  116. class="rounded-md bg-slate-900 text-white px-4 py-2 text-sm font-medium hover:bg-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600">
  117. Commit import
  118. </button>
  119. <a href="/sprints/import" class="inline-flex items-center rounded-md border border-slate-300 bg-white text-slate-700 px-4 py-2 text-sm hover:bg-slate-100 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-200 dark:hover:bg-slate-700">
  120. Cancel
  121. </a>
  122. </div>
  123. </form>
  124. </section>
  125. {% endblock %}