Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 

276 wiersze
15 KiB

  1. {# templates/report/times.html.twig #}
  2. {% extends 'base.html.twig' %}
  3. {% set monthsShort = deMonthsShort() %}
  4. {% block title %}{{ 'app.report.page_title'|trans }}{% endblock %}
  5. {% block javascripts %}
  6. {{ parent() }}
  7. {{ encore_entry_script_tags('report') }}
  8. {% endblock %}
  9. {% block body %}
  10. <script>
  11. window.Report = {
  12. trackingInterval: {{ trackingInterval }},
  13. currentUserId: {{ currentUserId }},
  14. isAdmin: {{ isAdmin ? 'true' : 'false' }},
  15. isTracker: {{ isTracker ? 'true' : 'false' }},
  16. limit: {{ limit }},
  17. clients: [
  18. {% for client in clients %}
  19. { id: {{ client.id }}, name: {{ client.name|json_encode|raw }} }{% if not loop.last %},{% endif %}
  20. {% endfor %}
  21. ],
  22. projects: [
  23. {% for project in projects %}
  24. {
  25. id: {{ project.id }},
  26. name: {{ project.name|json_encode|raw }},
  27. clientName: {{ project.client.name|json_encode|raw }} }{% if not loop.last %},{% endif %}
  28. {% endfor %}
  29. ],
  30. services: [
  31. {% for service in services %}
  32. {
  33. id: {{ service.id }},
  34. name: {{ service.name|json_encode|raw }},
  35. billable: {{ service.billable ? 'true' : 'false' }} }{% if not loop.last %},{% endif %}
  36. {% endfor %}
  37. ],
  38. users: {{ userList|json_encode|raw }},
  39. i18n: {
  40. btnSave: {{ 'app.entry.btn_save'|trans|json_encode|raw }},
  41. btnCancel: {{ 'app.entry.btn_cancel'|trans|json_encode|raw }},
  42. btnEdit: {{ 'app.entry.btn_edit'|trans|json_encode|raw }},
  43. btnDelete: {{ 'app.entry.btn_delete'|trans|json_encode|raw }},
  44. confirmDelete: {{ 'app.entry.confirm_delete'|trans|json_encode|raw }},
  45. errorSave: {{ 'app.entry.error_save'|trans|json_encode|raw }},
  46. errorDelete: {{ 'app.entry.error_delete'|trans|json_encode|raw }},
  47. errorNoProject: {{ 'app.entry.error_no_project'|trans|json_encode|raw }},
  48. errorZeroDuration: {{ 'app.entry.error_zero_duration'|trans|json_encode|raw }},
  49. errorDurationTooLong:{{ 'app.entry.error_duration_too_long'|trans|json_encode|raw }},
  50. warnDurationLong: {{ 'app.entry.warn_duration_long'|trans|json_encode|raw }},
  51. billable: {{ 'app.service.billable'|trans|json_encode|raw }},
  52. notBillable: {{ 'app.service.not_billable'|trans|json_encode|raw }},
  53. selectPh: {{ 'app.entry.select_placeholder'|trans|json_encode|raw }},
  54. btnLock: {{ 'app.report.btn_lock'|trans|json_encode|raw }},
  55. btnUnlock: {{ 'app.report.btn_unlock'|trans|json_encode|raw }},
  56. }
  57. };
  58. </script>
  59. <div class="report-page">
  60. <div class="report-header">
  61. <h1 class="report-header__title">{{ 'app.report.heading'|trans }}</h1>
  62. <div class="report-header__right">
  63. <span class="report-account-name">
  64. <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="report-account-name__icon">
  65. <path d="M10 11a4 4 0 100-8 4 4 0 000 8zM3 17a7 7 0 0114 0" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
  66. </svg>
  67. {{ accountName }}
  68. </span>
  69. <nav class="account-tabs">
  70. <a href="{{ path('report_times') }}"
  71. class="account-tab account-tab--active">
  72. {{ 'app.report.tab_times'|trans }}
  73. </a>
  74. <span class="account-tab account-tab--disabled">
  75. {{ 'app.report.tab_projects'|trans }}
  76. </span>
  77. </nav>
  78. </div>
  79. </div>
  80. <div class="report-content">
  81. <div class="report-card">
  82. {# ── Toolbar ──────────────────────────────────────────────────────── #}
  83. <div class="report-toolbar">
  84. <div class="report-toolbar__left">
  85. <button class="report-toolbar__action{% if filterActive %} report-toolbar__action--active{% endif %}"
  86. id="btn-filter-toggle"
  87. type="button">
  88. <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  89. <circle cx="6.5" cy="6.5" r="4" stroke="currentColor" stroke-width="1.3"/>
  90. <path d="M11 11l2.5 2.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
  91. </svg>
  92. {{ 'app.report.toolbar_filter'|trans }}
  93. </button>
  94. </div>
  95. </div>
  96. {# ── Filter-Panel ─────────────────────────────────────────────────── #}
  97. {% include 'report/_filter-panel.html.twig' %}
  98. {# ── Tabellen-Header ───────────────────────────────────────────────── #}
  99. <div class="report-table">
  100. <div class="report-table__head">
  101. <div class="report-table__cell report-table__cell--date">
  102. {{ 'app.report.col_date'|trans }}
  103. <span class="report-table__sort-icon">▾</span>
  104. </div>
  105. <div class="report-table__cell report-table__cell--client">{{ 'app.report.col_client'|trans }}</div>
  106. <div class="report-table__cell report-table__cell--project">{{ 'app.report.col_project'|trans }}</div>
  107. <div class="report-table__cell report-table__cell--service">{{ 'app.report.col_service'|trans }}</div>
  108. <div class="report-table__cell report-table__cell--user">{{ 'app.report.col_user'|trans }}</div>
  109. <div class="report-table__cell report-table__cell--note">{{ 'app.report.col_note'|trans }}</div>
  110. <div class="report-table__cell report-table__cell--duration">
  111. {{ 'app.report.col_hours'|trans }}
  112. <span class="report-table__summary">{{ totalDuration }}</span>
  113. </div>
  114. <div class="report-table__cell report-table__cell--revenue">
  115. {{ 'app.report.col_revenue'|trans }}
  116. <span class="report-table__summary">{{ totalRevenue|number_format(2, ',', '.') }} €</span>
  117. </div>
  118. <div class="report-table__cell report-table__cell--actions"></div>
  119. </div>
  120. {# ── Einträge ──────────────────────────────────────────────────── #}
  121. {% for entry in entries %}
  122. {% set service = entry.service %}
  123. {% set billable = (service is null or service.billable) %}
  124. {% set hourlyRate = entry.project.client.hourlyRate %}
  125. {% set monthShort = monthsShort[entry.date|date('n') - 1] %}
  126. {% set canEdit = isAdmin or (entry.userId == currentUserId) %}
  127. <div class="report-table__row{% if entry.invoiced %} report-table__row--invoiced{% endif %}"
  128. data-entry-id="{{ entry.id }}"
  129. data-user-id="{{ entry.userId }}"
  130. data-project-id="{{ entry.project.id }}"
  131. data-service-id="{{ entry.service ? entry.service.id : '' }}"
  132. data-duration="{{ entry.duration }}"
  133. data-note="{{ entry.note|default('')|e('html_attr') }}"
  134. data-invoiced="{{ entry.invoiced ? 'true' : 'false' }}">
  135. <div class="report-table__cell report-table__cell--date">
  136. {{ entry.date|date('j') }}. {{ monthShort }} {{ entry.date|date('y') }}
  137. </div>
  138. <div class="report-table__cell report-table__cell--client">
  139. {{ entry.project.client.name }}
  140. </div>
  141. <div class="report-table__cell report-table__cell--project">
  142. {{ entry.project.name }}
  143. </div>
  144. <div class="report-table__cell report-table__cell--service">
  145. {{ service ? service.name : '' }}
  146. </div>
  147. <div class="report-table__cell report-table__cell--user">
  148. {{ userMap[entry.userId] ?? ('User #' ~ entry.userId) }}
  149. </div>
  150. <div class="report-table__cell report-table__cell--note">
  151. {{ entry.note }}
  152. </div>
  153. <div class="report-table__cell report-table__cell--duration">
  154. {{ entry.durationFormatted }}
  155. </div>
  156. <div class="report-table__cell report-table__cell--revenue">
  157. {% if billable and hourlyRate is not null %}
  158. {{ (hourlyRate * entry.duration / 60)|number_format(2, ',', '.') }} €
  159. {% endif %}
  160. </div>
  161. <div class="report-table__cell report-table__cell--actions">
  162. {% if canEdit and not entry.invoiced %}
  163. <button class="report-action-btn report-action-btn--edit"
  164. data-action="edit"
  165. title="{{ 'app.entry.btn_edit'|trans }}">
  166. {% include '_atoms/icon-edit.html.twig' %}
  167. </button>
  168. <button class="report-action-btn report-action-btn--delete"
  169. data-action="delete"
  170. title="{{ 'app.entry.btn_delete'|trans }}">
  171. {% include '_atoms/icon-delete.html.twig' %}
  172. </button>
  173. {% endif %}
  174. {% if canEdit %}
  175. <button class="report-lock{% if entry.invoiced %} report-lock--invoiced{% endif %}"
  176. data-action="toggle-invoiced"
  177. title="{{ entry.invoiced ? 'app.report.btn_unlock'|trans : 'app.report.btn_lock'|trans }}">
  178. {% include '_atoms/icon-lock.html.twig' %}
  179. </button>
  180. {% endif %}
  181. </div>
  182. {# ── Inline-Edit-Formular ───────────────────────────────── #}
  183. {% if canEdit and not entry.invoiced %}
  184. <div class="report-row__edit" hidden>
  185. <div class="report-row__edit-grid">
  186. <label class="report-row__edit-label">{{ 'app.entry.label_duration'|trans }}</label>
  187. <div class="report-row__edit-field">
  188. <input type="text"
  189. class="input input--sm edit-duration"
  190. value="{{ entry.durationFormatted }}"
  191. autocomplete="off" />
  192. {% include '_atoms/duration-help.html.twig' %}
  193. </div>
  194. <label class="report-row__edit-label">{{ 'app.entry.label_project_service'|trans }}</label>
  195. <div class="report-row__edit-field report-row__edit-field--selects">
  196. <select class="select edit-project"></select>
  197. <select class="select edit-service"></select>
  198. </div>
  199. <label class="report-row__edit-label">{{ 'app.entry.label_note'|trans }}</label>
  200. <div class="report-row__edit-field">
  201. <textarea class="textarea edit-note" rows="2">{{ entry.note|default('') }}</textarea>
  202. </div>
  203. <div class="report-row__edit-actions">
  204. <button type="button" class="btn btn-primary" data-action="save">
  205. {{ 'app.entry.btn_save'|trans }}
  206. </button>
  207. <button type="button" class="btn btn-secondary" data-action="cancel">
  208. {{ 'app.entry.btn_cancel'|trans }}
  209. </button>
  210. </div>
  211. </div>
  212. </div>
  213. {% endif %}
  214. </div>
  215. {% else %}
  216. <div class="report-table__empty">{{ 'app.report.no_entries'|trans }}</div>
  217. {% endfor %}
  218. {# ── Pagination-Footer ─────────────────────────────────────────── #}
  219. <div class="report-pagination">
  220. <div class="report-pagination__limits">
  221. {{ 'app.report.show'|trans }}
  222. {% for l in validLimits %}
  223. {% if l == limit %}
  224. <strong>{{ l }}</strong>
  225. {% else %}
  226. <a href="{{ path('report_times', {limit: l}) }}">{{ l }}</a>
  227. {% endif %}
  228. {% endfor %}
  229. {{ 'app.report.of_total'|trans({'%count%': totalCount|number_format(0, ',', '.')}) }}
  230. </div>
  231. <span class="report-pagination__duration">{{ totalDuration }}</span>
  232. <span class="report-pagination__revenue">{{ totalRevenue|number_format(2, ',', '.') }} €</span>
  233. <span class="report-pagination__lock-spacer"></span>
  234. </div>
  235. </div>{# /.report-table #}
  236. </div>{# /.report-card #}
  237. </div>{# /.report-content #}
  238. </div>{# /.report-page #}
  239. {% endblock %}