Browse Source

add calendar

main
Christoph Marzell 1 month ago
parent
commit
527ed96b19
  1. 68
      app/assets/stylesheets/application.css
  2. 64
      app/controllers/calendar_controller.rb
  3. 1
      app/controllers/entries_controller.rb
  4. 14
      app/helpers/application_helper.rb
  5. 110
      app/views/calendar/month.html.erb
  6. 75
      app/views/calendar/week.html.erb
  7. 2
      app/views/entries/monthly_report.html.erb
  8. 70
      app/views/layouts/application.html.erb
  9. 3
      config/routes.rb

68
app/assets/stylesheets/application.css

@ -16,3 +16,71 @@
*= require_tree . *= require_tree .
*= require_self *= require_self
*/ */
.bg-purple {
background-color: #6f42c1 !important;
color: white !important;
}
.calendar-table th,
.calendar-table td {
min-width: 120px;
height: 100px;
vertical-align: top;
}
.calendar-table td .badge {
font-size: 0.85em;
}
.calendar-table td {
vertical-align: top;
padding: 4px;
height: 130px;
width: 14.28%;
}
.day-box {
border-radius: 6px;
padding: 4px;
height: 100%;
display: flex;
flex-direction: column;
}
.day-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.day-number {
font-weight: 600;
font-size: 0.9rem;
}
.day-add {
padding: 0 6px;
font-size: 0.75rem;
}
.day-entries {
flex-grow: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
}
.day-entry {
background: #f8f9fa;
border-radius: 4px;
padding: 3px 5px;
font-size: 0.75rem;
display: flex;
align-items: center;
gap: 4px;
}
.day-entry:hover {
background: #e9ecef;
}

64
app/controllers/calendar_controller.rb

@ -0,0 +1,64 @@
class CalendarController < ApplicationController
before_action :authenticate_user!
def month
@year = params[:year].to_i
@month = params[:month].to_i
while @month < 1
@month += 12
@year -= 1
end
while @month > 12
@month -= 12
@year += 1
end
first_of_month = Date.new(@year, @month, 1)
last_of_month = first_of_month.end_of_month
@filter_typ = params[:typ]
@filter_art = params[:art]
@entries = current_user.entries
.where(date: first_of_month..last_of_month)
@entries = @entries.where(praktikums_typ: @filter_typ) if @filter_typ.present?
@entries = @entries.where(entry_art: @filter_art) if @filter_art.present?
@entries = @entries.group_by { |e| e.date.to_date }
puts @entries
@days = (first_of_month..last_of_month).to_a
end
def week
@year = params[:year].to_i
@week = params[:week].to_i
while @month < 1
@month += 12
@year -= 1
end
while @month > 12
@month -= 12
@year += 1
end
first = Date.commercial(@year, @week, 1)
last = Date.commercial(@year, @week, 7)
@filter_typ = params[:typ]
@filter_art = params[:art]
@entries = current_user.entries
.where(date: first..last)
@entries = @entries.where(praktikums_typ: @filter_typ) if @filter_typ.present?
@entries = @entries.where(entry_art: @filter_art) if @filter_art.present?
@entries = @entries.group_by{ |e| e.date.to_date }
@days = (first..last).to_a
end
end

1
app/controllers/entries_controller.rb

@ -224,6 +224,7 @@ class EntriesController < ApplicationController
rows.each do |r| rows.each do |r|
month = r.attributes["month"].to_date month = r.attributes["month"].to_date
total = r.attributes["total_minutes"].to_i total = r.attributes["total_minutes"].to_i
next if total.zero?
typ_art = [r.praktikums_typ, r.entry_art] typ_art = [r.praktikums_typ, r.entry_art]
@report[month] ||= {} @report[month] ||= {}

14
app/helpers/application_helper.rb

@ -1,2 +1,16 @@
module ApplicationHelper module ApplicationHelper
def color_class_for(entry)
case entry.entry_art
when "Praktikum"
"bg-primary text-white"
when "Selbsterfahrung"
"bg-purple text-white"
when "Supervision"
"bg-warning text-dark"
when "Fortbildung"
"bg-success text-white"
else
"bg-secondary text-white"
end
end
end end

110
app/views/calendar/month.html.erb

