Sfoglia il codice sorgente

beautify html

master
FlorianEisenmenger 1 settimana fa
parent
commit
2f73217759
14 ha cambiato i file con 336 aggiunte e 199 eliminazioni
  1. +49
    -15
      httpdocs/README.md
  2. +8
    -8
      httpdocs/templates/_nav.html.twig
  3. +6
    -7
      httpdocs/templates/email/registration_confirm.html.twig
  4. +7
    -7
      httpdocs/templates/email/registration_notify.html.twig
  5. +5
    -5
      httpdocs/templates/email/registration_welcome.html.twig
  6. +9
    -8
      httpdocs/templates/email/team_invite.html.twig
  7. +3
    -3
      httpdocs/templates/invite/error.html.twig
  8. +6
    -6
      httpdocs/templates/invite/set_password.html.twig
  9. +4
    -4
      httpdocs/templates/registration/confirm_error.html.twig
  10. +6
    -6
      httpdocs/templates/registration/confirmed.html.twig
  11. +16
    -16
      httpdocs/templates/registration/register.html.twig
  12. +6
    -6
      httpdocs/templates/security/login.html.twig
  13. +78
    -78
      httpdocs/templates/service/index.html.twig
  14. +133
    -30
      httpdocs/translations/messages.de.yaml

+ 49
- 15
httpdocs/README.md Vedi File

@@ -1,33 +1,54 @@
# INSTALLATION
# spawntree Timetracker

## Installation

```bash
cd httpdocs
ddev start => Läuft dann unter https://timetracking.ddev.site:8459
ddev start
# Läuft dann unter https://timetracking.ddev.site:8459

ddev exec composer install
ddev exec npm install
sh 1-reset-and-seed.sh (Entweder mit oder ohne Testdaten)

# Dann SCSS / JS bauen und beobachten:
sh 1-reset-and-seed.sh
# Entweder mit oder ohne Testdaten

ddev exec npm run watch
```

---

## Live-Setup (einmalig)

# LIVE EINMALIG AUSFÜHREN:
GRANT ALL PRIVILEGES ON \`db_%\`.* TO 'deindbuser'@'%'; FLUSH PRIVILEGES;
```sql
GRANT ALL PRIVILEGES ON `db_%`.* TO 'deindbuser'@'%'; FLUSH PRIVILEGES;
```

# Central Entity geändert → Migration erstellen + ausführen:
---

## Migrationen

### Central Entity geändert

```bash
ddev exec php bin/console doctrine:migrations:diff --em=central --namespace=DoctrineMigrations
ddev exec php bin/console doctrine:migrations:migrate --em=central --no-interaction
```

### Tenant Entity geändert

Kein Migrations-Workflow — die Tenant-DB wird per SchemaTool neu angelegt:

# Tenant Entity geändert → kein Migrations-Workflow.
```bash
sh reset-and-seed.sh
# Das legt die Tenant-DB per SchemaTool neu an.
```

# Alle URLs anzeigen
ddev describe
---

# Cache clear
ddev exec php bin/console cache:clear
## Webpack / Assets

# WEBPACK
# Einmalig bauen (Dev)
```bash
# Dev (einmalig)
ddev exec npm run dev

# Watch-Modus (bei Änderungen automatisch neu bauen)
@@ -35,3 +56,16 @@ ddev exec npm run watch

# Production Build
ddev exec npm run build
```

---

## Hilfsbefehle

```bash
# Alle URLs anzeigen
ddev describe

