You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

326 rivejä
19 KiB

  1. {# templates/account/index.html.twig #}
  2. {% extends 'base.html.twig' %}
  3. {% block title %}
  4. {% if tab == 'import' %}{{ 'app.import.tab'|trans }}{% elseif tab == 'account' %}{{ 'app.account.page_title_account'|trans }}{% else %}{{ 'app.account.page_title_user'|trans }}{% endif %}
  5. {% endblock %}
  6. {% block body %}
  7. <script>
  8. window.ACCOUNT = {
  9. tab: '{{ tab }}',
  10. isSuperAdmin: {{ isSuperAdmin ? 'true' : 'false' }},
  11. theme: '{{ user.theme|default('standard') }}',
  12. i18n: {
  13. invalidHex: {{ 'app.account.invalid_hex'|trans|json_encode|raw }},
  14. saved: {{ 'app.account.saved'|trans|json_encode|raw }},
  15. savedReloading: {{ 'app.account.saved_reloading'|trans|json_encode|raw }},
  16. ownerChanged: {{ 'app.account.owner_changed'|trans|json_encode|raw }},
  17. ownerConfirm: {{ 'app.account.owner_confirm'|trans|json_encode|raw }},
  18. themeChanged: {{ 'app.account.theme_changed'|trans|json_encode|raw }},
  19. passwordMismatch: {{ 'app.account.password_mismatch'|trans|json_encode|raw }},
  20. changeLabel: {{ 'app.account.change_label'|trans|json_encode|raw }},
  21. cancelLabel: {{ 'app.account.cancel_label'|trans|json_encode|raw }},
  22. errorGeneric: {{ 'app.account.error_generic'|trans|json_encode|raw }},
  23. importAnalyze: {{ 'app.import.btn_analyze'|trans|json_encode|raw }},
  24. importAnalyzing: {{ 'app.import.btn_analyzing'|trans|json_encode|raw }},
  25. importExecute: {{ 'app.import.btn_execute'|trans|json_encode|raw }},
  26. importExecuting: {{ 'app.import.btn_executing'|trans|json_encode|raw }},
  27. importSuccess: {{ 'app.import.success'|trans|json_encode|raw }},
  28. importConfirm: {{ 'app.import.confirm'|trans|json_encode|raw }},
  29. importErrorNoFile: {{ 'app.import.error_no_file'|trans|json_encode|raw }},
  30. importLabelClients: {{ 'app.import.label_clients'|trans|json_encode|raw }},
  31. importLabelProjects: {{ 'app.import.label_projects'|trans|json_encode|raw }},
  32. importLabelServices: {{ 'app.import.label_services'|trans|json_encode|raw }},
  33. importLabelEntries: {{ 'app.import.label_entries'|trans|json_encode|raw }},
  34. importLabelUsers: {{ 'app.import.label_users'|trans|json_encode|raw }},
  35. importLabelDateRange: {{ 'app.import.label_date_range'|trans|json_encode|raw }},
  36. importLabelWarnings: {{ 'app.import.label_warnings'|trans|json_encode|raw }},
  37. importUserMatched: {{ 'app.import.user_matched'|trans|json_encode|raw }},
  38. importUserFallback: {{ 'app.import.user_fallback'|trans|json_encode|raw }},
  39. importNewLabel: {{ 'app.import.new_label'|trans|json_encode|raw }},
  40. importExistingLabel: {{ 'app.import.existing_label'|trans|json_encode|raw }},
  41. importResultClients: {{ 'app.import.result_clients'|trans|json_encode|raw }},
  42. importResultProjects: {{ 'app.import.result_projects'|trans|json_encode|raw }},
  43. importResultServices: {{ 'app.import.result_services'|trans|json_encode|raw }},
  44. importResultEntries: {{ 'app.import.result_entries'|trans|json_encode|raw }},
  45. importResultUsers: {{ 'app.import.result_users'|trans|json_encode|raw }},
  46. importUserAssignMe: {{ 'app.import.user_assign_me'|trans|json_encode|raw }},
  47. importUserCreate: {{ 'app.import.user_create'|trans|json_encode|raw }},
  48. },
  49. };
  50. </script>
  51. <div class="account-page">
  52. <div class="account-header">
  53. <h1 class="account-header__title">
  54. {% if tab == 'import' %}{{ 'app.import.tab'|trans }}{% elseif tab == 'account' %}{{ 'app.account.page_title_account'|trans }}{% else %}{{ 'app.account.page_title_user'|trans }}{% endif %}
  55. </h1>
  56. {% if isAdmin %}
  57. <nav class="account-tabs">
  58. <a href="{{ path('account_index', {tab: 'account'}) }}"
  59. class="account-tab{% if tab == 'account' %} account-tab--active{% endif %}">
  60. {{ 'app.account.tab_account'|trans }}
  61. </a>
  62. <a href="{{ path('account_index', {tab: 'user'}) }}"
  63. class="account-tab{% if tab == 'user' %} account-tab--active{% endif %}">
  64. {{ 'app.account.tab_user'|trans }}
  65. </a>
  66. <a href="{{ path('account_index', {tab: 'import'}) }}"
  67. class="account-tab{% if tab == 'import' %} account-tab--active{% endif %}">
  68. {{ 'app.import.tab'|trans }}
  69. </a>
  70. </nav>
  71. {% endif %}
  72. </div>
  73. <div class="account-content">
  74. {# ── Import-Tab (nur Admin) ────────────────────────────────────────── #}
  75. {% if tab == 'import' and isAdmin %}
  76. <div class="account-card">
  77. <h2 class="import-section__title">{{ 'app.import.title_mite'|trans }}</h2>
  78. <p class="import-section__desc">{{ 'app.import.desc_mite'|trans }}</p>
  79. <div class="import-upload" id="import-upload">
  80. <label class="import-upload__area" id="import-drop-area">
  81. <input type="file" id="import-file" accept=".xml" hidden />
  82. <span class="import-upload__icon">
  83. <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  84. <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
  85. <polyline points="17 8 12 3 7 8"/>
  86. <line x1="12" y1="3" x2="12" y2="15"/>
  87. </svg>
  88. </span>
  89. <span class="import-upload__text">{{ 'app.import.upload_text'|trans }}</span>
  90. <span class="import-upload__hint">{{ 'app.import.upload_hint'|trans }}</span>
  91. </label>
  92. <div class="import-upload__file-info" id="import-file-info" hidden>
  93. <span id="import-file-name"></span>
  94. <button type="button" class="import-upload__remove" id="import-file-remove">&times;</button>
  95. </div>
  96. </div>
  97. <div class="import-actions" id="import-actions">
  98. <button type="button" class="btn btn-primary" id="btn-import-analyze" disabled>
  99. {{ 'app.import.btn_analyze'|trans }}
  100. </button>
  101. </div>
  102. </div>
  103. <div class="account-card" id="import-preview" hidden>
  104. <h2 class="import-section__title">{{ 'app.import.title_preview'|trans }}</h2>
  105. <div id="import-preview-content"></div>
  106. <div class="import-actions import-actions--preview">
  107. <button type="button" class="btn btn-primary" id="btn-import-execute">
  108. {{ 'app.import.btn_execute'|trans }}
  109. </button>
  110. <button type="button" class="btn btn-secondary" id="btn-import-reset">
  111. {{ 'app.import.btn_reset'|trans }}
  112. </button>
  113. </div>
  114. </div>
  115. <div class="account-card import-result" id="import-result" hidden>
  116. <h2 class="import-section__title">{{ 'app.import.title_result'|trans }}</h2>
  117. <div id="import-result-content"></div>
  118. </div>
  119. {% else %}
  120. <div class="account-card">
  121. {# ── Account-Tab (nur Admin) ────────────────────────────────────────── #}
  122. {% if tab == 'account' and isAdmin %}
  123. <div class="account-form__grid" id="account-form">
  124. <label class="account-form__label" for="account-name">{{ 'app.account.label_company_name'|trans }}</label>
  125. <div class="account-form__field">
  126. <input type="text" id="account-name" class="input"
  127. value="{{ account.name|e('html_attr') }}" />
  128. <span class="account-form__hint">
  129. {{ 'app.account.hint_subdomain'|trans({'%subdomain%': account.slug ~ '.' ~ (app.request.host|split('.')|slice(1)|join('.'))}) }}
  130. </span>
  131. </div>
  132. <label class="account-form__label" for="account-interval">{{ 'app.account.label_interval'|trans }}</label>
  133. <div class="account-form__field">
  134. <select id="account-interval" class="select">
  135. {% for value, label in intervalOptions %}
  136. <option value="{{ value }}"{% if account.trackingInterval == value %} selected{% endif %}>
  137. {{ label }}
  138. </option>
  139. {% endfor %}
  140. </select>
  141. <span class="account-form__hint">{{ 'app.account.hint_interval'|trans }}</span>
  142. </div>
  143. {% if isSuperAdmin %}
  144. <label class="account-form__label" for="account-color">{{ 'app.account.label_color'|trans }}</label>
  145. <div class="account-form__field">
  146. <div class="account-color-field">
  147. <input type="color" id="account-color-picker"
  148. value="{{ account.primaryColor ?? '#3a7bbf' }}"
  149. class="account-color-field__swatch" />
  150. <input type="text" id="account-color"
  151. value="{{ account.primaryColor ?? '#3a7bbf' }}"
  152. class="input account-color-field__hex"
  153. maxlength="7" placeholder="#3a7bbf" autocomplete="off" />
  154. </div>
  155. <span class="account-form__hint">{{ 'app.account.hint_color'|trans }}</span>
  156. </div>
  157. {% endif %}
  158. <div class="account-form__divider-row">
  159. <hr class="account-form__divider">
  160. </div>
  161. <label class="account-form__label">{{ 'app.account.label_lexoffice_key'|trans }}</label>
  162. <div class="account-form__field">
  163. {% if account.hasLexofficeApiKey() %}
  164. <span class="account-form__key-status" id="lexoffice-key-status">
  165. <span class="account-form__key-mask">••••••••</span>
  166. <a href="#" class="account-form__link" id="btn-lexoffice-key-change">{{ 'app.account.change_label'|trans }}</a>
  167. </span>
  168. {% endif %}
  169. <input type="text" id="account-lexoffice-key" class="input"
  170. {{ account.hasLexofficeApiKey() ? 'hidden' : '' }}
  171. placeholder="{{ 'app.account.placeholder_lexoffice_key'|trans }}"
  172. autocomplete="off" data-1p-ignore data-lpignore="true" />
  173. <span class="account-form__hint">{{ 'app.account.hint_lexoffice_key'|trans }}</span>
  174. </div>
  175. <div class="account-form__actions">
  176. <button type="button" class="btn btn-primary" id="btn-account-save">{{ 'app.entry.btn_save'|trans }}</button>
  177. <a href="{{ path('account_index', {tab: 'account'}) }}" class="btn btn-secondary">{{ 'app.entry.btn_cancel'|trans }}</a>
  178. </div>
  179. </div>
  180. {# ── Benutzer-Tab ──────────────────────────────────────────────────── #}
  181. {% else %}
  182. <div class="account-form__grid" id="user-form">
  183. <label class="account-form__label" for="user-firstname">{{ 'app.account.label_first_name'|trans }}</label>
  184. <div class="account-form__field">
  185. <input type="text" id="user-firstname" class="input"
  186. value="{{ user.firstName|e('html_attr') }}" />
  187. </div>
  188. <label class="account-form__label" for="user-lastname">{{ 'app.account.label_last_name'|trans }}</label>
  189. <div class="account-form__field">
  190. <input type="text" id="user-lastname" class="input"
  191. value="{{ user.lastName|e('html_attr') }}" />
  192. </div>
  193. <label class="account-form__label" for="user-email">{{ 'app.account.label_email'|trans }}</label>
  194. <div class="account-form__field">
  195. <input type="email" id="user-email" class="input"
  196. value="{{ user.email|e('html_attr') }}" />
  197. </div>
  198. <label class="account-form__label">{{ 'app.account.label_password'|trans }}</label>
  199. <div class="account-form__field">
  200. <a href="#" class="account-form__link" id="btn-pw-toggle">{{ 'app.account.change_label'|trans }}</a>
  201. </div>
  202. <div class="account-form__pw-section" id="pw-section" hidden>
  203. <label class="account-form__label" for="user-pw-current">{{ 'app.account.label_current_password'|trans }}</label>
  204. <div class="account-form__field">
  205. <input type="password" id="user-pw-current" class="input" autocomplete="current-password" />
  206. </div>
  207. <label class="account-form__label" for="user-pw-new">{{ 'app.account.label_new_password'|trans }}</label>
  208. <div class="account-form__field">
  209. <input type="password" id="user-pw-new" class="input" autocomplete="new-password" minlength="8" />
  210. </div>
  211. <label class="account-form__label" for="user-pw-repeat">{{ 'app.account.label_password_repeat'|trans }}</label>
  212. <div class="account-form__field">
  213. <input type="password" id="user-pw-repeat" class="input" autocomplete="new-password" />
  214. </div>
  215. </div>
  216. <div class="account-form__actions">
  217. <button type="button" class="btn btn-primary" id="btn-user-save">{{ 'app.entry.btn_save'|trans }}</button>
  218. <a href="{{ path('account_index', {tab: 'user'}) }}" class="btn btn-secondary">{{ 'app.entry.btn_cancel'|trans }}</a>
  219. </div>
  220. </div>
  221. {# ── Darstellung ───────────────────────────────────────────────────── #}
  222. <div class="account-form__grid account-form__grid--appearance" id="appearance-form">
  223. <div class="account-form__divider-row">
  224. <hr class="account-form__divider">
  225. </div>
  226. <label class="account-form__label">{{ 'app.account.label_appearance'|trans }}</label>
  227. <div class="account-form__field">
  228. <div class="theme-picker" id="theme-picker">
  229. <label class="theme-option{% if user.theme|default('standard') == 'standard' %} theme-option--active{% endif %}" data-theme="standard">
  230. <input type="radio" name="theme" value="standard"{% if user.theme|default('standard') == 'standard' %} checked{% endif %}>
  231. <span class="theme-option__label">{{ 'app.account.theme_standard'|trans }}</span>
  232. <span class="theme-option__desc">{{ 'app.account.theme_standard_desc'|trans }}</span>
  233. </label>
  234. <label class="theme-option{% if user.theme|default('standard') == 'minimal' %} theme-option--active{% endif %}" data-theme="minimal">
  235. <input type="radio" name="theme" value="minimal"{% if user.theme|default('standard') == 'minimal' %} checked{% endif %}>
  236. <span class="theme-option__label">{{ 'app.account.theme_minimal'|trans }}</span>
  237. <span class="theme-option__desc">{{ 'app.account.theme_minimal_desc'|trans }}</span>
  238. </label>
  239. </div>
  240. </div>
  241. </div>
  242. {% endif %}
  243. </div>
  244. {# ── Besitzer des Accounts (nur Admin, Account-Tab) ─────────────────── #}
  245. {% if tab == 'account' and isAdmin %}
  246. <div class="account-card account-card--owner">
  247. <div class="account-form__grid">
  248. <label class="account-form__label" for="superadmin-select">{{ 'app.account.label_owner'|trans }}</label>
  249. <div class="account-form__field">
  250. <select id="superadmin-select" class="select"
  251. {% if superAdminUserId != user.id %}disabled{% endif %}>
  252. {% for admin in adminUsers %}
  253. <option value="{{ admin.id }}"{% if admin.id == superAdminUserId %} selected{% endif %}>
  254. {{ admin.name }}
  255. </option>
  256. {% endfor %}
  257. </select>
  258. <p class="account-form__hint account-form__hint--owner">
  259. {{ 'app.account.hint_owner'|trans }}
  260. </p>
  261. </div>
  262. </div>
  263. </div>
  264. {% endif %}
  265. {% endif %}{# Ende Import-else #}
  266. </div>
  267. </div>
  268. <div class="account-toast" id="account-toast"></div>
  269. {% endblock %}
  270. {% block javascripts %}
  271. {{ parent() }}
  272. {{ encore_entry_script_tags('account') }}
  273. {% endblock %}