chore: initialize public repository
CI / checks (push) Has been cancelled

This commit is contained in:
maddin
2026-03-22 12:55:55 +00:00
commit 9794362f39
143 changed files with 19832 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
{% macro badge(text, tone='default', class_name='') -%}
<span class="badge badge--{{ tone }} {{ class_name }}">{{ text }}</span>
{%- endmacro %}
+9
View File
@@ -0,0 +1,9 @@
{% macro button(label, type='button', variant='primary', extra_class='', icon=None) -%}
<button type="{{ type }}" class="btn btn--{{ variant }} {{ extra_class }}">
{% if icon %}<span class="btn__icon" aria-hidden="true">{{ icon }}</span>{% endif %}
<span>{{ label }}</span>
</button>
{%- endmacro %}
{% macro link_button(label, href, variant='primary', extra_class='') -%}
<a href="{{ href }}" class="btn btn--{{ variant }} {{ extra_class }}">{{ label }}</a>
{%- endmacro %}
+5
View File
@@ -0,0 +1,5 @@
{% macro card(class_name='') -%}
<section class="card {{ class_name }}">
{{ caller() }}
</section>
{%- endmacro %}
+3
View File
@@ -0,0 +1,3 @@
{% macro chip(text, kind='default', extra_class='') -%}
<span class="ui-chip ui-chip--{{ kind }} {{ extra_class }}">{{ text }}</span>
{%- endmacro %}
+12
View File
@@ -0,0 +1,12 @@
{% macro collapsible_section(title, section_id, classes='', sync_group='') -%}
<details id="{{ section_id }}"
class="settings-section settings-section--collapsible form-card full-width {{ classes }}"
data-component="settings-section"
{% if sync_group %}data-sync-group="{{ sync_group }}"{% endif %}>
<summary class="settings-section__summary">
<span class="settings-section__heading">{{ title }}</span>
<span class="settings-section__chevron" aria-hidden="true"></span>
</summary>
<div class="settings-section__content">{{ caller() }}</div>
</details>
{%- endmacro %}
+127
View File
@@ -0,0 +1,127 @@
{% from "ui/chip.html" import chip %}
{% macro status_badge_form(csrf_token, day, return_to, kind) -%}
{% if kind == 'vacation' %}
<form method="post"
action="/vacation/day/toggle"
class="inline-form"
data-async-refresh="view">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="date" value="{{ day.date.isoformat() }}" />
<input type="hidden" name="return_to" value="{{ return_to }}" />
<button type="submit" class="day-status-badge is-vacation" title="Urlaub entfernen" aria-label="Urlaub entfernen">U</button>
</form>
{% elif kind == 'holiday' %}
<form method="post"
action="/special-day/toggle"
class="inline-form"
data-async-refresh="view">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="date" value="{{ day.date.isoformat() }}" />
<input type="hidden" name="status" value="holiday" />
<input type="hidden" name="return_to" value="{{ return_to }}" />
<button type="submit" class="day-status-badge is-holiday" title="Feiertag entfernen" aria-label="Feiertag entfernen">F</button>
</form>
{% elif kind == 'sick' %}
<form method="post"
action="/special-day/toggle"
class="inline-form"
data-async-refresh="view">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="date" value="{{ day.date.isoformat() }}" />
<input type="hidden" name="status" value="sick" />
<input type="hidden" name="return_to" value="{{ return_to }}" />
<button type="submit" class="day-status-badge is-sick" title="Krankheitstag entfernen" aria-label="Krankheitstag entfernen">K</button>
</form>
{% elif kind == 'overtime' %}
<form method="post" action="/overtime-adjustment/clear" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="date" value="{{ day.date.isoformat() }}" />
<input type="hidden" name="return_to" value="{{ return_to }}" />
<button type="submit" class="day-status-badge is-overtime" title="Stundenausgleich entfernen" aria-label="Stundenausgleich entfernen">S</button>
</form>
{% endif %}
{%- endmacro %}
{% macro add_menu_status_form(action_url, csrf_token, day, return_to, label, status='') -%}
<form method="post"
action="{{ action_url }}"
class="inline-form"
data-async-refresh="view">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="date" value="{{ day.date.isoformat() }}" />
<input type="hidden" name="return_to" value="{{ return_to }}" />
{% if status %}<input type="hidden" name="status" value="{{ status }}" />{% endif %}
<button type="submit" class="day-row__add-menu-item">{{ label }}</button>
</form>
{%- endmacro %}
{% macro day_row(day, csrf_token, date_label, return_to='/dashboard', mode='week') -%}
{% set is_vacation = day.is_vacation if day.is_vacation is defined else false %}
{% set special_status = day.special_status if day.special_status is defined else None %}
{% set overtime_adjustment_minutes = day.overtime_adjustment_minutes if day.overtime_adjustment_minutes is defined else 0 %}
{% set has_status = is_vacation or special_status in ['holiday', 'sick'] or overtime_adjustment_minutes %}
{% set is_weekend = day.is_weekend if day.is_weekend is defined else day.date.weekday() >= 5 %}
{% set is_today = today_date is defined and day.date == today_date %}
{% set status_edit_url = None %}
{% if overtime_adjustment_minutes %}
{% set status_edit_url = '/overtime-adjustment/edit?date=' ~ day.date.isoformat() %}
{% elif is_vacation %}
{% set status_edit_url = '/day-status/edit?date=' ~ day.date.isoformat() ~ '&status=vacation' %}
{% elif special_status == 'holiday' %}
{% set status_edit_url = '/day-status/edit?date=' ~ day.date.isoformat() ~ '&status=holiday' %}
{% elif special_status == 'sick' %}
{% set status_edit_url = '/day-status/edit?date=' ~ day.date.isoformat() ~ '&status=sick' %}
{% endif %}
<article class="day-row day-row--{{ mode }} {% if is_weekend %}day-row--weekend{% endif %} {% if is_today %}day-row--today{% endif %} {% if has_status %}day-row--has-status{% endif %}">
<div class="day-row__label" title="{{ date_label }}">{{ date_label }}</div>
<div class="day-row__cells {% if not day.entry %}day-row__cells--empty{% endif %}">
{% if day.entry %}
{{ chip(minutes_to_hhmm(day.entry.start_minutes) ~ ' → ' ~ minutes_to_hhmm(day.entry.end_minutes), 'time') }}
{{ chip('Pause: ' ~ day.entry.break_minutes ~ ' min', 'break') }}
{{ chip('Netto: ' ~ minutes_to_hhmm(day.net_minutes), 'net') }}
{% else %}
{{ chip('Keinen Eintrag', 'empty') }}
{% endif %}
</div>
<div class="day-row__actions">
{% if is_vacation %}{{ status_badge_form(csrf_token, day, return_to, 'vacation') }}{% endif %}
{% if special_status == 'holiday' %}{{ status_badge_form(csrf_token, day, return_to, 'holiday') }}{% endif %}
{% if special_status == 'sick' %}{{ status_badge_form(csrf_token, day, return_to, 'sick') }}{% endif %}
{% if overtime_adjustment_minutes %}{{ status_badge_form(csrf_token, day, return_to, 'overtime') }}{% endif %}
{% if day.entry %}
<a class="icon-button" href="/entry/{{ day.entry.id }}/edit" title="Eintrag bearbeiten" aria-label="Eintrag bearbeiten">
<img class="dash-icon" src="/static/icons/edit.svg" alt="" />
</a>
<form method="post" action="/entry/{{ day.entry.id }}/delete" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<button type="submit" class="icon-button" title="Eintrag löschen" aria-label="Eintrag löschen">
<img class="dash-icon" src="/static/icons/delete.svg" alt="" />
</button>
</form>
{% else %}
{% if has_status and status_edit_url %}
<a class="icon-button" href="{{ status_edit_url }}" title="Tag bearbeiten" aria-label="Tag bearbeiten">
<img class="dash-icon" src="/static/icons/edit.svg" alt="" />
</a>
{% endif %}
<details class="day-row__add-menu">
<summary class="icon-button" title="Optionen hinzufügen" aria-label="Optionen hinzufügen">
<img class="dash-icon" src="/static/icons/add.svg" alt="" />
</summary>
<div class="day-row__add-menu-panel">
<a class="day-row__add-menu-item" href="/entry/new?date={{ day.date.isoformat() }}">Zeit</a>
{{ add_menu_status_form('/vacation/day/toggle', csrf_token, day, return_to, 'Urlaub (U)') }}
{{ add_menu_status_form('/special-day/toggle', csrf_token, day, return_to, 'Feiertag (F)', 'holiday') }}
{{ add_menu_status_form('/special-day/toggle', csrf_token, day, return_to, 'Krankheit (K)', 'sick') }}
<a class="day-row__add-menu-item" href="/overtime-adjustment/edit?date={{ day.date.isoformat() }}">Stundenausgleich (S)</a>
</div>
</details>
{% endif %}
</div>
</article>
{%- endmacro %}
+6
View File
@@ -0,0 +1,6 @@
{% macro empty_state(title, text='') -%}
<section class="empty-state">
<h2 class="empty-state__title">{{ title }}</h2>
{% if text %}<p class="empty-state__text muted">{{ text }}</p>{% endif %}
</section>
{%- endmacro %}
+12
View File
@@ -0,0 +1,12 @@
{% macro alert(message, level='info') -%}
<div class="alert alert--{{ level }}"
role="status"
aria-live="polite"
data-component="flash">
<span class="alert__message">{{ message }}</span>
<button class="alert__close"
type="button"
aria-label="Hinweis schließen"
data-action="flash-close">×</button>
</div>
{%- endmacro %}
+37
View File
@@ -0,0 +1,37 @@
{% macro input_field(label, name, type='text', value='', required=false, placeholder='', autocomplete='', extra_class='', attrs='') -%}
<label class="form-field {{ extra_class }}">
<span class="form-field__label">{{ label }}</span>
<input class="input"
type="{{ type }}"
name="{{ name }}"
value="{{ value }}"
{% if required %}required{% endif %}
{% if placeholder %}placeholder="{{ placeholder }}"{% endif %}
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
{% if attrs %}{{ attrs|safe }}{% endif %} />
</label>
{%- endmacro %}
{% macro textarea_field(label, name, value='', rows=3, placeholder='', extra_class='', attrs='') -%}
<label class="form-field {{ extra_class }}">
<span class="form-field__label">{{ label }}</span>
<textarea class="input"
name="{{ name }}"
rows="{{ rows }}"
{% if placeholder %} placeholder="{{ placeholder }}"{% endif %}
{% if attrs %} {{ attrs|safe }}{% endif %}>{{ value }}</textarea>
</label>
{%- endmacro %}
{% macro select_field(label, name, options, selected='', required=false, extra_class='', attrs='') -%}
<label class="form-field {{ extra_class }}">
<span class="form-field__label">{{ label }}</span>
<select class="input"
name="{{ name }}"
{% if required %}required{% endif %}
{% if attrs %}{{ attrs|safe }}{% endif %}>
{% for option in options %}
<option value="{{ option.value }}"
{% if option.value == selected %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
</select>
</label>
{%- endmacro %}
+9
View File
@@ -0,0 +1,9 @@
{% macro help_section(title, subtitle='') -%}
<article class="help-section">
<header class="help-section__header">
<h2>{{ title }}</h2>
{% if subtitle %}<p class="muted">{{ subtitle }}</p>{% endif %}
</header>
<div class="help-section__body">{{ caller() }}</div>
</article>
{%- endmacro %}
+17
View File
@@ -0,0 +1,17 @@
{% macro icon_link(href, icon, label, title='', extra_class='') -%}
<a class="icon-button {{ extra_class }}"
href="{{ href }}"
title="{{ title if title else label }}"
aria-label="{{ label }}">
<img class="dash-icon" src="{{ icon }}" alt="" />
</a>
{%- endmacro %}
{% macro icon_submit(icon, label, title='', extra_class='') -%}
<button type="submit"
class="icon-button {{ extra_class }}"
title="{{ title if title else label }}"
aria-label="{{ label }}">
<img class="dash-icon" src="{{ icon }}" alt="" />
</button>
{%- endmacro %}
+18
View File
@@ -0,0 +1,18 @@
{% macro kpi_bar(items, extra_class='') -%}
<section class="kpi-bar {{ extra_class }}">
{% for item in items %}
<p class="kpi-bar__item {% if item.get('show_edit') %}kpi-bar__item--editable{% endif %}">
<span class="kpi-bar__label">{{ item.label }}:</span>
<strong class="kpi-bar__value {% if item.get('value_class') %}{{ item.get('value_class') }}{% endif %}">{{ item.value }}</strong>
{% if item.get('show_edit') %}
<button type="button"
class="kpi-bar__edit js-toggle-weekly-target-editor"
aria-label="Wochen-Soll bearbeiten"
title="Wochen-Soll bearbeiten">
<img class="dash-icon" src="/static/icons/edit.svg" alt="" />
</button>
{% endif %}
</p>
{% endfor %}
</section>
{%- endmacro %}
+18
View File
@@ -0,0 +1,18 @@
{% macro modal(id, title, close_label='Schließen') -%}
<div class="modal" id="{{ id }}" data-component="modal" hidden>
<div class="modal__backdrop" data-action="modal-close"></div>
<section class="modal__dialog"
role="dialog"
aria-modal="true"
aria-labelledby="{{ id }}-title">
<header class="modal__header">
<h2 id="{{ id }}-title">{{ title }}</h2>
<button class="modal__close"
type="button"
data-action="modal-close"
aria-label="{{ close_label }}">×</button>
</header>
<div class="modal__body">{{ caller() }}</div>
</section>
</div>
{%- endmacro %}
+5
View File
@@ -0,0 +1,5 @@
{% from "ui/week_group_header.html" import week_group_header %}
{% macro month_week_divider(week, csrf_token, return_to) -%}
{{ week_group_header(week, csrf_token, return_to) }}
{%- endmacro %}
+23
View File
@@ -0,0 +1,23 @@
{% macro month_header_bar(prev_url, next_url, title) -%}
<section class="period-header"
data-component="swipe-nav"
data-prev-url="{{ prev_url }}"
data-next-url="{{ next_url }}">
<div class="period-header__nav" data-component="period-nav">
<a class="icon-button"
href="{{ prev_url }}"
title="Vorheriger Monat"
aria-label="Vorheriger Monat">
<img class="dash-icon dash-icon-rotate-180" src="/static/icons/arrow.svg" alt="" />
</a>
<p class="period-header__title">{{ title }}</p>
<a class="icon-button"
href="{{ next_url }}"
title="Nächster Monat"
aria-label="Nächster Monat">
<img class="dash-icon" src="/static/icons/arrow.svg" alt="" />
</a>
</div>
<div class="period-header__actions">{{ caller() }}</div>
</section>
{%- endmacro %}
+6
View File
@@ -0,0 +1,6 @@
{% macro page_header(title, subtitle='') -%}
<header class="page-header">
<h1 class="page-header__title">{{ title }}</h1>
{% if subtitle %}<p class="page-header__subtitle muted">{{ subtitle }}</p>{% endif %}
</header>
{%- endmacro %}
+6
View File
@@ -0,0 +1,6 @@
{% macro pagination(prev_href='', next_href='', class_name='') -%}
<nav class="pagination {{ class_name }}" aria-label="Seitennavigation">
{% if prev_href %}<a class="btn btn--ghost" href="{{ prev_href }}">Zurück</a>{% endif %}
{% if next_href %}<a class="btn btn--ghost" href="{{ next_href }}">Weiter</a>{% endif %}
</nav>
{%- endmacro %}
+8
View File
@@ -0,0 +1,8 @@
{% macro segmented_toggle(items, aria_label='Umschalter', extra_class='') -%}
<nav class="segmented-toggle {{ extra_class }}" aria-label="{{ aria_label }}">
{% for item in items %}
<a href="{{ item.href }}"
class="segmented-toggle__item {% if item.active %}is-active{% endif %}">{{ item.label }}</a>
{% endfor %}
</nav>
{%- endmacro %}
+5
View File
@@ -0,0 +1,5 @@
{% from "ui/kpi_bar.html" import kpi_bar %}
{% macro status_strip(items, columns='4', extra_class='') -%}
{{ kpi_bar(items, extra_class) }}
{%- endmacro %}
+14
View File
@@ -0,0 +1,14 @@
{% macro table(headers, class_name='') -%}
<div class="table-wrap {{ class_name }}">
<table class="table">
<thead>
<tr>
{% for header in headers %}<th>{{ header }}</th>{% endfor %}
</tr>
</thead>
<tbody>
{{ caller() }}
</tbody>
</table>
</div>
{%- endmacro %}
+5
View File
@@ -0,0 +1,5 @@
{% from "ui/day_row.html" import day_row %}
{% macro time_day_row(day, csrf_token, date_label, return_to='/dashboard') -%}
{{ day_row(day, csrf_token, date_label, return_to, 'week') }}
{%- endmacro %}
+13
View File
@@ -0,0 +1,13 @@
{% macro workhours_target_warning_banner(warning) -%}
{% if warning and warning.at_risk %}
<section class="workhours-warning-banner"
data-component="workhours-warning"
data-workhours-warning="{{ warning.start_date.isoformat() }}-{{ warning.end_date.isoformat() }}-{{ warning.target_minutes }}">
<p class="workhours-warning-text">Achtung: Arbeitsstundenziel wird ggf. nicht erreicht</p>
<button type="button"
class="workhours-warning-close"
aria-label="Hinweis ausblenden"
data-action="warning-close">×</button>
</section>
{% endif %}
{%- endmacro %}
@@ -0,0 +1,5 @@
{% macro week_group_card_mobile(week, csrf_token, return_to) -%}
<section class="week-group-card-mobile {% if week.is_vacation_week %}is-vacation-week{% endif %}">
{{ caller() }}
</section>
{%- endmacro %}
+27
View File
@@ -0,0 +1,27 @@
{% macro week_group_header(week, csrf_token, return_to) -%}
<header class="week-group-header {% if week.is_vacation_week %}is-vacation-week{% endif %}">
<div class="week-group-header__left">
<strong>KW{{ week.iso_week }}</strong>
<span class="week-group-header__arrow">&rarr;</span>
<span>{{ week.week_start.strftime("%d.%m.") }} - {{ week.week_end.strftime("%d.%m.") }}</span>
</div>
<p class="week-group-header__meta">
Ist {{ minutes_to_hhmm(week.weekly_ist) }} | Soll {{ minutes_to_hhmm(week.weekly_soll) }} | Delta
<span class="{% if week.weekly_delta < 0 %}negative{% else %}positive{% endif %}">{{ minutes_to_hhmm(week.weekly_delta) }}</span>
| Urlaubstage {{ week.vacation_days }}
</p>
<form method="post"
action="/vacation/week/toggle"
class="inline-form week-group-header__action"
data-async-refresh="view">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="week_start" value="{{ week.week_start.isoformat() }}" />
<input type="hidden" name="week_end" value="{{ week.week_end.isoformat() }}" />
<input type="hidden" name="return_to" value="{{ return_to }}" />
<button type="submit"
class="week-vacation-button {% if week.is_vacation_week %}is-active{% endif %}"
title="Urlaub für ganze Woche umschalten"
aria-label="Urlaub für ganze Woche umschalten">Urlaub</button>
</form>
</header>
{%- endmacro %}
+23
View File
@@ -0,0 +1,23 @@
{% macro week_header_bar(prev_url, next_url, title) -%}
<section class="period-header"
data-component="swipe-nav"
data-prev-url="{{ prev_url }}"
data-next-url="{{ next_url }}">
<div class="period-header__nav" data-component="period-nav">
<a class="icon-button"
href="{{ prev_url }}"
title="Vorherige Woche"
aria-label="Vorherige Woche">
<img class="dash-icon dash-icon-rotate-180" src="/static/icons/arrow.svg" alt="" />
</a>
<p class="period-header__title">{{ title }}</p>
<a class="icon-button"
href="{{ next_url }}"
title="Nächste Woche"
aria-label="Nächste Woche">
<img class="dash-icon" src="/static/icons/arrow.svg" alt="" />
</a>
</div>
<div class="period-header__actions">{{ caller() }}</div>
</section>
{%- endmacro %}