From 527ed96b196e4512bcbf611676b7ae348121115b Mon Sep 17 00:00:00 2001 From: Christoph Marzell Date: Sun, 8 Feb 2026 06:53:44 +0100 Subject: [PATCH] add calendar --- app/assets/stylesheets/application.css | 68 +++++++++++++ app/controllers/calendar_controller.rb | 64 +++++++++++++ app/controllers/entries_controller.rb | 1 + app/helpers/application_helper.rb | 14 +++ app/views/calendar/month.html.erb | 110 ++++++++++++++++++++++ app/views/calendar/week.html.erb | 75 +++++++++++++++ app/views/entries/monthly_report.html.erb | 2 +- app/views/layouts/application.html.erb | 70 ++++++++++++++ config/routes.rb | 3 + 9 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 app/controllers/calendar_controller.rb create mode 100644 app/views/calendar/month.html.erb create mode 100644 app/views/calendar/week.html.erb diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 96d4125..e0a9b96 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -16,3 +16,71 @@ *= require_tree . *= 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; +} diff --git a/app/controllers/calendar_controller.rb b/app/controllers/calendar_controller.rb new file mode 100644 index 0000000..1b9e4fc --- /dev/null +++ b/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 diff --git a/app/controllers/entries_controller.rb b/app/controllers/entries_controller.rb index 2807f86..f8e94be 100644 --- a/app/controllers/entries_controller.rb +++ b/app/controllers/entries_controller.rb @@ -224,6 +224,7 @@ class EntriesController < ApplicationController rows.each do |r| month = r.attributes["month"].to_date total = r.attributes["total_minutes"].to_i + next if total.zero? typ_art = [r.praktikums_typ, r.entry_art] @report[month] ||= {} diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..b0a134f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,16 @@ 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 diff --git a/app/views/calendar/month.html.erb b/app/views/calendar/month.html.erb new file mode 100644 index 0000000..a3340bc --- /dev/null +++ b/app/views/calendar/month.html.erb @@ -0,0 +1,110 @@ +

Kalender – <%= Date::MONTHNAMES[@month] %> <%= @year %>

+ +
+
+ <%= 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" %> +
+ +
+ <%= form_with url: calendar_month_path(@year, @month), method: :get, local: true, class: "row g-2 justify-content-end" do %> +
+ <%= select_tag :typ, options_for_select(User::PRAKTIKUMSTYPEN, @filter_typ), include_blank: "Alle Typen", class: "form-select" %> +
+
+ <%= select_tag :art, options_for_select(User::ENTRY_ARTEN, @filter_art), include_blank: "Alle Arten", class: "form-select" %> +
+
+ <%= submit_tag "Filtern", class: "btn btn-outline-secondary" %> +
+ <% end %> +
+
+ + + + + <% Date::ABBR_DAYNAMES.each do |day| %> + + <% end %> + + + + <% weeks = @days.in_groups_of(7, nil) %> + <% weeks.each do |week| %> + + <% week.each do |date| %> + + + <% end %> + + <% end %> + +
<%= day %>
+ + <% if date %> +
+
+ <%= date.day %> + + <%= link_to "+", + new_entry_path(date: date), + class: "btn btn-sm btn-success day-add", + title: "Eintrag hinzufügen", + onclick: "event.stopPropagation();" %> +
+ +
+ <% (@entries[date.to_date] || []).each do |entry| %> + <%= link_to edit_entry_path(entry), + class: "day-entry text-decoration-none", + onclick: "event.stopPropagation();" do %> + + <%= entry.entry_art.capitalize %> + + + <%= entry.hours %>h <%= entry.minutes %>min + + <% end %> + <% end %> +
+
+ <% end %> +
+ + + + + + + diff --git a/app/views/calendar/week.html.erb b/app/views/calendar/week.html.erb new file mode 100644 index 0000000..8b4bdd0 --- /dev/null +++ b/app/views/calendar/week.html.erb @@ -0,0 +1,75 @@ +

Kalender – Woche <%= @week %> / <%= @year %>

+ +
+
+ <%= 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" %> +
+ +
+ <%= form_with url: calendar_week_path(@year, @week), method: :get, local: true, class: "row g-2 justify-content-end" do %> +
+ <%= select_tag :typ, options_for_select(User::PRAKTIKUMSTYPEN, @filter_typ), include_blank: "Alle Typen", class: "form-select" %> +
+
+ <%= select_tag :art, options_for_select(User::ENTRY_ARTEN, @filter_art), include_blank: "Alle Arten", class: "form-select" %> +
+
+ <%= submit_tag "Filtern", class: "btn btn-outline-secondary" %> +
+ <% end %> +
+
+ +
+ <% @days.each do |date| %> +
+
<%= date.strftime("%a %d.%m.") %>
+
+ <% if @entries[date] %> + <% @entries[date].each do |entry| %> +
+ <%= entry.entry_art.capitalize %> + <%= "#{entry.hours}h #{entry.minutes}min" %> + <%= link_to "✏️", edit_entry_path(entry), class: "btn btn-sm btn-outline-secondary ms-2" %> +
+ <% end %> + <% else %> +

Keine Einträge

+ <% end %> +
+ <%= link_to "+", new_entry_path(date: date), class: "btn btn-sm btn-success mt-1", onclick: "event.stopPropagation();" %> +
+ <% end %> +
+ + + + + diff --git a/app/views/entries/monthly_report.html.erb b/app/views/entries/monthly_report.html.erb index 1067f56..4d2997c 100644 --- a/app/views/entries/monthly_report.html.erb +++ b/app/views/entries/monthly_report.html.erb @@ -2,7 +2,7 @@ <% @report.each do |month, groups| %> <% is_current_month = month.month == Date.today.month && month.year == Date.today.year %> -
+
" >
<%= month.strftime("%B %Y") %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 87555ea..de56a65 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -65,6 +65,74 @@ 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; + } +