Quellcode durchsuchen

inverted filtering

master
FlorianEisenmenger vor 1 Woche
Ursprung
Commit
9a934715df
7 geänderte Dateien mit 337 neuen und 174 gelöschten Zeilen
  1. +40
    -0
      httpdocs/assets/scripts/report.js
  2. +55
    -3
      httpdocs/assets/styles/sections/_report.scss
  3. +7
    -0
      httpdocs/src/Controller/ReportController.php
  4. +31
    -11
      httpdocs/src/Repository/Tenant/TimeEntryRepository.php
  5. +203
    -154
      httpdocs/templates/report/_filter-panel.html.twig
  6. +0
    -6
      httpdocs/templates/report/times.html.twig
  7. +1
    -0
      httpdocs/translations/messages.de.yaml

+ 40
- 0
httpdocs/assets/scripts/report.js Datei anzeigen

@@ -253,11 +253,24 @@ class ReportFilter {
if (removeBtn) this.removeControl(removeBtn);
});

// Select-Änderung → Optionen in der Gruppe aktualisieren
this.panel.addEventListener('change', e => {
const sel = e.target.closest('.filter-select');
if (!sel) return;
const container = sel.closest('.filter-row__controls');
if (container) this.refreshGroupSelects(container);
});

// Initialer Zustand
this.panel.querySelectorAll('.filter-row').forEach(row => {
const cb = row.querySelector('.filter-row__checkbox');
this.syncRowState(row, cb?.checked ?? false);
});

// Bereits geladene Mehrfach-Selects deduplizieren (nach Seiten-Reload mit Filtern)
this.panel.querySelectorAll('.filter-row__controls').forEach(container => {
this.refreshGroupSelects(container);
});
}

// ── Panel toggeln ─────────────────────────────────────────────────────────
@@ -338,6 +351,9 @@ class ReportFilter {

container.appendChild(clone);

// Optionen deduplizieren
this.refreshGroupSelects(container);

// Row aktivieren
const row = btn.closest('.filter-row');
const cb = row?.querySelector('.filter-row__checkbox');
@@ -365,6 +381,30 @@ class ReportFilter {
this.syncRowState(row, false);
}
}

// Verbleibende Selects aktualisieren
if (container) this.refreshGroupSelects(container);
}

// ── Optionen in Mehrfach-Selects deduplizieren ────────────────────────────

refreshGroupSelects(container) {
const selects = [...container.querySelectorAll('.filter-select')];
if (selects.length < 2) return;

// Alle gewählten Values sammeln
const selectedValues = new Set(
selects.map(s => s.value).filter(v => v !== '')
);

selects.forEach(sel => {
const ownValue = sel.value;
sel.querySelectorAll('option').forEach(opt => {
if (!opt.value) return; // "..." immer sichtbar lassen
// Verstecken wenn woanders gewählt, aber nicht beim eigenen Select
opt.hidden = selectedValues.has(opt.value) && opt.value !== ownValue;
});
});
}

// ── Filter anwenden → URL bauen und navigieren ────────────────────────────


+ 55
- 3
httpdocs/assets/styles/sections/_report.scss Datei anzeigen

