| @@ -1,33 +1,54 @@ | |||||
| # INSTALLATION | |||||
| # spawntree Timetracker | |||||
| ## Installation | |||||
| ```bash | |||||
| cd httpdocs | 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 composer install | ||||
| ddev exec npm 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 | 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:diff --em=central --namespace=DoctrineMigrations | ||||
| ddev exec php bin/console doctrine:migrations:migrate --em=central --no-interaction | 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 | 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 | ddev exec npm run dev | ||||
| # Watch-Modus (bei Änderungen automatisch neu bauen) | # Watch-Modus (bei Änderungen automatisch neu bauen) | ||||
| @@ -35,3 +56,16 @@ ddev exec npm run watch | |||||
| # Production Build | # Production Build | ||||
| ddev exec npm run build | ddev exec npm run build | ||||
| ``` | |||||
| --- | |||||
| ## Hilfsbefehle | |||||
| ```bash | |||||
| # Alle URLs anzeigen | |||||
| ddev describe | |||||
| # Cache leeren | |||||
| ddev exec php bin/console cache:clear | |||||
| ``` | |||||
| @@ -5,37 +5,37 @@ | |||||
| <div class="main-nav__left"> | <div class="main-nav__left"> | ||||
| <a href="{{ path('timetracking_week') }}" | <a href="{{ path('timetracking_week') }}" | ||||
| class="main-nav__item{% if currentRoute starts with 'timetracking' %} main-nav__item--active{% endif %}"> | class="main-nav__item{% if currentRoute starts with 'timetracking' %} main-nav__item--active{% endif %}"> | ||||
| Zeit erfassen | |||||
| {{ 'app.nav.time_tracking'|trans }} | |||||
| </a> | </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> | ||||
| <div class="main-nav__right"> | <div class="main-nav__right"> | ||||
| {% if isCurrentUserMemberOrAdmin() %} | {% if isCurrentUserMemberOrAdmin() %} | ||||
| <a href="{{ path('client_index') }}" | <a href="{{ path('client_index') }}" | ||||
| class="main-nav__item{% if currentRoute starts with 'client' %} main-nav__item--active{% endif %}"> | class="main-nav__item{% if currentRoute starts with 'client' %} main-nav__item--active{% endif %}"> | ||||
| Kunden | |||||
| {{ 'app.nav.clients'|trans }} | |||||
| </a> | </a> | ||||
| <a href="{{ path('project_index') }}" | <a href="{{ path('project_index') }}" | ||||
| class="main-nav__item{% if currentRoute starts with 'project' %} main-nav__item--active{% endif %}"> | class="main-nav__item{% if currentRoute starts with 'project' %} main-nav__item--active{% endif %}"> | ||||
| Projekte | |||||
| {{ 'app.nav.projects'|trans }} | |||||
| </a> | </a> | ||||
| <a href="{{ path('service_index') }}" | <a href="{{ path('service_index') }}" | ||||
| class="main-nav__item{% if currentRoute starts with 'service' %} main-nav__item--active{% endif %}"> | class="main-nav__item{% if currentRoute starts with 'service' %} main-nav__item--active{% endif %}"> | ||||
| Leistungen | |||||
| {{ 'app.nav.services'|trans }} | |||||
| </a> | </a> | ||||
| {% endif %} | {% endif %} | ||||
| {% if isCurrentUserAdmin() %} | {% if isCurrentUserAdmin() %} | ||||
| <a href="{{ path('team_index') }}" | <a href="{{ path('team_index') }}" | ||||
| class="main-nav__item{% if currentRoute starts with 'team' %} main-nav__item--active{% endif %}"> | class="main-nav__item{% if currentRoute starts with 'team' %} main-nav__item--active{% endif %}"> | ||||
| Team | |||||
| {{ 'app.nav.team'|trans }} | |||||
| </a> | </a> | ||||
| {% endif %} | {% endif %} | ||||
| <a href="{{ path('account_index') }}" | <a href="{{ path('account_index') }}" | ||||
| class="main-nav__item{% if currentRoute starts with 'account' %} main-nav__item--active{% endif %}"> | class="main-nav__item{% if currentRoute starts with 'account' %} main-nav__item--active{% endif %}"> | ||||
| Account | |||||
| {{ 'app.nav.account'|trans }} | |||||
| </a> | </a> | ||||
| <a href="{{ path('app_logout') }}" class="main-nav__item"> | <a href="{{ path('app_logout') }}" class="main-nav__item"> | ||||
| Abmelden | |||||
| {{ 'app.nav.logout'|trans }} | |||||
| </a> | </a> | ||||
| </div> | </div> | ||||
| </nav> | </nav> | ||||
| @@ -8,23 +8,22 @@ | |||||
| <p style="font-size: 1.2rem; font-weight: 700; color: #1a2a3a; margin: 0 0 8px;">spawntree Timetracker</p> | <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;"> | <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;"> | <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> | </p> | ||||
| <div style="text-align: center; margin: 32px 0;"> | <div style="text-align: center; margin: 32px 0;"> | ||||
| <a href="{{ verifyUrl }}" | <a href="{{ verifyUrl }}" | ||||
| style="display: inline-block; background: #f0a500; color: #fff; font-weight: 700; | style="display: inline-block; background: #f0a500; color: #fff; font-weight: 700; | ||||
| text-decoration: none; padding: 14px 40px; border-radius: 100px; font-size: 1rem;"> | text-decoration: none; padding: 14px 40px; border-radius: 100px; font-size: 1rem;"> | ||||
| E-Mail bestätigen | |||||
| {{ 'app.email.confirm.btn'|trans }} | |||||
| </a> | </a> | ||||
| </div> | </div> | ||||
| <p style="color: #7a8a9a; font-size: 0.8rem; margin: 24px 0 0;"> | <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> | ||||
| <p style="color: #aab8c6; font-size: 0.75rem; margin: 8px 0 0; word-break: break-all;"> | <p style="color: #aab8c6; font-size: 0.75rem; margin: 8px 0 0; word-break: break-all;"> | ||||
| {{ verifyUrl }} | {{ verifyUrl }} | ||||
| @@ -32,4 +31,4 @@ | |||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -3,13 +3,13 @@ | |||||
| <html lang="de"> | <html lang="de"> | ||||
| <head><meta charset="UTF-8"></head> | <head><meta charset="UTF-8"></head> | ||||
| <body style="font-family: monospace; padding: 20px; color: #1a2a3a;"> | <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> | <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> | </table> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -8,23 +8,23 @@ | |||||
| <p style="font-size: 1.2rem; font-weight: 700; color: #1a2a3a; margin: 0 0 8px;">spawntree Timetracker</p> | <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;"> | <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;"> | <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> | </p> | ||||
| <div style="text-align: center; margin: 32px 0;"> | <div style="text-align: center; margin: 32px 0;"> | ||||
| <a href="{{ loginUrl }}" | <a href="{{ loginUrl }}" | ||||
| style="display: inline-block; background: #4a90d9; color: #fff; font-weight: 700; | style="display: inline-block; background: #4a90d9; color: #fff; font-weight: 700; | ||||
| text-decoration: none; padding: 14px 40px; border-radius: 100px; font-size: 1rem;"> | text-decoration: none; padding: 14px 40px; border-radius: 100px; font-size: 1rem;"> | ||||
| Zum Timetracker → | |||||
| {{ 'app.email.welcome.btn'|trans }} | |||||
| </a> | </a> | ||||
| </div> | </div> | ||||
| <p style="color: #7a8a9a; font-size: 0.8rem; margin: 24px 0 0;"> | <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> | </p> | ||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -6,27 +6,28 @@ | |||||
| <div style="max-width: 480px; margin: 0 auto; background: #ffffff; border-radius: 8px; padding: 32px;"> | <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;"> | <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> | ||||
| <p style="color: #6b7a8d; margin: 0 0 24px;"> | <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> | ||||
| <p style="margin: 0 0 24px;"> | <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> | </p> | ||||
| <a href="{{ inviteUrl }}" | <a href="{{ inviteUrl }}" | ||||
| style="display:inline-block; background:#e8820a; color:#ffffff; padding:12px 28px; | style="display:inline-block; background:#e8820a; color:#ffffff; padding:12px 28px; | ||||
| border-radius:24px; text-decoration:none; font-weight:bold;"> | border-radius:24px; text-decoration:none; font-weight:bold;"> | ||||
| Passwort festlegen → | |||||
| {{ 'app.email.invite.btn'|trans }} | |||||
| </a> | </a> | ||||
| <p style="margin: 24px 0 0; font-size: 12px; color: #9aa5b1;"> | <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> | </p> | ||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -4,13 +4,13 @@ | |||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <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') }} | {{ encore_entry_link_tags('app') }} | ||||
| </head> | </head> | ||||
| <body class="login-body"> | <body class="login-body"> | ||||
| <div class="login-card"> | <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 class="login-card__error">{{ error }}</div> | ||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -4,7 +4,7 @@ | |||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <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') }} | {{ encore_entry_link_tags('app') }} | ||||
| </head> | </head> | ||||
| <body class="login-body"> | <body class="login-body"> | ||||
| @@ -12,7 +12,7 @@ | |||||
| <div class="login-card"> | <div class="login-card"> | ||||
| <div class="login-card__title">{{ invite.account.name }}</div> | <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 %} | {% if error %} | ||||
| <div class="login-card__error">{{ error }}</div> | <div class="login-card__error">{{ error }}</div> | ||||
| @@ -22,7 +22,7 @@ | |||||
| <div class="login-form__grid"> | <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"> | <div class="login-form__field"> | ||||
| <input type="password" | <input type="password" | ||||
| id="password" | id="password" | ||||
| @@ -34,7 +34,7 @@ | |||||
| minlength="8" /> | minlength="8" /> | ||||
| </div> | </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"> | <div class="login-form__field"> | ||||
| <input type="password" | <input type="password" | ||||
| id="passwordRepeat" | id="passwordRepeat" | ||||
| @@ -47,7 +47,7 @@ | |||||
| </div> | </div> | ||||
| <div class="login-form__actions"> | <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> | </div> | ||||
| </form> | </form> | ||||
| @@ -55,4 +55,4 @@ | |||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -4,7 +4,7 @@ | |||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <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') }} | {{ encore_entry_link_tags('app') }} | ||||
| </head> | </head> | ||||
| <body class="register-body"> | <body class="register-body"> | ||||
| @@ -13,14 +13,14 @@ | |||||
| <div class="register-card"> | <div class="register-card"> | ||||
| <div class="register-success"> | <div class="register-success"> | ||||
| <div class="register-success__icon register-success__icon--error">✕</div> | <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> | <p class="register-success__text">{{ error }}</p> | ||||
| <a href="{{ path('app_register') }}" class="btn btn-primary" style="margin-top: 1.5rem;"> | <a href="{{ path('app_register') }}" class="btn btn-primary" style="margin-top: 1.5rem;"> | ||||
| Erneut registrieren | |||||
| {{ 'app.confirm_error.btn_retry'|trans }} | |||||
| </a> | </a> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -4,7 +4,7 @@ | |||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <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') }} | {{ encore_entry_link_tags('app') }} | ||||
| <meta http-equiv="refresh" content="4; url={{ redirectUrl }}"> | <meta http-equiv="refresh" content="4; url={{ redirectUrl }}"> | ||||
| </head> | </head> | ||||
| @@ -14,17 +14,17 @@ | |||||
| <div class="register-card"> | <div class="register-card"> | ||||
| <div class="register-success"> | <div class="register-success"> | ||||
| <div class="register-success__icon">✓</div> | <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"> | <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> | </p> | ||||
| <a href="{{ redirectUrl }}" class="btn btn-primary" style="margin-top: 1.5rem;"> | <a href="{{ redirectUrl }}" class="btn btn-primary" style="margin-top: 1.5rem;"> | ||||
| Jetzt anmelden → | |||||
| {{ 'app.confirmed.btn_login'|trans }} | |||||
| </a> | </a> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -4,7 +4,7 @@ | |||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <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') }} | {{ encore_entry_link_tags('app') }} | ||||
| </head> | </head> | ||||
| <body class="register-body"> | <body class="register-body"> | ||||
| @@ -15,61 +15,61 @@ | |||||
| <div class="register-card__brand"> | <div class="register-card__brand"> | ||||
| <a href="{{ path('app_home') }}">spawntree Timetracker</a> | <a href="{{ path('app_home') }}">spawntree Timetracker</a> | ||||
| </div> | </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> | <div id="register-errors" class="register-errors" role="alert"></div> | ||||
| <form id="register-form" novalidate> | <form id="register-form" novalidate> | ||||
| <fieldset class="register-fieldset"> | <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"> | <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" | <input class="input" | ||||
| type="text" | type="text" | ||||
| id="companyName" | id="companyName" | ||||
| name="companyName" | name="companyName" | ||||
| placeholder="Muster GmbH" | |||||
| placeholder="{{ 'app.register.placeholder_company_name'|trans }}" | |||||
| autocomplete="organization" | autocomplete="organization" | ||||
| required /> | required /> | ||||
| <div class="register-field__hint"> | <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> | ||||
| </div> | </div> | ||||
| </fieldset> | </fieldset> | ||||
| <fieldset class="register-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-row"> | ||||
| <div class="register-field"> | <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" | <input class="input" type="text" id="firstName" name="firstName" | ||||
| autocomplete="given-name" required /> | autocomplete="given-name" required /> | ||||
| </div> | </div> | ||||
| <div class="register-field"> | <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" | <input class="input" type="text" id="lastName" name="lastName" | ||||
| autocomplete="family-name" required /> | autocomplete="family-name" required /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class="register-field"> | <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" | <input class="input" type="email" id="email" name="email" | ||||
| autocomplete="email" required /> | autocomplete="email" required /> | ||||
| </div> | </div> | ||||
| <div class="register-field-row"> | <div class="register-field-row"> | ||||
| <div class="register-field"> | <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" | <input class="input" type="password" id="password" name="password" | ||||
| autocomplete="new-password" minlength="8" required /> | autocomplete="new-password" minlength="8" required /> | ||||
| </div> | </div> | ||||
| <div class="register-field"> | <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" | <input class="input" type="password" id="passwordRepeat" name="passwordRepeat" | ||||
| autocomplete="new-password" required /> | autocomplete="new-password" required /> | ||||
| </div> | </div> | ||||
| @@ -78,10 +78,10 @@ | |||||
| <div class="register-actions"> | <div class="register-actions"> | ||||
| <button type="submit" id="submit-btn" class="btn btn-primary register-actions__submit"> | <button type="submit" id="submit-btn" class="btn btn-primary register-actions__submit"> | ||||
| Konto erstellen | |||||
| {{ 'app.register.btn_submit'|trans }} | |||||
| </button> | </button> | ||||
| <p class="register-actions__login"> | <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> | </p> | ||||
| </div> | </div> | ||||
| @@ -93,4 +93,4 @@ | |||||
| {{ encore_entry_script_tags('registration') }} | {{ encore_entry_script_tags('registration') }} | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -4,7 +4,7 @@ | |||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <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') }} | {{ encore_entry_link_tags('app') }} | ||||
| </head> | </head> | ||||
| <body class="login-body"> | <body class="login-body"> | ||||
| @@ -23,7 +23,7 @@ | |||||
| <div class="login-form__grid"> | <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"> | <div class="login-form__field"> | ||||
| <input type="email" | <input type="email" | ||||
| id="email" | id="email" | ||||
| @@ -35,7 +35,7 @@ | |||||
| required /> | required /> | ||||
| </div> | </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"> | <div class="login-form__field login-form__field--password"> | ||||
| <input type="password" | <input type="password" | ||||
| id="password" | id="password" | ||||
| @@ -50,16 +50,16 @@ | |||||
| <div class="login-form__remember"> | <div class="login-form__remember"> | ||||
| <label class="login-form__remember-label"> | <label class="login-form__remember-label"> | ||||
| <input type="checkbox" name="_remember_me" /> | <input type="checkbox" name="_remember_me" /> | ||||
| <span>Angemeldet bleiben</span> | |||||
| <span>{{ 'app.login.remember_me'|trans }}</span> | |||||
| </label> | </label> | ||||
| </div> | </div> | ||||
| <div class="login-form__actions"> | <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> | </div> | ||||
| </form> | </form> | ||||
| </div> | </div> | ||||
| </body> | </body> | ||||
| </html> | |||||
| </html> | |||||
| @@ -1,65 +1,65 @@ | |||||
| {# templates/service/index.html.twig #} | {# templates/service/index.html.twig #} | ||||
| {% extends 'base.html.twig' %} | {% extends 'base.html.twig' %} | ||||
| {% block title %}Leistungen{% endblock %} | |||||
| {% block title %}{{ 'app.service.page_title'|trans }}{% endblock %} | |||||
| {% block body %} | {% 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> | |||||
| <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 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 %} | {% endif %} | ||||
| <div class="crud-row{% if service.isArchived() %} crud-row--archived{% endif %}" | <div class="crud-row{% if service.isArchived() %} crud-row--archived{% endif %}" | ||||
| @@ -76,14 +76,14 @@ window.CRUD = { apiBase: '/api/services' }; | |||||
| </div> | </div> | ||||
| <div class="crud-row__actions"> | <div class="crud-row__actions"> | ||||
| {% if service.isArchived() %} | {% 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> | <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> | </button> | ||||
| {% else %} | {% 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> | <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> | ||||
| <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> | <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> | </button> | ||||
| {% endif %} | {% endif %} | ||||
| @@ -91,47 +91,47 @@ window.CRUD = { apiBase: '/api/services' }; | |||||
| </div> | </div> | ||||
| {% if not service.isArchived() %} | {% 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> | </div> | ||||
| </div> | |||||
| {% endif %} | {% endif %} | ||||
| </div> | </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> | |||||
| </div> | |||||
| {% endblock %} | {% endblock %} | ||||
| @@ -2,42 +2,145 @@ | |||||
| app: | app: | ||||
| date: | date: | ||||
| today: "Heute" | |||||
| tomorrow: "Morgen" | |||||
| yesterday: "Gestern" | |||||
| week_label: "Kalenderwoche" | |||||
| today: "Heute" | |||||
| tomorrow: "Morgen" | |||||
| yesterday: "Gestern" | |||||
| week_label: "Kalenderwoche" | |||||
| nav: | 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: | entry: | ||||
| label_duration: "Dauer" | |||||
| label_duration: "Dauer" | |||||
| label_project_service: "Projekt / Leistung" | 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" | 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." | 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: | 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." | |||||