@ -0,0 +1,110 @@
<h1 class="mb-3">Kalender – <%= Date::MONTHNAMES[@month] %> <%= @year %></h1>
<div class="row mb-3">
<div class="col-md-6">
<%= link_to "← Vorheriger Monat", calendar_month_path(@year, @month - 1, typ: @filter_typ, art: @filter_art), class: "btn btn-outline-primary" %>
<%= link_to "Nächster Monat →", calendar_month_path(@year, @month + 1, typ: @filter_typ, art: @filter_art), class: "btn btn-outline-primary ms-2" %>
</div>
<div class="col-md-6 text-end">
<%= form_with url: calendar_month_path(@year, @month), method: :get, local: true, class: "row g-2 justify-content-end" do %>
<div class="col-auto">
<%= select_tag :typ, options_for_select(User::PRAKTIKUMSTYPEN, @filter_typ), include_blank: "Alle Typen", class: "form-select" %>
</div>
<div class="col-auto">
<%= select_tag :art, options_for_select(User::ENTRY_ARTEN, @filter_art), include_blank: "Alle Arten", class: "form-select" %>
</div>
<div class="col-auto">
<%= submit_tag "Filtern", class: "btn btn-outline-secondary" %>
</div>
<% end %>
</div>
</div>
<table class="table table-bordered calendar-table">
<thead class="table-light">
<tr>
<% Date::ABBR_DAYNAMES.each do |day| %>
<th><%= day %></th>
<% end %>
</tr>
</thead>
<tbody>
<% weeks = @days.in_groups_of(7, nil) %>
<% weeks.each do |week| %>
<tr>
<% week.each do |date| %>
<td class="day-cell
<%= 'bg-light' if date.blank? %>
<%= 'today' if date == Date.current %>"
data-date="<%= date %>"
data-label="<%= date&.strftime('%d.%m.%Y') %>">
<% if date %>
<div class="day-box">
<div class="day-header">
<span class="day-number"><%= date.day %></span>
<%= link_to "+",
new_entry_path(date: date),
class: "btn btn-sm btn-success day-add",
title: "Eintrag hinzufügen",
onclick: "event.stopPropagation();" %>
</div>
<div class="day-entries">
<% (@entries[date.to_date] || []).each do |entry| %>
<%= link_to edit_entry_path(entry),
class: "day-entry text-decoration-none",
onclick: "event.stopPropagation();" do %>
<span class="badge <%= color_class_for(entry) %>">
<%= entry.entry_art.capitalize %>
</span>
<span class="entry-time">
<%= entry.hours %>h <%= entry.minutes %>min
</span>
<% end %>
<% end %>
</div>
</div>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<!-- Tagesmodal -->
<div class="modal fade" id="dayEntriesModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Einträge für <span id="modal-date-label"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="modal-day-entries"></div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".day-cell").forEach(cell => {
cell.addEventListener("click", function () {
const date = this.dataset.date;
const label = this.dataset.label;
const entriesHtml = this.querySelector(".day-entries").innerHTML;
document.getElementById("modal-date-label").textContent = label;
document.getElementById("modal-day-entries").innerHTML = entriesHtml;
const modal = new bootstrap.Modal(document.getElementById("dayEntriesModal"));
modal.show();
});
});
});
</script>

75
app/views/calendar/week.html.erb