@@ -411,7 +411,7 @@ button.report-toolbar__action {
cursor: pointer;
font-family: $font-family-base;
padding: $space-1 $space-3;
margin: -$space-1 -$space-3;
margin: (-$space-1) (-$space-3);
border-radius: $radius-pill;
transition: background $transition-fast, color $transition-fast;

@@ -441,6 +441,7 @@ button.report-toolbar__action {
.report-filter__col {
flex: 1;
min-width: 0;
max-width: 66%;
}

.report-filter__heading {
@@ -455,7 +456,7 @@ button.report-toolbar__action {
// ─── Filter-Row ───────────────────────────────────────────────────────────────
.filter-row {
display: grid;
grid-template-columns: 160px 1fr 28px;
grid-template-columns: 160px 1fr;
align-items: flex-start;
gap: $space-3;
padding: $space-2 0;
@@ -481,6 +482,11 @@ button.report-toolbar__action {
.filter-radio {
opacity: 0.5;
}

.filter-neg {
opacity: 0.4;
pointer-events: none;
}
}
}

@@ -503,11 +509,19 @@ button.report-toolbar__action {
accent-color: $color-primary;
}

// ─── Body: Controls + Meta nebeneinander ────────────────────────────────────
.filter-row__body {
display: flex;
align-items: flex-start;
gap: $space-3;
}

.filter-row__controls {
display: flex;
flex-direction: column;
gap: $space-2;
min-width: 0;
flex: 1;
}

.filter-row__control-group {
@@ -533,11 +547,24 @@ button.report-toolbar__action {
}
}

// ─── Meta: Plus-Button + Negativfilter ──────────────────────────────────────
.filter-row__meta {
display: flex;
align-items: center;
gap: $space-3;
padding-top: 7px; // vertikal mit Select ausrichten
flex-shrink: 0;
white-space: nowrap;

&--no-add {
padding-left: calc(22px + #{$space-3}); // Platz für fehlenden Add-Button
}
}

// ─── Plus- und Minus-Button ───────────────────────────────────────────────────
.filter-row__add {
width: 22px;
height: 22px;
margin-top: 7px;
border: 1px solid $color-input-border;
background: $color-white;
border-radius: $radius-sm;
@@ -656,3 +683,28 @@ button.report-toolbar__action {
color: $color-text-base;
}
}

// ─── Negativfilter-Checkbox ───────────────────────────────────────────────────
.filter-neg {
display: inline-flex;
align-items: center;
gap: $space-1;
font-size: $font-size-sm;
color: $color-text-muted;
cursor: pointer;
user-select: none;
white-space: nowrap;

input[type="checkbox"] {
width: 13px;
height: 13px;
cursor: pointer;
accent-color: $color-warning;
flex-shrink: 0;
}

&:has(input:checked) {
color: $color-warning;
font-weight: $font-weight-medium;
}
}

+ 7
- 0
httpdocs/src/Controller/ReportController.php Datei anzeigen

@@ -169,6 +169,13 @@ class ReportController extends AbstractController
$filters['invoiced'] = $f['invoiced'] === 'yes';
}

// Negativfilter-Flags
if (!empty($f['clients_neg'])) $filters['clientsNeg'] = true;
if (!empty($f['projects_neg'])) $filters['projectsNeg'] = true;
if (!empty($f['services_neg'])) $filters['servicesNeg'] = true;
if (!empty($f['users_neg'])) $filters['usersNeg'] = true;
if (!empty($f['period_neg'])) $filters['periodNeg'] = true;

return $filters;
}



+ 31
- 11
httpdocs/src/Repository/Tenant/TimeEntryRepository.php Datei anzeigen

@@ -197,29 +197,49 @@ class TimeEntryRepository extends ServiceEntityRepository
->leftJoin('t.service', 's');

if (!empty($filters['clientIds'])) {
$qb->andWhere('c.id IN (:clientIds)')
$op = !empty($filters['clientsNeg']) ? 'NOT IN' : 'IN';
$qb->andWhere("c.id $op (:clientIds)")
->setParameter('clientIds', $filters['clientIds']);
}
if (!empty($filters['projectIds'])) {
$qb->andWhere('p.id IN (:projectIds)')
$op = !empty($filters['projectsNeg']) ? 'NOT IN' : 'IN';
$qb->andWhere("p.id $op (:projectIds)")
->setParameter('projectIds', $filters['projectIds']);
}
if (!empty($filters['serviceIds'])) {
$qb->andWhere('s.id IN (:serviceIds)')
$op = !empty($filters['servicesNeg']) ? 'NOT IN' : 'IN';
$qb->andWhere("s.id $op (:serviceIds)")
->setParameter('serviceIds', $filters['serviceIds']);
}
if (!empty($filters['userIds'])) {
$qb->andWhere('t.userId IN (:userIds)')
$op = !empty($filters['usersNeg']) ? 'NOT IN' : 'IN';
$qb->andWhere("t.userId $op (:userIds)")
->setParameter('userIds', $filters['userIds']);
}
if (!empty($filters['dateFrom'])) {
$qb->andWhere('t.date >= :dateFrom')
->setParameter('dateFrom', $filters['dateFrom']->format('Y-m-d'));
}
if (!empty($filters['dateTo'])) {
$qb->andWhere('t.date <= :dateTo')
->setParameter('dateTo', $filters['dateTo']->format('Y-m-d'));

// Zeitraum — positiv: BETWEEN, negativ: NOT BETWEEN (= < OR >)
$dateFrom = $filters['dateFrom'] ?? null;
$dateTo = $filters['dateTo'] ?? null;
$periodNeg = !empty($filters['periodNeg']);

if ($dateFrom !== null && $dateTo !== null) {
if ($periodNeg) {
$qb->andWhere('t.date < :dateFrom OR t.date > :dateTo');
} else {
$qb->andWhere('t.date BETWEEN :dateFrom AND :dateTo');
}
$qb->setParameter('dateFrom', $dateFrom->format('Y-m-d'))
->setParameter('dateTo', $dateTo->format('Y-m-d'));
} elseif ($dateFrom !== null) {
$op = $periodNeg ? '<' : '>=';
$qb->andWhere("t.date $op :dateFrom")
->setParameter('dateFrom', $dateFrom->format('Y-m-d'));
} elseif ($dateTo !== null) {
$op = $periodNeg ? '>' : '<=';
$qb->andWhere("t.date $op :dateTo")
->setParameter('dateTo', $dateTo->format('Y-m-d'));
}

if (!empty($filters['note'])) {
$qb->andWhere('t.note LIKE :note')
->setParameter('note', '%' . $filters['note'] . '%');


+ 203
- 154
httpdocs/templates/report/_filter-panel.html.twig Datei anzeigen

@@ -2,13 +2,13 @@
{# Variablen: clients, projects, services, userList, filterRaw, filterActive,
filterDateFrom, filterDateTo, isTracker, limit #}

{% set fr = filterRaw %}
{% set fr = filterRaw %}
{% set frPeriod = fr.period is defined ? fr.period : '' %}
{% set frInvoiced = fr.invoiced is defined ? fr.invoiced : '' %}
{% set months = deMonths() %}
{% set yearNow = 'now'|date('Y') %}
{% set dayNow = 'now'|date('j') %}
{% set monthNow = 'now'|date('n') %}
{% set months = deMonths() %}
{% set yearNow = 'now'|date('Y') %}
{% set dayNow = 'now'|date('j') %}
{% set monthNow = 'now'|date('n') %}

{% set hasClients = fr.clients is defined and fr.clients is not empty %}
{% set hasProjects = fr.projects is defined and fr.projects is not empty %}
@@ -19,6 +19,12 @@
{% set hasInvoiced = fr.invoiced is defined and fr.invoiced is not empty %}
{% set isCustom = hasPeriod and frPeriod == 'custom' %}

{% set clientsNeg = fr.clients_neg is defined and fr.clients_neg is not empty %}
{% set projectsNeg = fr.projects_neg is defined and fr.projects_neg is not empty %}
{% set servicesNeg = fr.services_neg is defined and fr.services_neg is not empty %}
{% set usersNeg = fr.users_neg is defined and fr.users_neg is not empty %}
{% set periodNeg = fr.period_neg is defined and fr.period_neg is not empty %}

{% set fromDay = filterDateFrom ? filterDateFrom|date('j') : dayNow %}
{% set fromMonth = filterDateFrom ? filterDateFrom|date('n') : monthNow %}
{% set fromYear = filterDateFrom ? filterDateFrom|date('Y') : yearNow %}
@@ -40,23 +46,31 @@
<input type="checkbox" class="filter-row__checkbox"{% if hasClients %} checked{% endif %}>
<span>{{ 'app.report.filter_client'|trans }}</span>
</label>
<div class="filter-row__controls" id="filter-controls-clients">
{% set selClients = hasClients ? fr.clients : [''] %}
{% for i, val in selClients %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="clients">
<option value="">...</option>
{% for client in clients %}
<option value="{{ client.id }}"{% if val == client.id %} selected{% endif %}>{{ client.name }}</option>
{% endfor %}
</select>
{% if i > 0 %}
<button type="button" class="filter-row__remove">×</button>
{% endif %}
</div>
{% endfor %}
<div class="filter-row__body">
<div class="filter-row__controls" id="filter-controls-clients">
{% set selClients = hasClients ? fr.clients : [''] %}
{% for i, val in selClients %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="clients">
<option value="">...</option>
{% for client in clients %}
<option value="{{ client.id }}"{% if val == client.id %} selected{% endif %}>{{ client.name }}</option>
{% endfor %}
</select>
{% if i > 0 %}
<button type="button" class="filter-row__remove">×</button>
{% endif %}
</div>
{% endfor %}
</div>
<div class="filter-row__meta">
<button type="button" class="filter-row__add" data-target="filter-controls-clients" data-filter-key="clients">+</button>
<label class="filter-neg">
<input type="checkbox" class="filter-neg-checkbox"{% if clientsNeg %} checked{% endif %}>
<span>{{ 'app.report.filter_neg'|trans }}</span>
</label>
</div>
</div>
<button type="button" class="filter-row__add" data-target="filter-controls-clients" data-filter-key="clients">+</button>
</div>

{# ── Projekt ──────────────────────────────────────────────────── #}
@@ -66,23 +80,31 @@
<input type="checkbox" class="filter-row__checkbox"{% if hasProjects %} checked{% endif %}>
<span>{{ 'app.report.filter_project'|trans }}</span>
</label>
<div class="filter-row__controls" id="filter-controls-projects">
{% set selProjects = hasProjects ? fr.projects : [''] %}
{% for i, val in selProjects %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="projects">
<option value="">...</option>
{% for project in projects %}
<option value="{{ project.id }}"{% if val == project.id %} selected{% endif %}>{{ project.name }} ({{ project.client.name }})</option>
{% endfor %}
</select>
{% if i > 0 %}
<button type="button" class="filter-row__remove">×</button>
{% endif %}
</div>
{% endfor %}
<div class="filter-row__body">
<div class="filter-row__controls" id="filter-controls-projects">
{% set selProjects = hasProjects ? fr.projects : [''] %}
{% for i, val in selProjects %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="projects">
<option value="">...</option>
{% for project in projects %}
<option value="{{ project.id }}"{% if val == project.id %} selected{% endif %}>{{ project.name }} ({{ project.client.name }})</option>
{% endfor %}
</select>
{% if i > 0 %}
<button type="button" class="filter-row__remove">×</button>
{% endif %}
</div>
{% endfor %}
</div>
<div class="filter-row__meta">
<button type="button" class="filter-row__add" data-target="filter-controls-projects" data-filter-key="projects">+</button>
<label class="filter-neg">
<input type="checkbox" class="filter-neg-checkbox"{% if projectsNeg %} checked{% endif %}>
<span>{{ 'app.report.filter_neg'|trans }}</span>
</label>
</div>
</div>
<button type="button" class="filter-row__add" data-target="filter-controls-projects" data-filter-key="projects">+</button>
</div>

{# ── Leistung ─────────────────────────────────────────────────── #}
@@ -92,65 +114,26 @@
<input type="checkbox" class="filter-row__checkbox"{% if hasServices %} checked{% endif %}>
<span>{{ 'app.report.filter_service'|trans }}</span>
</label>
<div class="filter-row__controls" id="filter-controls-services">
{% set selServices = hasServices ? fr.services : [''] %}
{% set activeServices = services|filter(s => not s.isArchived()) %}
{% set archivedServices = services|filter(s => s.isArchived()) %}
{% for i, val in selServices %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="services">
<option value="">...</option>
{% if activeServices|length > 0 %}
<optgroup label="Aktiv">
{% for service in activeServices %}
<option value="{{ service.id }}"{% if val == service.id %} selected{% endif %}>{{ service.name }}{% if not service.billable %} (nicht-verrechenbar){% endif %}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if archivedServices|length > 0 %}
<optgroup label="Archiviert">
{% for service in archivedServices %}
<option value="{{ service.id }}"{% if val == service.id %} selected{% endif %}>{{ service.name }}{% if not service.billable %} (nicht-verrechenbar){% endif %}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
{% if i > 0 %}
<button type="button" class="filter-row__remove">×</button>
{% endif %}
</div>
{% endfor %}
</div>
<button type="button" class="filter-row__add" data-target="filter-controls-services" data-filter-key="services">+</button>
</div>

{# ── Benutzer (nur für Admins / Members, nicht für Tracker) ───── #}
{% if not isTracker %}
<div class="filter-row{% if not hasUsers %} filter-row--inactive{% endif %}"
data-filter-key="users">
<label class="filter-row__label">
<input type="checkbox" class="filter-row__checkbox"{% if hasUsers %} checked{% endif %}>
<span>{{ 'app.report.filter_user'|trans }}</span>
</label>
<div class="filter-row__controls" id="filter-controls-users">
{% set selUsers = hasUsers ? fr.users : [''] %}
{% set activeUsers = userList|filter(u => not u.archived) %}
{% set archivedUsers = userList|filter(u => u.archived) %}
{% for i, val in selUsers %}
<div class="filter-row__body">
<div class="filter-row__controls" id="filter-controls-services">
{% set selServices = hasServices ? fr.services : [''] %}
{% set activeServices = services|filter(s => not s.isArchived()) %}
{% set archivedServices = services|filter(s => s.isArchived()) %}
{% for i, val in selServices %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="users">
<select class="select filter-select" data-filter-key="services">
<option value="">...</option>
{% if activeUsers|length > 0 %}
{% if activeServices|length > 0 %}
<optgroup label="Aktiv">
{% for user in activeUsers %}
<option value="{{ user.id }}"{% if val == user.id %} selected{% endif %}>{{ user.name }}</option>
{% for service in activeServices %}
<option value="{{ service.id }}"{% if val == service.id %} selected{% endif %}>{{ service.name }}{% if not service.billable %} (nicht-verrechenbar){% endif %}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if archivedUsers|length > 0 %}
{% if archivedServices|length > 0 %}
<optgroup label="Archiviert">
{% for user in archivedUsers %}
<option value="{{ user.id }}"{% if val == user.id %} selected{% endif %}>{{ user.name }}</option>
{% for service in archivedServices %}
<option value="{{ service.id }}"{% if val == service.id %} selected{% endif %}>{{ service.name }}{% if not service.billable %} (nicht-verrechenbar){% endif %}</option>
{% endfor %}
</optgroup>
{% endif %}
@@ -161,7 +144,62 @@
</div>
{% endfor %}
</div>
<button type="button" class="filter-row__add" data-target="filter-controls-users" data-filter-key="users">+</button>
<div class="filter-row__meta">
<button type="button" class="filter-row__add" data-target="filter-controls-services" data-filter-key="services">+</button>
<label class="filter-neg">
<input type="checkbox" class="filter-neg-checkbox"{% if servicesNeg %} checked{% endif %}>
<span>{{ 'app.report.filter_neg'|trans }}</span>
</label>
</div>
</div>
</div>

{# ── Benutzer (nur für Admins / Members, nicht für Tracker) ───── #}
{% if not isTracker %}
<div class="filter-row{% if not hasUsers %} filter-row--inactive{% endif %}"
data-filter-key="users">
<label class="filter-row__label">
<input type="checkbox" class="filter-row__checkbox"{% if hasUsers %} checked{% endif %}>
<span>{{ 'app.report.filter_user'|trans }}</span>
</label>
<div class="filter-row__body">
<div class="filter-row__controls" id="filter-controls-users">
{% set selUsers = hasUsers ? fr.users : [''] %}
{% set activeUsers = userList|filter(u => not u.archived) %}
{% set archivedUsers = userList|filter(u => u.archived) %}
{% for i, val in selUsers %}
<div class="filter-row__control-group">
<select class="select filter-select" data-filter-key="users">
<option value="">...</option>
{% if activeUsers|length > 0 %}
<optgroup label="Aktiv">
{% for user in activeUsers %}
<option value="{{ user.id }}"{% if val == user.id %} selected{% endif %}>{{ user.name }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if archivedUsers|length > 0 %}
<optgroup label="Archiviert">
{% for user in archivedUsers %}
<option value="{{ user.id }}"{% if val == user.id %} selected{% endif %}>{{ user.name }}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
{% if i > 0 %}
<button type="button" class="filter-row__remove">×</button>
{% endif %}
</div>
{% endfor %}
</div>
<div class="filter-row__meta">
<button type="button" class="filter-row__add" data-target="filter-controls-users" data-filter-key="users">+</button>
<label class="filter-neg">
<input type="checkbox" class="filter-neg-checkbox"{% if usersNeg %} checked{% endif %}>
<span>{{ 'app.report.filter_neg'|trans }}</span>
</label>
</div>
</div>
</div>
{% endif %}

@@ -172,60 +210,67 @@
<input type="checkbox" class="filter-row__checkbox"{% if hasPeriod %} checked{% endif %}>
<span>{{ 'app.report.filter_period'|trans }}</span>
</label>
<div class="filter-row__controls">
<div class="filter-row__control-group filter-row__control-group--period">
<select class="select filter-select filter-period-select" data-filter-key="period">
<option value="">...</option>
<option value="today" {% if frPeriod == 'today' %}selected{% endif %}>{{ 'app.report.period_today'|trans }}</option>
<option value="yesterday" {% if frPeriod == 'yesterday' %}selected{% endif %}>{{ 'app.report.period_yesterday'|trans }}</option>
<option value="current_week" {% if frPeriod == 'current_week' %}selected{% endif %}>{{ 'app.report.period_current_week'|trans }}</option>
<option value="prev_week" {% if frPeriod == 'prev_week' %}selected{% endif %}>{{ 'app.report.period_prev_week'|trans }}</option>
<option value="current_month" {% if frPeriod == 'current_month' %}selected{% endif %}>{{ 'app.report.period_current_month'|trans }}</option>
<option value="prev_month" {% if frPeriod == 'prev_month' %}selected{% endif %}>{{ 'app.report.period_prev_month'|trans }}</option>
<option value="current_year" {% if frPeriod == 'current_year' %}selected{% endif %}>{{ 'app.report.period_current_year'|trans }}</option>
<option value="prev_year" {% if frPeriod == 'prev_year' %}selected{% endif %}>{{ 'app.report.period_prev_year'|trans }}</option>
<option value="custom" {% if frPeriod == 'custom' %}selected{% endif %}>{{ 'app.report.period_custom'|trans }}</option>
</select>
<div class="filter-row__body">
<div class="filter-row__controls">
<div class="filter-row__control-group filter-row__control-group--period">
<select class="select filter-select filter-period-select" data-filter-key="period">
<option value="">...</option>
<option value="today" {% if frPeriod == 'today' %}selected{% endif %}>{{ 'app.report.period_today'|trans }}</option>
<option value="yesterday" {% if frPeriod == 'yesterday' %}selected{% endif %}>{{ 'app.report.period_yesterday'|trans }}</option>
<option value="current_week" {% if frPeriod == 'current_week' %}selected{% endif %}>{{ 'app.report.period_current_week'|trans }}</option>
<option value="prev_week" {% if frPeriod == 'prev_week' %}selected{% endif %}>{{ 'app.report.period_prev_week'|trans }}</option>
<option value="current_month" {% if frPeriod == 'current_month' %}selected{% endif %}>{{ 'app.report.period_current_month'|trans }}</option>
<option value="prev_month" {% if frPeriod == 'prev_month' %}selected{% endif %}>{{ 'app.report.period_prev_month'|trans }}</option>
<option value="current_year" {% if frPeriod == 'current_year' %}selected{% endif %}>{{ 'app.report.period_current_year'|trans }}</option>
<option value="prev_year" {% if frPeriod == 'prev_year' %}selected{% endif %}>{{ 'app.report.period_prev_year'|trans }}</option>
<option value="custom" {% if frPeriod == 'custom' %}selected{% endif %}>{{ 'app.report.period_custom'|trans }}</option>
</select>

<div class="filter-custom-dates"{% if not isCustom %} hidden{% endif %}>
<div class="filter-date-group">
<span class="filter-date-label">{{ 'app.report.period_from'|trans }}</span>
<select class="select filter-date-select filter-date-select--sm" data-date-field="from-day">
{% for d in 1..31 %}
<option value="{{ d }}"{% if fromDay == d %} selected{% endif %}>{{ d }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--month" data-date-field="from-month">
{% for i in 0..11 %}
<option value="{{ i + 1 }}"{% if fromMonth == (i + 1) %} selected{% endif %}>{{ months[i] }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--sm" data-date-field="from-year">
{% for y in range(yearNow - 5, yearNow) %}
<option value="{{ y }}"{% if fromYear == y %} selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
</div>
<div class="filter-date-group">
<span class="filter-date-label">{{ 'app.report.period_to'|trans }}</span>
<select class="select filter-date-select filter-date-select--sm" data-date-field="to-day">
{% for d in 1..31 %}
<option value="{{ d }}"{% if toDay == d %} selected{% endif %}>{{ d }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--month" data-date-field="to-month">
{% for i in 0..11 %}
<option value="{{ i + 1 }}"{% if toMonth == (i + 1) %} selected{% endif %}>{{ months[i] }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--sm" data-date-field="to-year">
{% for y in range(yearNow - 5, yearNow) %}
<option value="{{ y }}"{% if toYear == y %} selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
<div class="filter-custom-dates"{% if not isCustom %} hidden{% endif %}>
<div class="filter-date-group">
<span class="filter-date-label">{{ 'app.report.period_from'|trans }}</span>
<select class="select filter-date-select filter-date-select--sm" data-date-field="from-day">
{% for d in 1..31 %}
<option value="{{ d }}"{% if fromDay == d %} selected{% endif %}>{{ d }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--month" data-date-field="from-month">
{% for i in 0..11 %}
<option value="{{ i + 1 }}"{% if fromMonth == (i + 1) %} selected{% endif %}>{{ months[i] }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--sm" data-date-field="from-year">
{% for y in range(yearNow - 5, yearNow) %}
<option value="{{ y }}"{% if fromYear == y %} selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
</div>
<div class="filter-date-group">
<span class="filter-date-label">{{ 'app.report.period_to'|trans }}</span>
<select class="select filter-date-select filter-date-select--sm" data-date-field="to-day">
{% for d in 1..31 %}
<option value="{{ d }}"{% if toDay == d %} selected{% endif %}>{{ d }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--month" data-date-field="to-month">
{% for i in 0..11 %}
<option value="{{ i + 1 }}"{% if toMonth == (i + 1) %} selected{% endif %}>{{ months[i] }}</option>
{% endfor %}
</select>
<select class="select filter-date-select filter-date-select--sm" data-date-field="to-year">
{% for y in range(yearNow - 5, yearNow) %}
<option value="{{ y }}"{% if toYear == y %} selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
</div>
</div>
</div>

</div>
<div class="filter-row__meta filter-row__meta--no-add">
<label class="filter-neg">
<input type="checkbox" class="filter-neg-checkbox"{% if periodNeg %} checked{% endif %}>
<span>{{ 'app.report.filter_neg'|trans }}</span>
</label>
</div>
</div>
</div>
@@ -237,9 +282,11 @@
<input type="checkbox" class="filter-row__checkbox"{% if hasNote %} checked{% endif %}>
<span>{{ 'app.report.filter_note'|trans }}</span>
</label>
<div class="filter-row__controls">
<div class="filter-row__control-group">
<input type="text" class="input filter-note-input" value="{{ fr.note ?? '' }}" placeholder="...">
<div class="filter-row__body">
<div class="filter-row__controls">
<div class="filter-row__control-group">
<input type="text" class="input filter-note-input" value="{{ fr.note ?? '' }}" placeholder="...">
</div>
</div>
</div>
</div>
@@ -251,16 +298,18 @@
<input type="checkbox" class="filter-row__checkbox"{% if hasInvoiced %} checked{% endif %}>
<span>{{ 'app.report.filter_invoiced'|trans }}</span>
</label>
<div class="filter-row__controls">
<div class="filter-row__control-group filter-row__control-group--radio">
<label class="filter-radio">
<input type="radio" class="filter-invoiced-radio" name="filter-invoiced-radio" value="yes"{% if frInvoiced != 'no' %} checked{% endif %}>
{{ 'app.report.invoiced_yes'|trans }}
</label>
<label class="filter-radio">
<input type="radio" class="filter-invoiced-radio" name="filter-invoiced-radio" value="no"{% if frInvoiced == 'no' %} checked{% endif %}>
{{ 'app.report.invoiced_no'|trans }}
</label>
<div class="filter-row__body">
<div class="filter-row__controls">
<div class="filter-row__control-group filter-row__control-group--radio">
<label class="filter-radio">
<input type="radio" class="filter-invoiced-radio" name="filter-invoiced-radio" value="yes"{% if frInvoiced != 'no' %} checked{% endif %}>
{{ 'app.report.invoiced_yes'|trans }}
</label>
<label class="filter-radio">
<input type="radio" class="filter-invoiced-radio" name="filter-invoiced-radio" value="no"{% if frInvoiced == 'no' %} checked{% endif %}>
{{ 'app.report.invoiced_no'|trans }}
</label>
</div>
</div>
</div>
</div>


+ 0
- 6
httpdocs/templates/report/times.html.twig Datei anzeigen

@@ -102,12 +102,6 @@
</svg>
{{ 'app.report.toolbar_filter'|trans }}
</button>
<span class="report-toolbar__action report-toolbar__action--disabled">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 2l3 3L5 14H2v-3L11 2z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ 'app.report.toolbar_edit'|trans }}
</span>
</div>
</div>



+ 1
- 0
httpdocs/translations/messages.de.yaml Datei anzeigen

@@ -108,6 +108,7 @@ app:
period_to: "bis"
invoiced_yes: "Ja"
invoiced_no: "Nein"
filter_neg: "Negativfilter"

forgot_password:
page_title: "Passwort vergessen – spawntree"


Laden…
Abbrechen
Speichern