選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 

237 行
13 KiB

  1. {# templates/team/index.html.twig #}
  2. {% extends 'base.html.twig' %}
  3. {% block title %}Team{% endblock %}
  4. {% block body %}
  5. <div class="crud-page">
  6. <div class="crud-page__header">
  7. <h1 class="crud-page__title">Team</h1>
  8. <button class="btn btn-primary" id="team-invite-btn">Neuer Benutzer</button>
  9. </div>
  10. <div class="crud-tabs">
  11. <button class="crud-tab crud-tab--active" data-tab="active">
  12. Aktiv ({{ activeUsers|length + pendingInvites|length }})
  13. </button>
  14. <button class="crud-tab" data-tab="archived">
  15. Archiviert ({{ archivedUsers|length }})
  16. </button>
  17. </div>
  18. {# ── Aktive User ──────────────────────────────────────────────────── #}
  19. <div class="crud-list" id="team-list" data-tab-panel="active">
  20. {% for au in activeUsers %}
  21. <div class="crud-row"
  22. id="au-{{ au.id }}"
  23. data-id="{{ au.id }}"
  24. data-first-name="{{ au.user.firstName|e('html_attr') }}"
  25. data-last-name="{{ au.user.lastName|e('html_attr') }}"
  26. data-email="{{ au.user.email|e('html_attr') }}"
  27. data-note="{{ au.user.note|default('')|e('html_attr') }}"
  28. data-role="{{ au.role }}"
  29. data-is-self="{{ au.user.id == currentUserId ? '1' : '0' }}">
  30. <div class="crud-row__display">
  31. <div class="crud-row__info">
  32. <span class="crud-row__name">{{ au.user.fullName }}</span>
  33. <span class="crud-row__meta">({{ au.roleLabel }})</span>
  34. {% if au.user.password is null %}
  35. <span class="team-badge team-badge--pending">Einladung ausstehend</span>
  36. {% endif %}
  37. </div>
  38. <div class="crud-row__actions">
  39. <button class="crud-row__btn crud-row__btn--edit"
  40. data-action="edit"
  41. title="Bearbeiten">
  42. <svg viewBox="0 0 16 16" fill="none"><path d="M11 2l3 3L5 14H2v-3L11 2z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
  43. </button>
  44. {% if au.user.id != currentUserId %}
  45. <button class="crud-row__btn crud-row__btn--delete"
  46. data-action="delete"
  47. title="Entfernen">
  48. <svg viewBox="0 0 16 16" fill="none"><path d="M3 4h10M6 4V2h4v2M5 4l.5 9h5l.5-9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
  49. </button>
  50. {% endif %}
  51. </div>
  52. </div>
  53. <div class="crud-row__edit" hidden>
  54. <div class="entry-form__grid entry-form__grid--inline">
  55. <label class="entry-form__label">Vorname</label>
  56. <div class="entry-form__field">
  57. <input type="text" class="input edit-first-name"
  58. value="{{ au.user.firstName }}" />
  59. </div>
  60. <label class="entry-form__label">Nachname</label>
  61. <div class="entry-form__field">
  62. <input type="text" class="input edit-last-name"
  63. value="{{ au.user.lastName }}" />
  64. </div>
  65. <label class="entry-form__label">E-Mail</label>
  66. <div class="entry-form__field">
  67. <input type="email" class="input edit-email"
  68. value="{{ au.user.email }}" />
  69. </div>
  70. <label class="entry-form__label">Bemerkung</label>
  71. <div class="entry-form__field">
  72. <textarea class="textarea edit-note" rows="2">{{ au.user.note|default('') }}</textarea>
  73. </div>
  74. <label class="entry-form__label">Rolle</label>
  75. <div class="entry-form__field">
  76. <div class="team-role-selector{% if au.user.id == currentUserId and au.isAdmin() %} team-role-selector--disabled{% endif %}">
  77. {% set roleDisabled = (au.user.id == currentUserId and au.isAdmin()) ? 'disabled' : '' %}
  78. <label class="team-role-option">
  79. <input type="radio" class="edit-role" name="role-{{ au.id }}"
  80. value="tracker" {{ au.role == 'tracker' ? 'checked' : '' }} {{ roleDisabled }} />
  81. <span class="team-role-option__label">Zeiterfasser</span>
  82. </label>
  83. <label class="team-role-option">
  84. <input type="radio" class="edit-role" name="role-{{ au.id }}"
  85. value="member" {{ au.role == 'member' ? 'checked' : '' }} {{ roleDisabled }} />
  86. <span class="team-role-option__label">Standard-Nutzer</span>
  87. </label>
  88. <label class="team-role-option">
  89. <input type="radio" class="edit-role" name="role-{{ au.id }}"
  90. value="admin" {{ au.role == 'admin' ? 'checked' : '' }} {{ roleDisabled }} />
  91. <span class="team-role-option__label">Administrator</span>
  92. </label>
  93. </div>
  94. {% if au.user.id == currentUserId and au.isAdmin() %}
  95. <p class="team-role-hint">Eigene Administrator-Rolle kann nicht geändert werden.</p>
  96. {% endif %}
  97. </div>
  98. <div class="entry-form__actions">
  99. <button type="button" class="btn btn-primary" data-action="save">Sichern</button>
  100. <button type="button" class="btn btn-secondary" data-action="cancel">Abbrechen</button>
  101. </div>
  102. </div>
  103. </div>
  104. </div>
  105. {% endfor %}
  106. {# ── Ausstehende Einladungen ──────────────────────────────────── #}
  107. {% for invite in pendingInvites %}
  108. <div class="crud-row" id="invite-{{ invite.id }}">
  109. <div class="crud-row__display">
  110. <div class="crud-row__info">
  111. <span class="crud-row__name">{{ invite.firstName }} {{ invite.lastName }}</span>
  112. <span class="crud-row__meta">({{ invite.email }})</span>
  113. <span class="team-badge team-badge--pending">Einladung ausstehend</span>
  114. </div>
  115. <div class="crud-row__actions">
  116. <button class="crud-row__btn crud-row__btn--delete"
  117. data-action="delete-invite"
  118. data-id="{{ invite.id }}"
  119. title="Einladung zurückziehen">
  120. <svg viewBox="0 0 16 16" fill="none"><path d="M3 4h10M6 4V2h4v2M5 4l.5 9h5l.5-9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
  121. </button>
  122. </div>
  123. </div>
  124. </div>
  125. {% endfor %}
  126. {% if activeUsers is empty and pendingInvites is empty %}
  127. <div class="crud-list__empty">Noch keine aktiven Teammitglieder.</div>
  128. {% endif %}
  129. </div>
  130. {# ── Archivierte User ─────────────────────────────────────────────── #}
  131. <div class="crud-list" id="team-list-archived" data-tab-panel="archived" hidden>
  132. {% for au in archivedUsers %}
  133. <div class="crud-row crud-row--archived" id="au-{{ au.id }}" data-id="{{ au.id }}">
  134. <div class="crud-row__display">
  135. <div class="crud-row__info">
  136. <span class="crud-row__name">{{ au.user.fullName }}</span>
  137. <span class="crud-row__meta">({{ au.roleLabel }})</span>
  138. </div>
  139. <div class="crud-row__actions">
  140. <button class="crud-row__btn crud-row__btn--restore"
  141. data-action="unarchive"
  142. title="Wiederherstellen">
  143. <svg viewBox="0 0 16 16" fill="none"><path d="M2 8a6 6 0 1 1 1.5 3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M2 13V9h4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
  144. </button>
  145. </div>
  146. </div>
  147. </div>
  148. {% else %}
  149. <div class="crud-list__empty">Keine archivierten Teammitglieder.</div>
  150. {% endfor %}
  151. </div>
  152. </div>
  153. {# ── Einlade-Modal ────────────────────────────────────────────────────────────── #}
  154. <div class="modal-overlay" id="team-modal" hidden>
  155. <div class="modal-card">
  156. <div class="modal-card__header">
  157. <h2 class="modal-card__title">Neuen Benutzer einladen</h2>
  158. <button class="modal-card__close" id="team-modal-close" type="button">
  159. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  160. <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
  161. </svg>
  162. </button>
  163. </div>
  164. <div id="team-modal-errors" class="form-errors" hidden></div>
  165. <div class="modal-card__body">
  166. <div class="form-row">
  167. <div class="form-field">
  168. <label class="form-field__label" for="inv-firstName">Vorname</label>
  169. <input class="input" type="text" id="inv-firstName" autocomplete="off" />
  170. </div>
  171. <div class="form-field">
  172. <label class="form-field__label" for="inv-lastName">Nachname</label>
  173. <input class="input" type="text" id="inv-lastName" autocomplete="off" />
  174. </div>
  175. </div>
  176. <div class="form-field">
  177. <label class="form-field__label" for="inv-email">E-Mail</label>
  178. <input class="input" type="email" id="inv-email" autocomplete="off" />
  179. </div>
  180. <div class="form-field">
  181. <label class="form-field__label">Rolle</label>
  182. <div class="team-role-selector">
  183. <label class="team-role-option">
  184. <input type="radio" name="inv-role" value="tracker" />
  185. <span class="team-role-option__label">Zeiterfasser</span>
  186. </label>
  187. <label class="team-role-option">
  188. <input type="radio" name="inv-role" value="member" checked />
  189. <span class="team-role-option__label">Standard-Nutzer</span>
  190. </label>
  191. <label class="team-role-option">
  192. <input type="radio" name="inv-role" value="admin" />
  193. <span class="team-role-option__label">Administrator</span>
  194. </label>
  195. </div>
  196. </div>
  197. </div>
  198. <div class="modal-card__footer">
  199. <button class="btn btn-secondary" id="team-modal-cancel" type="button">Abbrechen</button>
  200. <button class="btn btn-cta" id="team-modal-submit" type="button">Einladung senden</button>
  201. </div>
  202. </div>
  203. </div>
  204. {% endblock %}
  205. {% block javascripts %}
  206. {{ parent() }}
  207. {{ encore_entry_script_tags('team') }}
  208. {% endblock %}