@ -0,0 +1,75 @@
<h1 class="mb-3">Kalender – Woche <%= @week %> / <%= @year %></h1>
<div class="row mb-3">
<div class="col-md-6">
<%= link_to "← Vorherige Woche", calendar_week_path(@year, @week - 1, typ: @filter_typ, art: @filter_art), class: "btn btn-outline-primary" %>
<%= link_to "Nächste Woche →", calendar_week_path(@year, @week + 1, typ: @filter_typ, art: @filter_art), class: "btn btn-outline-primary ms-2" %>
</div>
<div class="col-md-6 text-end">
<%= form_with url: calendar_week_path(@year, @week), method: :get, local: true, class: "row g-2 justify-content-end" do %>
<div class="col-auto">
<%= select_tag :typ, options_for_select(User::PRAKTIKUMSTYPEN, @filter_typ), include_blank: "Alle Typen", class: "form-select" %>
</div>
<div class="col-auto">
<%= select_tag :art, options_for_select(User::ENTRY_ARTEN, @filter_art), include_blank: "Alle Arten", class: "form-select" %>
</div>
<div class="col-auto">
<%= submit_tag "Filtern", class: "btn btn-outline-secondary" %>
</div>
<% end %>
</div>
</div>
<div class="row">
<% @days.each do |date| %>
<div class="col border p-2 day-cell" data-date="<%= date %>" data-label="<%= date.strftime('%A %d.%m.%Y') %>">
<div class="fw-bold"><%= date.strftime("%a %d.%m.") %></div>
<div class="day-entries d-none">
<% if @entries[date] %>
<% @entries[date].each do |entry| %>
<div class="mb-2">
<span class="badge <%= color_class_for(entry) %>"><%= entry.entry_art.capitalize %></span>
<%= "#{entry.hours}h #{entry.minutes}min" %>
<%= link_to "✏️", edit_entry_path(entry), class: "btn btn-sm btn-outline-secondary ms-2" %>
</div>
<% end %>
<% else %>
<p class="text-muted small">Keine Einträge</p>
<% end %>
</div>
<%= link_to "+", new_entry_path(date: date), class: "btn btn-sm btn-success mt-1", onclick: "event.stopPropagation();" %>
</div>
<% end %>
</div>
<!-- Tagesmodal (wie im Monat) -->
<div class="modal fade" id="dayEntriesModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Einträge für <span id="modal-date-label"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="modal-day-entries"></div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".day-cell").forEach(cell => {
cell.addEventListener("click", function () {
const date = this.dataset.date;
const label = this.dataset.label;
const entriesHtml = this.querySelector(".day-entries").innerHTML;
document.getElementById("modal-date-label").textContent = label;
document.getElementById("modal-day-entries").innerHTML = entriesHtml;
const modal = new bootstrap.Modal(document.getElementById("dayEntriesModal"));
modal.show();
});
});
});
</script>

2
app/views/entries/monthly_report.html.erb

@ -2,7 +2,7 @@
<% @report.each do |month, groups| %> <% @report.each do |month, groups| %>
<% is_current_month = month.month == Date.today.month && month.year == Date.today.year %> <% is_current_month = month.month == Date.today.month && month.year == Date.today.year %>
<div class="card mb‑4 mt-4 <%= 'bg-secondary text-white' if is_current_month %>">
<div class="card mb‑4 mt-4 <%= 'bg-secondary text-white' if is_current_month %> <%= "style=\"opacity:7.0;\"" if is_current_month %>" >
<div class="card-header"> <div class="card-header">
<strong><%= month.strftime("%B %Y") %></strong> <strong><%= month.strftime("%B %Y") %></strong>
</div> </div>

70
app/views/layouts/application.html.erb

@ -65,6 +65,74 @@
max-width: 1820px !important; max-width: 1820px !important;
} }
} }
.calendar-table th,
.calendar-table td {
min-width: 120px;
height: 100px;
vertical-align: top;
}
.calendar-table td .badge {
font-size: 0.85em;
}
.calendar-table td {
vertical-align: top;
padding: 4px;
height: 130px;
width: 14.28%;
}
.day-box {
border-radius: 6px;
padding: 4px;
height: 100%;
display: flex;
flex-direction: column;
}
.day-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.day-number {
font-weight: 600;
font-size: 0.9rem;
}
.day-add {
padding: 0 6px;
font-size: 0.75rem;
}
.day-entries {
flex-grow: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
}
.day-entry {
background: #f8f9fa;
border-radius: 4px;
padding: 3px 5px;
font-size: 0.75rem;
display: flex;
align-items: center;
gap: 4px;
}
.day-entry:hover {
background: #e9ecef;
}
.day-cell.today {
box-shadow: inset 0 0 0 2px #0d6efd;
}
</style> </style>
<script> <script>
@ -128,6 +196,8 @@
<%= link_to "Profil", edit_user_registration_path, class: "nav-link" %> <%= link_to "Profil", edit_user_registration_path, class: "nav-link" %>
</li> </li>
<li class="nav-item"><%= link_to "Monatskalender", calendar_month_path(Date.today.year, Date.today.month), class: "nav-link" %></li>
<% if current_user.is_admin? %> <% if current_user.is_admin? %>
<li class="nav-item"> <li class="nav-item">
<%= link_to "Admin", admin_root_url, class: "nav-link" %> <%= link_to "Admin", admin_root_url, class: "nav-link" %>

3
config/routes.rb

@ -16,6 +16,9 @@ Rails.application.routes.draw do
end end
end end
get "/calendar/month/:year/:month", to: "calendar#month", as: :calendar_month
get "/calendar/week/:year/:week", to: "calendar#week", as: :calendar_week
resources :mileage_rates, only: [:index, :new, :create, :edit, :update] resources :mileage_rates, only: [:index, :new, :create, :edit, :update]

Loading…
Cancel
Save