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

339 lines
12 KiB

<h1 class="mb-4">Meine Einträge</h1>
<!-- 🔢 Zusammenfassung -->
<div class="container my-4 rounded border shadow-sm py-3 px-4">
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
<div class="col rounded border shadow-sm p-3">
<h5>🚗 Fahrtkosten (Kilometergeld)</h5>
<% @total_kilometer_costs_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
</p>
<% end %>
</div>
<div class="col rounded border shadow-sm p-3">
<h5>💶 Fortbildungskosten</h5>
<% @fortbildungskosten_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
</p>
<% end %>
</div>
<div class="col rounded border shadow-sm p-3">
<h5>🧠 Selbsterfahrungskosten</h5>
<% @selbsterfahrungskosten_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
</p>
<% end %>
</div>
<div class="col rounded border shadow-sm p-3">
<h5>👨‍🏫 Supervision</h5>
<% @selbstsupervision_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
</p>
<% end %>
</div>
<div class="col rounded border shadow-sm p-3">
<h5>📊 Gesamtkosten</h5>
<% @allekosten_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
</p>
<% end %>
</div>
<div class="col rounded border shadow-sm p-3">
<h5>🕒 Gesamtzeit</h5>
<p><strong>Total: <%= @total_minutes / 60 %>h <%= @total_minutes % 60 %> min</strong></p>
<p>Propädeutikum: <%= @total_minutes_praktikum_prop / 60 %>h <%= @total_minutes_praktikum_prop % 60 %> min</p>
<p>Fachspezifikum: <%= @total_minutes_praktikum_fach / 60 %>h <%= @total_minutes_praktikum_fach % 60 %> min</p>
</div>
</div>
</div>
<!-- 📊 Übersicht je Kombination -->
<div class="mb-4 rounded border shadow-sm p-3">
<h4>📊 Übersicht je Kombination</h4>
<div class="table-responsive">
<table class="table table-bordered table-sm table-striped">
<thead class="table-light">
<tr>
<th>Typ</th>
<th>Art</th>
<th>Verbleibend</th>
<th>Soll (h)</th>
<th>Wöchentlich</th>
<th>Vorauss. Ende</th>
<th>Fortschritt</th>
</tr>
</thead>
<tbody>
<% ["propädeutikum", "fachspezifikum"].each do |typ| %>
<% ["Praktikum", "Selbsterfahrung", "Supervision"].each do |art| %>
<% remaining = @remaining_minutes_matrix.dig(typ, art) %>
<% soll = current_user.required_hours_matrix.dig(typ, art) %>
<% weekly = current_user.weekly_target_matrix.dig(typ, art) %>
<% ende = @estimated_end_by_typ_art.dig(typ, art) %>
<% if remaining.present? || soll.present? || weekly.present? %>
<tr>
<td><%= typ.capitalize %></td>
<td><%= art.capitalize %></td>
<td><%= remaining ? "#{remaining / 60} h #{remaining % 60} min" : "—" %></td>
<td><%= soll || "—" %> h</td>
<td><%= weekly || "—" %> h/Woche</td>
<td><%= ende.present? ? ende.strftime("%d.%m.%Y") : "—" %></td>
<td>
<% percent = @completion_percent_by_typ_art[[typ, art]] %>
<%= percent ? "#{percent} %" : "—" %>
</td>
</tr>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>
<div class="container my-4 rounded border shadow-sm p-3">
<h4 class="mb-4">⏱ Timer</h4>
<% if @running_entry.present? %>
<div class="alert alert-info d-flex justify-content-between align-items-center">
<div>
<strong>Laufender Eintrag:</strong>
<%= @running_entry.praktikums_typ %> – <%= @running_entry.entry_art %><br>
Gestartet: <%= l(@running_entry.start_time, format: :short) %><br>
Dauer: <span id="live-timer">Berechne …</span>
</div>
<div>
<%= form_with url: stop_timer_entry_path(@running_entry), method: :post, local: true do |f| %>
<div class="form-check mb-2">
<%= f.check_box :lunch_break, class: 'form-check-input', id: 'lunch_break' %>
<%= f.label :lunch_break, '30 Min Mittagspause abziehen', class: 'form-check-label' %>
</div>
<%= f.submit '⏹️ Stoppen', class: "btn btn-danger" %>
<% end %>
</div>
</div>
<% end %>
<div class="mb-4 row">
<%= form_with url: start_timer_entries_path, method: :post, local: true do %>
<div class="row g-2 align-items-end">
<div class="col-md-4">
<%= label_tag :typ, 'Typ' %>
<%= select_tag :typ,
options_for_select(
current_user.praepedeutikum_abgeschlossen? ?
Entry::PRAKTIKUMSTYPEN.reject { |typ| typ == 'propädeutikum' } :
Entry::PRAKTIKUMSTYPEN
),
class: "form-select" %>
</div>
<div class="col-md-4">
<%= label_tag :art, 'Art' %>
<%= select_tag :art, options_for_select(Entry::ENTRY_ARTEN), class: "form-select" %>
</div>
<div class="col-md-4">
<%= submit_tag '▶️ Start', class: "btn btn-success w-100", disabled: @running_entry.present? %>
</div>
</div>
<% end %>
</div>
</div>
<%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-outline-primary mt-3 w-100 w-md-auto mb-3' %>
<!-- 📋 Tabelle aller Einträge -->
<div class="mb-4 rounded border shadow-sm p-3">
<h4 class="mb-3">📋 Einträge</h4>
<div class="mb-3">
<input type="text" id="entryFilter" class="form-control" placeholder="🔍 Filter nach Datum, Typ, Art …">
</div>
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered" id="entriesTable">
<thead>
<tr>
<th data-sort-index="0" class="sortable">Datum</th>
<th data-sort-index="1" class="sortable">Zeit</th>
<th data-sort-index="2" class="sortable">Typ</th>
<th data-sort-index="3" class="sortable">Art</th>
<th data-sort-index="4" class="sortable">Beschreibung</th>
<th data-sort-index="5" class="sortable">Kilometer</th>
<th data-sort-index="6" class="sortable">Pauschale</th>
<th data-sort-index="7" class="sortable">Kosten</th>
<th data-sort-index="8" class="sortable">Zählt als Fortbildung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td>
<%= entry.date.strftime('%d.%m.%Y') %>
</td>
<td>
<% if entry.hours.to_i > 0 || entry.minutes.to_i > 0 %>
<%= "#{entry.hours} Stunden, #{entry.minutes} Minuten" %>
<% end %>
<td>
<%= ["Fortbildung", "Semesterkosten"].include?(entry.entry_art) ? entry.entry_art : entry.praktikums_typ.capitalize %>
</td>
<td>
<%= ["Fortbildung", "Semesterkosten"].include?(entry.entry_art) ? entry.beschreibung : entry.entry_art.capitalize %>
</td>
<td>
<%= entry.beschreibung %>
</td>
<td><%= entry.distance_km.to_f %> km</td>
<td>
<%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %>
</td>
<td>
<%= number_to_currency(entry.kosten, unit: "€", separator: ",", delimiter: ".") %>
</td>
<td class="text-center">
<%= check_box_tag "fortbildung_#{entry.id}", '1', entry.zaehlt_als_fortbildung, disabled: true %>
</td>
<td class="text-end">
<div class="d-flex justify-content-between">
<%= link_to 'Bearbeiten', edit_entry_path(entry), class: 'btn btn-sm btn-outline-primary' %>
<%= link_to 'Löschen', entry_path(entry), class: 'btn btn-sm btn-outline-danger open-delete-modal' %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<%= link_to "Export als CSV", export_csv_entries_path(format: :csv), class: "btn btn-outline-secondary mt-3 w-100 w-md-auto" %>
</div>
</div>
<!-- JS für Modal -->
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.open-delete-modal').forEach(button => {
button.addEventListener('click', function (e) {
e.preventDefault();
const targetUrl = this.getAttribute('href');
document.getElementById('modal-delete-form').setAttribute('action', targetUrl);
const modal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
modal.show();
});
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const filterInput = document.getElementById("entryFilter");
const table = document.getElementById("entriesTable");
const tableRows = table?.querySelectorAll("tbody tr");
if (filterInput && table && tableRows) {
filterInput.addEventListener("keyup", function () {
const query = this.value.toLowerCase();
tableRows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(query) ? "" : "none";
});
});
}
});
</script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const table = document.getElementById("entriesTable");
const headers = table.querySelectorAll("th.sortable");
let sortDirection = 1;
headers.forEach((header, index) => {
header.style.cursor = 'pointer';
header.addEventListener("click", () => {
const tbody = table.querySelector("tbody");
const rows = Array.from(tbody.querySelectorAll("tr"));
const isNumeric = header.dataset.sort === 'km' || header.dataset.sort === 'pauschale';
const isDate = header.dataset.sort === 'date';
rows.sort((a, b) => {
const cellA = a.children[index].textContent.trim();
const cellB = b.children[index].textContent.trim();
if (isDate) {
return sortDirection * (new Date(cellA.split('.').reverse().join('-')) - new Date(cellB.split('.').reverse().join('-')));
}
if (isNumeric) {
const numA = parseFloat(cellA.replace(/[^\d,.-]/g, "").replace(",", "."));
const numB = parseFloat(cellB.replace(/[^\d,.-]/g, "").replace(",", "."));
return sortDirection * (numA - numB);
}
return sortDirection * cellA.localeCompare(cellB);
});
rows.forEach(row => tbody.appendChild(row));
sortDirection *= -1; // Toggle asc/desc
});
});
});
document.addEventListener('DOMContentLoaded', function () {
const timerElement = document.getElementById('live-timer');
if (!timerElement) return;
<% if @running_entry&.start_time.present? %>
const startedAt = new Date("<%= @running_entry.start_time.iso8601 %>");
<% end %>
if (!startedAt) return;
const updateTimer = () => {
const now = new Date();
const diffMs = now - startedAt;
const totalMinutes = Math.floor(diffMs / 60000);
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
timerElement.textContent = `${hours}h ${minutes}min (${totalMinutes}min)`;
};
updateTimer();
setInterval(updateTimer, 60000); // alle Minute aktualisieren
});
</script>