Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 

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