# Cache leeren
ddev exec php bin/console cache:clear
```

+ 8
- 8
httpdocs/templates/_nav.html.twig Vedi File

@@ -5,37 +5,37 @@
<div class="main-nav__left">
<a href="{{ path('timetracking_week') }}"
class="main-nav__item{% if currentRoute starts with 'timetracking' %} main-nav__item--active{% endif %}">
Zeit erfassen
{{ 'app.nav.time_tracking'|trans }}
</a>
<span class="main-nav__item main-nav__item--disabled">Reports</span>
<span class="main-nav__item main-nav__item--disabled">{{ 'app.nav.reports'|trans }}</span>
</div>
<div class="main-nav__right">
{% if isCurrentUserMemberOrAdmin() %}
<a href="{{ path('client_index') }}"
class="main-nav__item{% if currentRoute starts with 'client' %} main-nav__item--active{% endif %}">
Kunden
{{ 'app.nav.clients'|trans }}
</a>
<a href="{{ path('project_index') }}"
class="main-nav__item{% if currentRoute starts with 'project' %} main-nav__item--active{% endif %}">
Projekte
{{ 'app.nav.projects'|trans }}
</a>
<a href="{{ path('service_index') }}"
class="main-nav__item{% if currentRoute starts with 'service' %} main-nav__item--active{% endif %}">
Leistungen
{{ 'app.nav.services'|trans }}
</a>
{% endif %}
{% if isCurrentUserAdmin() %}
<a href="{{ path('team_index') }}"
class="main-nav__item{% if currentRoute starts with 'team' %} main-nav__item--active{% endif %}">
Team
{{ 'app.nav.team'|trans }}
</a>
{% endif %}
<a href="{{ path('account_index') }}"
class="main-nav__item{% if currentRoute starts with 'account' %} main-nav__item--active{% endif %}">
Account
{{ 'app.nav.account'|trans }}
</a>
<a href="{{ path('app_logout') }}" class="main-nav__item">
Abmelden
{{ 'app.nav.logout'|trans }}
</a>
</div>
</nav>

+ 6
- 7
httpdocs/templates/email/registration_confirm.html.twig Vedi File

@@ -8,23 +8,22 @@
<p style="font-size: 1.2rem; font-weight: 700; color: #1a2a3a; margin: 0 0 8px;">spawntree Timetracker</p>
<hr style="border: none; border-top: 1px solid #d0d8e0; margin: 0 0 32px;">

<p style="color: #3a4a5a; margin: 0 0 16px;">Hallo {{ token.firstName }},</p>
<p style="color: #3a4a5a; margin: 0 0 16px;">{{ 'app.email.confirm.greeting'|trans({'%name%': token.firstName}) }}</p>
<p style="color: #3a4a5a; margin: 0 0 24px;">
bitte bestätige deine Registrierung für <strong>{{ token.companyName }}</strong>
mit einem Klick auf den Button.
{{ 'app.email.confirm.body'|trans({'%company%': token.companyName}) }}
</p>

<div style="text-align: center; margin: 32px 0;">
<a href="{{ verifyUrl }}"
style="display: inline-block; background: #f0a500; color: #fff; font-weight: 700;
text-decoration: none; padding: 14px 40px; border-radius: 100px; font-size: 1rem;">
E-Mail bestätigen
{{ 'app.email.confirm.btn'|trans }}
</a>
</div>

<p style="color: #7a8a9a; font-size: 0.8rem; margin: 24px 0 0;">
Der Link ist 24 Stunden gültig (bis {{ token.expiresAt|date('d.m.Y H:i') }} Uhr).<br>
Falls du dich nicht registriert hast, kannst du diese E-Mail ignorieren.
{{ 'app.email.confirm.expiry'|trans({'%expires%': token.expiresAt|date('d.m.Y H:i')}) }}<br>
{{ 'app.email.confirm.ignore'|trans }}
</p>
<p style="color: #aab8c6; font-size: 0.75rem; margin: 8px 0 0; word-break: break-all;">
{{ verifyUrl }}
@@ -32,4 +31,4 @@

</div>
</body>
</html>
</html>

+ 7
- 7
httpdocs/templates/email/registration_notify.html.twig Vedi File

@@ -3,13 +3,13 @@
<html lang="de">
<head><meta charset="UTF-8"></head>
<body style="font-family: monospace; padding: 20px; color: #1a2a3a;">
<p><strong>Neue Registrierung im Timetracker</strong></p>
<p><strong>{{ 'app.email.notify.title'|trans }}</strong></p>
<table>
<tr><td style="padding-right: 16px; color: #7a8a9a;">Firma</td><td>{{ account.name }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">Slug</td><td>{{ account.slug }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">Name</td><td>{{ user.fullName }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">E-Mail</td><td>{{ user.email }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">Datum</td><td>{{ account.createdAt|date('d.m.Y H:i') }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">{{ 'app.email.notify.col_company'|trans }}</td><td>{{ account.name }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">{{ 'app.email.notify.col_slug'|trans }}</td><td>{{ account.slug }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">{{ 'app.email.notify.col_name'|trans }}</td><td>{{ user.fullName }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">{{ 'app.email.notify.col_email'|trans }}</td><td>{{ user.email }}</td></tr>
<tr><td style="padding-right: 16px; color: #7a8a9a;">{{ 'app.email.notify.col_date'|trans }}</td><td>{{ account.createdAt|date('d.m.Y H:i') }}</td></tr>
</table>
</body>
</html>
</html>

+ 5
- 5
httpdocs/templates/email/registration_welcome.html.twig Vedi File

@@ -8,23 +8,23 @@
<p style="font-size: 1.2rem; font-weight: 700; color: #1a2a3a; margin: 0 0 8px;">spawntree Timetracker</p>
<hr style="border: none; border-top: 1px solid #d0d8e0; margin: 0 0 32px;">

<p style="color: #3a4a5a; margin: 0 0 16px;">Hallo {{ user.firstName }},</p>
<p style="color: #3a4a5a; margin: 0 0 16px;">{{ 'app.email.welcome.greeting'|trans({'%name%': user.firstName}) }}</p>
<p style="color: #3a4a5a; margin: 0 0 16px;">
dein Konto für <strong>{{ account.name }}</strong> ist jetzt aktiv. Los geht's!
{{ 'app.email.welcome.body'|trans({'%company%': account.name}) }}
</p>

<div style="text-align: center; margin: 32px 0;">
<a href="{{ loginUrl }}"
style="display: inline-block; background: #4a90d9; color: #fff; font-weight: 700;
text-decoration: none; padding: 14px 40px; border-radius: 100px; font-size: 1rem;">
Zum Timetracker →
{{ 'app.email.welcome.btn'|trans }}
</a>
</div>

<p style="color: #7a8a9a; font-size: 0.8rem; margin: 24px 0 0;">
Deine URL: <a href="{{ loginUrl }}" style="color: #4a90d9;">{{ loginUrl }}</a>
{{ 'app.email.welcome.url_label'|trans }} <a href="{{ loginUrl }}" style="color: #4a90d9;">{{ loginUrl }}</a>
</p>

</div>
</body>
</html>
</html>

+ 9
- 8
httpdocs/templates/email/team_invite.html.twig Vedi File

@@ -6,27 +6,28 @@
<div style="max-width: 480px; margin: 0 auto; background: #ffffff; border-radius: 8px; padding: 32px;">

<p style="font-size: 18px; font-weight: bold; margin: 0 0 8px;">
Du wurdest zu {{ invite.account.name }} eingeladen
{{ 'app.email.invite.title'|trans({'%company%': invite.account.name}) }}
</p>
<p style="color: #6b7a8d; margin: 0 0 24px;">
Hallo {{ invite.firstName }},<br>
du wurdest als <strong>{{ invite.role == 'admin' ? 'Administrator' : (invite.role == 'tracker' ? 'Zeiterfasser' : 'Standard-Nutzer') }}</strong>
zum Team von <strong>{{ invite.account.name }}</strong> hinzugefügt.
{{ 'app.email.invite.greeting'|trans({'%name%': invite.firstName}) }}<br>
{{ 'app.email.invite.role_added_pre'|trans }}
<strong>{{ invite.role == 'admin' ? 'app.email.invite.role_admin'|trans : (invite.role == 'tracker' ? 'app.email.invite.role_tracker'|trans : 'app.email.invite.role_member'|trans) }}</strong>
{{ 'app.email.invite.role_added_post'|trans({'%company%': invite.account.name}) }}
</p>

<p style="margin: 0 0 24px;">
Klicke auf den Button, um dein Passwort festzulegen und loszulegen. Der Link ist 7 Tage gültig.
{{ 'app.email.invite.cta'|trans }}
</p>

<a href="{{ inviteUrl }}"
style="display:inline-block; background:#e8820a; color:#ffffff; padding:12px 28px;
border-radius:24px; text-decoration:none; font-weight:bold;">
Passwort festlegen →
{{ 'app.email.invite.btn'|trans }}
</a>

<p style="margin: 24px 0 0; font-size: 12px; color: #9aa5b1;">
Wenn du diese Einladung nicht erwartet hast, kannst du diese E-Mail ignorieren.
{{ 'app.email.invite.ignore'|trans }}
</p>
</div>
</body>
</html>
</html>

+ 3
- 3
httpdocs/templates/invite/error.html.twig Vedi File

@@ -4,13 +4,13 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fehler – Einladungslink</title>
<title>{{ 'app.invite_error.page_title'|trans }}</title>
{{ encore_entry_link_tags('app') }}
</head>
<body class="login-body">
<div class="login-card">
<div class="login-card__title">Ungültiger Link</div>
<div class="login-card__title">{{ 'app.invite_error.title'|trans }}</div>
<div class="login-card__error">{{ error }}</div>
</div>
</body>
</html>
</html>

+ 6
- 6
httpdocs/templates/invite/set_password.html.twig Vedi File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Passwort festlegen – {{ invite.account.name }}</title>
<title>{{ 'app.set_password.page_title'|trans({'%name%': invite.account.name}) }}</title>
{{ encore_entry_link_tags('app') }}
</head>
<body class="login-body">
@@ -12,7 +12,7 @@
<div class="login-card">

<div class="login-card__title">{{ invite.account.name }}</div>
<p class="login-card__sub">Hallo {{ invite.firstName }}, lege dein Passwort fest, um loszulegen.</p>
<p class="login-card__sub">{{ 'app.set_password.subtitle'|trans({'%name%': invite.firstName}) }}</p>

{% if error %}
<div class="login-card__error">{{ error }}</div>
@@ -22,7 +22,7 @@

<div class="login-form__grid">

<label class="login-form__label" for="password">Passwort</label>
<label class="login-form__label" for="password">{{ 'app.set_password.label_password'|trans }}</label>
<div class="login-form__field">
<input type="password"
id="password"
@@ -34,7 +34,7 @@
minlength="8" />
</div>

<label class="login-form__label" for="passwordRepeat">Wiederholen</label>
<label class="login-form__label" for="passwordRepeat">{{ 'app.set_password.label_password_repeat'|trans }}</label>
<div class="login-form__field">
<input type="password"
id="passwordRepeat"
@@ -47,7 +47,7 @@
</div>

<div class="login-form__actions">
<button type="submit" class="btn btn-primary login-form__submit">Passwort speichern & anmelden</button>
<button type="submit" class="btn btn-primary login-form__submit">{{ 'app.set_password.btn_submit'|trans }}</button>
</div>

</form>
@@ -55,4 +55,4 @@
</div>

</body>
</html>
</html>

+ 4
- 4
httpdocs/templates/registration/confirm_error.html.twig Vedi File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fehler – spawntree Timetracker</title>
<title>{{ 'app.confirm_error.page_title'|trans }}</title>
{{ encore_entry_link_tags('app') }}
</head>
<body class="register-body">
@@ -13,14 +13,14 @@
<div class="register-card">
<div class="register-success">
<div class="register-success__icon register-success__icon--error">✕</div>
<h1 class="register-success__title">Link ungültig</h1>
<h1 class="register-success__title">{{ 'app.confirm_error.title'|trans }}</h1>
<p class="register-success__text">{{ error }}</p>
<a href="{{ path('app_register') }}" class="btn btn-primary" style="margin-top: 1.5rem;">
Erneut registrieren
{{ 'app.confirm_error.btn_retry'|trans }}
</a>
</div>
</div>
</div>

</body>
</html>
</html>

+ 6
- 6
httpdocs/templates/registration/confirmed.html.twig Vedi File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Konto aktiviert – spawntree Timetracker</title>
<title>{{ 'app.confirmed.page_title'|trans }}</title>
{{ encore_entry_link_tags('app') }}
<meta http-equiv="refresh" content="4; url={{ redirectUrl }}">
</head>
@@ -14,17 +14,17 @@
<div class="register-card">
<div class="register-success">
<div class="register-success__icon">✓</div>
<h1 class="register-success__title">Konto aktiviert!</h1>
<h1 class="register-success__title">{{ 'app.confirmed.title'|trans }}</h1>
<p class="register-success__text">
Willkommen, <strong>{{ account.name }}</strong>.<br>
Du wirst gleich weitergeleitet …
{{ 'app.confirmed.welcome'|trans({'%name%': account.name}) }}<br>
{{ 'app.confirmed.redirect'|trans }}
</p>
<a href="{{ redirectUrl }}" class="btn btn-primary" style="margin-top: 1.5rem;">
Jetzt anmelden →
{{ 'app.confirmed.btn_login'|trans }}
</a>
</div>
</div>
</div>

</body>
</html>
</html>

+ 16
- 16
httpdocs/templates/registration/register.html.twig Vedi File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Registrieren – spawntree Timetracker</title>
<title>{{ 'app.register.page_title'|trans }}</title>
{{ encore_entry_link_tags('app') }}
</head>
<body class="register-body">
@@ -15,61 +15,61 @@
<div class="register-card__brand">
<a href="{{ path('app_home') }}">spawntree Timetracker</a>
</div>
<h1 class="register-card__title">Konto erstellen</h1>
<p class="register-card__sub">Kostenlos starten, keine Kreditkarte nötig.</p>
<h1 class="register-card__title">{{ 'app.register.title'|trans }}</h1>
<p class="register-card__sub">{{ 'app.register.subtitle'|trans }}</p>

<div id="register-errors" class="register-errors" role="alert"></div>

<form id="register-form" novalidate>

<fieldset class="register-fieldset">
<legend class="register-fieldset__legend">Dein Unternehmen</legend>
<legend class="register-fieldset__legend">{{ 'app.register.section_company'|trans }}</legend>

<div class="register-field">
<label class="register-field__label" for="companyName">Firmenname</label>
<label class="register-field__label" for="companyName">{{ 'app.register.label_company_name'|trans }}</label>
<input class="input"
type="text"
id="companyName"
name="companyName"
placeholder="Muster GmbH"
placeholder="{{ 'app.register.placeholder_company_name'|trans }}"
autocomplete="organization"
required />
<div class="register-field__hint">
Deine voraussichtliche URL: <strong id="slug-preview" class="register-field__slug">…</strong>
{{ 'app.register.url_preview_label'|trans }} <strong id="slug-preview" class="register-field__slug">…</strong>
</div>
</div>
</fieldset>

<fieldset class="register-fieldset">
<legend class="register-fieldset__legend">Dein Konto</legend>
<legend class="register-fieldset__legend">{{ 'app.register.section_account'|trans }}</legend>

<div class="register-field-row">
<div class="register-field">
<label class="register-field__label" for="firstName">Vorname</label>
<label class="register-field__label" for="firstName">{{ 'app.register.label_first_name'|trans }}</label>
<input class="input" type="text" id="firstName" name="firstName"
autocomplete="given-name" required />
</div>
<div class="register-field">
<label class="register-field__label" for="lastName">Nachname</label>
<label class="register-field__label" for="lastName">{{ 'app.register.label_last_name'|trans }}</label>
<input class="input" type="text" id="lastName" name="lastName"
autocomplete="family-name" required />
</div>
</div>

<div class="register-field">
<label class="register-field__label" for="email">E-Mail</label>
<label class="register-field__label" for="email">{{ 'app.register.label_email'|trans }}</label>
<input class="input" type="email" id="email" name="email"
autocomplete="email" required />
</div>

<div class="register-field-row">
<div class="register-field">
<label class="register-field__label" for="password">Passwort</label>
<label class="register-field__label" for="password">{{ 'app.register.label_password'|trans }}</label>
<input class="input" type="password" id="password" name="password"
autocomplete="new-password" minlength="8" required />
</div>
<div class="register-field">
<label class="register-field__label" for="passwordRepeat">Wiederholen</label>
<label class="register-field__label" for="passwordRepeat">{{ 'app.register.label_password_repeat'|trans }}</label>
<input class="input" type="password" id="passwordRepeat" name="passwordRepeat"
autocomplete="new-password" required />
</div>
@@ -78,10 +78,10 @@

<div class="register-actions">
<button type="submit" id="submit-btn" class="btn btn-primary register-actions__submit">
Konto erstellen
{{ 'app.register.btn_submit'|trans }}
</button>
<p class="register-actions__login">
Bereits registriert? <a href="{{ path('app_home') }}">Zur Anmeldung</a>
{{ 'app.register.already_registered'|trans }} <a href="{{ path('app_home') }}">{{ 'app.register.link_login'|trans }}</a>
</p>
</div>

@@ -93,4 +93,4 @@
{{ encore_entry_script_tags('registration') }}

</body>
</html>
</html>

+ 6
- 6
httpdocs/templates/security/login.html.twig Vedi File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Anmelden – spawntree</title>
<title>{{ 'app.login.page_title'|trans }}</title>
{{ encore_entry_link_tags('app') }}
</head>
<body class="login-body">
@@ -23,7 +23,7 @@

<div class="login-form__grid">

<label class="login-form__label" for="email">E-Mail</label>
<label class="login-form__label" for="email">{{ 'app.login.label_email'|trans }}</label>
<div class="login-form__field">
<input type="email"
id="email"
@@ -35,7 +35,7 @@
required />
</div>

<label class="login-form__label" for="password">Passwort</label>
<label class="login-form__label" for="password">{{ 'app.login.label_password'|trans }}</label>
<div class="login-form__field login-form__field--password">
<input type="password"
id="password"
@@ -50,16 +50,16 @@
<div class="login-form__remember">
<label class="login-form__remember-label">
<input type="checkbox" name="_remember_me" />
<span>Angemeldet bleiben</span>
<span>{{ 'app.login.remember_me'|trans }}</span>
</label>
</div>

<div class="login-form__actions">
<button type="submit" class="btn btn-primary login-form__submit">Anmelden</button>
<button type="submit" class="btn btn-primary login-form__submit">{{ 'app.login.btn_submit'|trans }}</button>
</div>

</form>
</div>

</body>
</html>
</html>

+ 78
- 78
httpdocs/templates/service/index.html.twig Vedi File

@@ -1,65 +1,65 @@
{# templates/service/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}Leistungen{% endblock %}
{% block title %}{{ 'app.service.page_title'|trans }}{% endblock %}

{% block body %}
<script>
window.CRUD = { apiBase: '/api/services' };
</script>
<script>
window.CRUD = { apiBase: '/api/services' };
</script>

<div class="crud-page">
<div class="crud-page">

<div class="crud-page__header">
<h1 class="crud-page__title">Leistungen</h1>
<button class="btn btn-primary" id="btn-new">Neue Leistung</button>
</div>
<div class="crud-page__header">
<h1 class="crud-page__title">{{ 'app.service.page_title'|trans }}</h1>
<button class="btn btn-primary" id="btn-new">{{ 'app.service.btn_new'|trans }}</button>
</div>

<div class="crud-create" id="crud-create">
<div class="entry-form__grid">
<div class="crud-create" id="crud-create">
<div class="entry-form__grid">

<label class="entry-form__label">Name</label>
<div class="entry-form__field">
<input type="text" id="create-name" class="input" placeholder="Leistungsname" />
</div>
<label class="entry-form__label">{{ 'app.crud.label_name'|trans }}</label>
<div class="entry-form__field">
<input type="text" id="create-name" class="input" placeholder="{{ 'app.service.placeholder_name'|trans }}" />
</div>

<label class="entry-form__label">Verrechenbar</label>
<div class="entry-form__field">
<label class="crud-checkbox-label">
<input type="checkbox" id="create-billable" checked />
<span>Ja, diese Leistung ist verrechenbar</span>
</label>
</div>
<label class="entry-form__label">{{ 'app.service.label_billable'|trans }}</label>
<div class="entry-form__field">
<label class="crud-checkbox-label">
<input type="checkbox" id="create-billable" checked />
<span>{{ 'app.service.billable_checkbox'|trans }}</span>
</label>
</div>

<label class="entry-form__label">Bemerkung</label>
<div class="entry-form__field">
<textarea id="create-note" class="textarea" rows="2"></textarea>
</div>
<label class="entry-form__label">{{ 'app.crud.label_note'|trans }}</label>
<div class="entry-form__field">
<textarea id="create-note" class="textarea" rows="2"></textarea>
</div>

<div class="entry-form__actions">
<button type="button" class="btn btn-primary" id="btn-create-save">Erstellen</button>
<button type="button" class="btn btn-secondary" id="btn-create-cancel">Abbrechen</button>
</div>
<div class="entry-form__actions">
<button type="button" class="btn btn-primary" id="btn-create-save">{{ 'app.entry.btn_create'|trans }}</button>
<button type="button" class="btn btn-secondary" id="btn-create-cancel">{{ 'app.entry.btn_cancel'|trans }}</button>
</div>

</div>
</div>
</div>

<div class="crud-tabs">
<button class="crud-tab crud-tab--active" data-tab="active">Aktiv</button>
<button class="crud-tab" data-tab="archived">Archiviert</button>
</div>
<div class="crud-tabs">
<button class="crud-tab crud-tab--active" data-tab="active">{{ 'app.crud.tab_active'|trans }}</button>
<button class="crud-tab" data-tab="archived">{{ 'app.crud.tab_archived'|trans }}</button>
</div>

<div class="crud-list" id="crud-list">
<div class="crud-list" id="crud-list">

{% set currentGroup = null %}
{% for service in services %}
{% set currentGroup = null %}
{% for service in services %}

{% set group = service.billable ? 'Verrechenbar' : 'Nicht-verrechenbar' %}
{% set group = service.billable ? 'app.service.billable'|trans : 'app.service.not_billable'|trans %}
{% if group != currentGroup %}
{% if currentGroup is not null %}</div>{% endif %}
<div class="crud-list__group">
<div class="crud-list__group-label">{{ group }}</div>
{% set currentGroup = group %}
{% if currentGroup is not null %}</div>{% endif %}
<div class="crud-list__group">
<div class="crud-list__group-label">{{ group }}</div>
{% set currentGroup = group %}
{% endif %}

<div class="crud-row{% if service.isArchived() %} crud-row--archived{% endif %}"
@@ -76,14 +76,14 @@ window.CRUD = { apiBase: '/api/services' };
</div>
<div class="crud-row__actions">
{% if service.isArchived() %}
<button class="crud-row__btn crud-row__btn--restore" data-action="unarchive" title="Wiederherstellen">
<button class="crud-row__btn crud-row__btn--restore" data-action="unarchive" title="{{ 'app.crud.btn_restore'|trans }}">
<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>
</button>
{% else %}
<button class="crud-row__btn crud-row__btn--edit" data-action="edit" title="Bearbeiten">
<button class="crud-row__btn crud-row__btn--edit" data-action="edit" title="{{ 'app.entry.btn_edit'|trans }}">
<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>
</button>
<button class="crud-row__btn crud-row__btn--delete" data-action="delete" title="Löschen">
<button class="crud-row__btn crud-row__btn--delete" data-action="delete" title="{{ 'app.entry.btn_delete'|trans }}">
<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>
</button>
{% endif %}
@@ -91,47 +91,47 @@ window.CRUD = { apiBase: '/api/services' };
</div>

{% if not service.isArchived() %}
<div class="crud-row__edit" hidden>
<div class="entry-form__grid entry-form__grid--inline">

<label class="entry-form__label">Name</label>
<div class="entry-form__field">
<input type="text" class="input edit-name" value="{{ service.name }}" />
</div>

<label class="entry-form__label">Verrechenbar</label>
<div class="entry-form__field">
<label class="crud-checkbox-label">
<input type="checkbox" class="edit-billable" {{ service.billable ? 'checked' : '' }} />
<span>Ja, diese Leistung ist verrechenbar</span>
</label>
</div>

<label class="entry-form__label">Bemerkung</label>
<div class="entry-form__field">
<textarea class="textarea edit-note" rows="2">{{ service.note|default('') }}</textarea>
</div>
<div class="crud-row__edit" hidden>
<div class="entry-form__grid entry-form__grid--inline">

<label class="entry-form__label">{{ 'app.crud.label_name'|trans }}</label>
<div class="entry-form__field">
<input type="text" class="input edit-name" value="{{ service.name }}" />
</div>

<label class="entry-form__label">{{ 'app.service.label_billable'|trans }}</label>
<div class="entry-form__field">
<label class="crud-checkbox-label">
<input type="checkbox" class="edit-billable" {{ service.billable ? 'checked' : '' }} />
<span>{{ 'app.service.billable_checkbox'|trans }}</span>
</label>
</div>

<label class="entry-form__label">{{ 'app.crud.label_note'|trans }}</label>
<div class="entry-form__field">
<textarea class="textarea edit-note" rows="2">{{ service.note|default('') }}</textarea>
</div>

<div class="entry-form__actions">
<button type="button" class="btn btn-primary" data-action="save">{{ 'app.entry.btn_save'|trans }}</button>
<button type="button" class="btn btn-secondary" data-action="cancel">{{ 'app.entry.btn_cancel'|trans }}</button>
</div>

<div class="entry-form__actions">
<button type="button" class="btn btn-primary" data-action="save">Sichern</button>
<button type="button" class="btn btn-secondary" data-action="cancel">Abbrechen</button>
</div>

</div>
</div>
{% endif %}

</div>

{% else %}
<div class="empty-state">
<p class="empty-state__title">Noch keine Leistungen angelegt.</p>
</div>
{% endfor %}
{% if currentGroup is not null %}</div>{% endif %}
{% else %}
<div class="empty-state">
<p class="empty-state__title">{{ 'app.service.empty'|trans }}</p>
</div>
{% endfor %}
{% if currentGroup is not null %}</div>{% endif %}

</div>
</div>
</div>

{% endblock %}



+ 133
- 30
httpdocs/translations/messages.de.yaml Vedi File

@@ -2,42 +2,145 @@

app:
date:
today: "Heute"
tomorrow: "Morgen"
yesterday: "Gestern"
week_label: "Kalenderwoche"
today: "Heute"
tomorrow: "Morgen"
yesterday: "Gestern"
week_label: "Kalenderwoche"

nav:
prev_week: "Vorherige Woche"
next_week: "Nächste Woche"
month_view: "Monatsansicht öffnen/schließen"
prev_month: "Vorheriger Monat"
next_month: "Nächster Monat"
close_month: "Monatsansicht schließen"
prev_week: "Vorherige Woche"
next_week: "Nächste Woche"
month_view: "Monatsansicht öffnen/schließen"
prev_month: "Vorheriger Monat"
next_month: "Nächster Monat"
close_month: "Monatsansicht schließen"
time_tracking: "Zeit erfassen"
reports: "Reports"
clients: "Kunden"
projects: "Projekte"
services: "Leistungen"
team: "Team"
account: "Account"
logout: "Abmelden"

entry:
label_duration: "Dauer"
label_duration: "Dauer"
label_project_service: "Projekt / Leistung"
label_note: "Bemerkung"
placeholder_note: "Optionale Beschreibung …"
label_note: "Bemerkung"
placeholder_note: "Optionale Beschreibung …"
placeholder_duration_hint: "Format: 1:30 oder 1.5"
btn_create: "Erstellen"
btn_save: "Sichern"
btn_cancel: "Abbrechen"
btn_edit: "Bearbeiten"
btn_delete: "Löschen"
no_entries: "Noch keine Zeiteinträge für diesen Tag"
select_placeholder: "..."
confirm_delete: "Eintrag wirklich löschen?"
error_no_project: "Bitte ein Projekt wählen."
error_save: "Fehler beim Speichern des Eintrags."
error_delete: "Fehler beim Löschen."
error_load: "Fehler beim Laden der Einträge."
duration_hint: "1:30 für 1 Std 30 Min · 8 12 für 8 bis 12 Uhr · 1,75 für 1 Std 45 Min · 0:00 zum Stoppen"
error_zero_duration: "Bitte eine Dauer größer als 0:00 eingeben."
btn_create: "Erstellen"
btn_save: "Sichern"
btn_cancel: "Abbrechen"
btn_edit: "Bearbeiten"
btn_delete: "Löschen"
no_entries: "Noch keine Zeiteinträge für diesen Tag"
select_placeholder: "..."
confirm_delete: "Eintrag wirklich löschen?"
error_no_project: "Bitte ein Projekt wählen."
error_save: "Fehler beim Speichern des Eintrags."
error_delete: "Fehler beim Löschen."
error_load: "Fehler beim Laden der Einträge."
duration_hint: "1:30 für 1 Std 30 Min · 8 12 für 8 bis 12 Uhr · 1,75 für 1 Std 45 Min · 0:00 zum Stoppen"
error_zero_duration: "Bitte eine Dauer größer als 0:00 eingeben."
error_duration_too_long: "Eine Dauer von mehr als 24 Stunden ist nicht möglich."
warn_duration_long: "Die Dauer ist länger als 8 Stunden. Wirklich speichern?"
warn_duration_long: "Die Dauer ist länger als 8 Stunden. Wirklich speichern?"

service:
billable: "Verrechenbar"
not_billable: "Nicht-verrechenbar"
billable: "Verrechenbar"
not_billable: "Nicht-verrechenbar"
page_title: "Leistungen"
btn_new: "Neue Leistung"
label_billable: "Verrechenbar"
billable_checkbox: "Ja, diese Leistung ist verrechenbar"
placeholder_name: "Leistungsname"
empty: "Noch keine Leistungen angelegt."

crud:
label_name: "Name"
label_note: "Bemerkung"
tab_active: "Aktiv"
tab_archived: "Archiviert"
btn_restore: "Wiederherstellen"

login:
page_title: "Anmelden – spawntree"
label_email: "E-Mail"
label_password: "Passwort"
remember_me: "Angemeldet bleiben"
btn_submit: "Anmelden"

register:
page_title: "Registrieren – spawntree Timetracker"
title: "Konto erstellen"
subtitle: "Kostenlos starten, keine Kreditkarte nötig."
section_company: "Dein Unternehmen"
label_company_name: "Firmenname"
placeholder_company_name: "Muster GmbH"
url_preview_label: "Deine voraussichtliche URL:"
section_account: "Dein Konto"
label_first_name: "Vorname"
label_last_name: "Nachname"
label_email: "E-Mail"
label_password: "Passwort"
label_password_repeat: "Wiederholen"
btn_submit: "Konto erstellen"
already_registered: "Bereits registriert?"
link_login: "Zur Anmeldung"

confirm_error:
page_title: "Fehler – spawntree Timetracker"
title: "Link ungültig"
btn_retry: "Erneut registrieren"

confirmed:
page_title: "Konto aktiviert – spawntree Timetracker"
title: "Konto aktiviert!"
welcome: "Willkommen, %name%."
redirect: "Du wirst gleich weitergeleitet …"
btn_login: "Jetzt anmelden →"

invite_error:
page_title: "Fehler – Einladungslink"
title: "Ungültiger Link"

set_password:
page_title: "Passwort festlegen – %name%"
subtitle: "Hallo %name%, lege dein Passwort fest, um loszulegen."
label_password: "Passwort"
label_password_repeat: "Wiederholen"
btn_submit: "Passwort speichern & anmelden"

email:
confirm:
greeting: "Hallo %name%,"
body: "bitte bestätige deine Registrierung für %company% mit einem Klick auf den Button."
btn: "E-Mail bestätigen"
expiry: "Der Link ist 24 Stunden gültig (bis %expires% Uhr)."
ignore: "Falls du dich nicht registriert hast, kannst du diese E-Mail ignorieren."

notify:
title: "Neue Registrierung im Timetracker"
col_company: "Firma"
col_slug: "Slug"
col_name: "Name"
col_email: "E-Mail"
col_date: "Datum"

welcome:
greeting: "Hallo %name%,"
body: "dein Konto für %company% ist jetzt aktiv. Los geht's!"
btn: "Zum Timetracker →"
url_label: "Deine URL:"

invite:
title: "Du wurdest zu %company% eingeladen"
greeting: "Hallo %name%,"
role_added_pre: "du wurdest als"
role_added_post: "zum Team von %company% hinzugefügt."
role_admin: "Administrator"
role_tracker: "Zeiterfasser"
role_member: "Standard-Nutzer"
cta: "Klicke auf den Button, um dein Passwort festzulegen und loszulegen. Der Link ist 7 Tage gültig."
btn: "Passwort festlegen →"
ignore: "Wenn du diese Einladung nicht erwartet hast, kannst du diese E-Mail ignorieren."

Caricamento…
Annulla
Salva