_task_row.twig 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. {# Single task row markup. Used both inside the tbody for-loop in
  2. _task_list.twig AND once more inside the hidden <template
  3. data-task-row-template> at the bottom of the same file — keeping a
  4. single source of truth so JS-built rows can never drift from
  5. server-rendered ones (R02-N02).
  6. Required overrides via {% include … with %}:
  7. - t : Task object, or null when rendering the template shell
  8. - assign : map sw_id → days (numeric); pass {} for the template
  9. - links : array of link descriptors; pass [] for the template
  10. - tot : numeric total of `assign`; pass 0 for the template
  11. Inherited from the surrounding sprint show/present view:
  12. - sprintWorkers, ownerChoices, taskStatusEnabled, statusGrid,
  13. currentUser, STATUS_ZUGEWIESEN.
  14. #}
  15. {% set isTemplate = (t is null) %}
  16. {% set tId = isTemplate ? '' : t.id %}
  17. {% set tTitle = isTemplate ? '' : t.title %}
  18. {% set tPrio = isTemplate ? 1 : t.priority %}
  19. {% set tOwnerId = isTemplate ? null : t.ownerWorkerId %}
  20. {% set tDesc = isTemplate ? '' : t.description %}
  21. {% set tUrl = isTemplate ? '' : t.url %}
  22. {% set tSort = isTemplate ? '' : t.sortOrder %}
  23. <tr data-task-row
  24. class="hover:bg-slate-50 dark:hover:bg-slate-700"
  25. data-task-id="{{ tId }}"
  26. data-prio="{{ tPrio }}"
  27. data-owner="{{ tOwnerId is not null ? tOwnerId : '' }}"
  28. data-sort-order="{{ tSort }}"
  29. data-description="{{ tDesc }}"
  30. data-url="{{ tUrl }}"
  31. data-task-title="{{ tTitle }}"
  32. data-links="{{ links|json_encode|e('html_attr') }}">
  33. <td class="px-2 py-1">
  34. <button type="button" data-task-menu-trigger
  35. class="task-menu-trigger inline-flex items-center justify-center w-6 h-6 rounded text-slate-500 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400"
  36. aria-haspopup="true" aria-expanded="false"
  37. aria-label="Task actions">
  38. <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
  39. <line x1="2" y1="4" x2="14" y2="4" stroke="currentColor" stroke-width="2"/>
  40. <line x1="2" y1="8" x2="14" y2="8" stroke="currentColor" stroke-width="2"/>
  41. <line x1="2" y1="12" x2="14" y2="12" stroke="currentColor" stroke-width="2"/>
  42. </svg>
  43. </button>
  44. </td>
  45. <td class="px-2 py-1 min-w-[14rem]">
  46. <div class="flex items-center gap-1.5">
  47. {% if currentUser.isAdmin %}
  48. <input type="text" data-title value="{{ tTitle }}"
  49. class="flex-1 rounded border border-slate-200 px-2 py-1 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">
  50. {% else %}
  51. <span class="flex-1">{{ tTitle }}</span>
  52. {% endif %}
  53. <a data-task-url-link href="{{ tUrl }}"
  54. target="_blank" rel="noopener noreferrer"
  55. class="task-url-link inline-flex items-center justify-center w-5 h-5 rounded text-blue-600 hover:bg-slate-100 dark:text-blue-400 dark:hover:bg-slate-700{% if tUrl == '' %} hidden{% endif %}"
  56. title="Open task link"
  57. aria-label="Open task link">
  58. <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true">
  59. <path d="M9 2h5v5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  60. <path d="M14 2L7 9" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  61. <path d="M12 9v4a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  62. </svg>
  63. </a>
  64. </div>
  65. </td>
  66. <td class="px-2 py-1" data-col="owner">
  67. {% if currentUser.isAdmin %}
  68. <select data-owner-select
  69. class="w-full rounded border border-slate-200 px-2 py-1 bg-white 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">
  70. <option value="">—</option>
  71. {% for ow in ownerChoices %}
  72. <option value="{{ ow.id }}" {{ tOwnerId == ow.id ? 'selected' : '' }}>
  73. {{ ow.name }}
  74. </option>
  75. {% endfor %}
  76. </select>
  77. {% else %}
  78. {% set ownerName = '—' %}
  79. {% for ow in ownerChoices %}
  80. {% if ow.id == tOwnerId %}{% set ownerName = ow.name %}{% endif %}
  81. {% endfor %}
  82. {{ ownerName }}
  83. {% endif %}
  84. </td>
  85. <td class="px-2 py-1 text-center" data-col="prio">
  86. {% if currentUser.isAdmin %}
  87. <select data-prio-select
  88. class="rounded border border-slate-200 px-2 py-1 bg-white 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">
  89. <option value="1" {{ tPrio == 1 ? 'selected' : '' }}>1</option>
  90. <option value="2" {{ tPrio == 2 ? 'selected' : '' }}>2</option>
  91. </select>
  92. {% else %}
  93. <span class="font-mono">{{ tPrio }}</span>
  94. {% endif %}
  95. </td>
  96. <td class="px-2 py-1 text-center font-mono font-semibold"
  97. data-col="tot" data-task-tot>
  98. {{ fmt_days(tot) }}
  99. </td>
  100. {% for sw in sprintWorkers %}
  101. {% set d = assign[sw.id]|default(0.0) %}
  102. {% set st = isTemplate ? STATUS_ZUGEWIESEN : (statusGrid[t.id][sw.id]|default(STATUS_ZUGEWIESEN)) %}
  103. {% set tdExtraClass = taskStatusEnabled ? ' assign-status-' ~ st : '' %}
  104. <td class="px-1 py-1 text-center whitespace-nowrap{{ tdExtraClass }}"
  105. data-col="sw-{{ sw.id }}"
  106. {% if taskStatusEnabled %}data-assign-cell data-status="{{ st }}" data-sw-id="{{ sw.id }}"{% endif %}
  107. data-sort-value-sw-{{ sw.id }}="{{ d|number_format(2, '.', '') }}">
  108. {% if currentUser.isAdmin %}
  109. <input type="number" min="0" step="0.5"
  110. value="{{ fmt_days(d) }}"
  111. data-assign
  112. data-sw-id="{{ sw.id }}"
  113. class="w-14 rounded border border-slate-200 px-1 py-1 text-center 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">
  114. {% else %}
  115. <span class="font-mono inline-block min-w-[2rem]{% if taskStatusEnabled %} cursor-pointer{% endif %}"
  116. data-assign-readonly
  117. data-sw-id="{{ sw.id }}">{{ fmt_days(d) }}</span>
  118. {% endif %}
  119. </td>
  120. {% endfor %}
  121. </tr>