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.
207 lines
8.1 KiB
207 lines
8.1 KiB
<div class="container my-4">
|
|
|
|
|
|
|
|
<h1 class="mb-4">Meine Einträge</h1>
|
|
|
|
<!-- 🔢 Zusammenfassung -->
|
|
<div class="mb-4">
|
|
<h5 class="mb-3">🚗 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 %>
|
|
|
|
<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 %>
|
|
|
|
|
|
<p><strong>Gesamtzeit:</strong> <%= @total_minutes / 60 %> h <%= @total_minutes % 60 %> min</p>
|
|
</div>
|
|
|
|
<!-- 📊 Übersicht je Kombination -->
|
|
<div class="mb-4">
|
|
<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>
|
|
</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>
|
|
</tr>
|
|
<% end %>
|
|
<% end %>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-light mt-3 w-100 w-md-auto' %>
|
|
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<input type="text" id="entryFilter" class="form-control" placeholder="🔍 Filter nach Datum, Typ, Art …">
|
|
</div>
|
|
|
|
<!-- 📋 Tabelle aller Einträge -->
|
|
<div class="mb-5">
|
|
<h4 class="mb-3">📋 Einträge</h4>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover" 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">Kilometer</th>
|
|
<th data-sort-index="5" class="sortable">Pauschale</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% @entries.each do |entry| %>
|
|
<tr>
|
|
<td><%= entry.date.strftime('%d.%m.%Y') %></td>
|
|
<td><%= entry.hours.to_i %>h <%= entry.minutes.to_i %>min</td>
|
|
<td>
|
|
<%= entry.entry_art == "Fortbildung" ? "Fortbildung" : entry.praktikums_typ.capitalize %>
|
|
</td>
|
|
<td>
|
|
<%= entry.entry_art == "Fortbildung" ? entry.beschreibung : entry.entry_art.capitalize %>
|
|
</td>
|
|
<td><%= entry.distance_km.to_f %> km</td>
|
|
<td><%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %></td>
|
|
<td class="text-end">
|
|
<%= 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' %>
|
|
</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>
|
|
|
|
<!-- 🔒 Modal zur Bestätigung -->
|
|
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Löschen bestätigen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
Bist du sicher, dass du diesen Eintrag löschen möchtest?
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<form id="modal-delete-form" method="post">
|
|
<input type="hidden" name="_method" value="delete">
|
|
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
|
|
<button type="submit" class="btn btn-danger">Löschen</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</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
|
|
});
|
|
});
|
|
});
|
|
</script>
|