diff --git a/Gemfile b/Gemfile index 4b3f9a1..a0610d6 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,15 @@ gem 'turbolinks', '~> 5' # Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" gem 'tzinfo-data' + +gem 'jquery-rails' + +# Use jquery-ui for pretty UI +gem 'jquery-ui-rails' + +# Use Sass to process CSS +gem "sassc-rails" + # Use Redis adapter to run Action Cable in production # gem "redis", ">= 4.0.1" diff --git a/Gemfile.lock b/Gemfile.lock index 9099e96..58d1bee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,6 +110,8 @@ GEM erubi (1.13.1) et-orbi (1.4.0) tzinfo + ffi (1.17.2-x64-mingw32) + ffi (1.17.2-x86_64-linux-gnu) fugit (1.12.1) et-orbi (~> 1.4) raabro (~> 1.4) @@ -125,6 +127,12 @@ GEM jbuilder (2.14.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) + jquery-rails (4.6.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + jquery-ui-rails (8.0.0) + railties (>= 3.2.16) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -230,6 +238,16 @@ GEM railties (>= 7.0) rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc (2.4.0-x64-mingw32) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt securerandom (0.3.2) sprockets (4.2.2) concurrent-ruby (~> 1.0) @@ -241,6 +259,7 @@ GEM sprockets (>= 3.0.0) stringio (3.1.7) thor (1.4.0) + tilt (2.6.1) timeout (0.4.4) tsort (0.2.0) turbolinks (5.2.1) @@ -272,10 +291,13 @@ DEPENDENCIES bootsnap devise jbuilder + jquery-rails + jquery-ui-rails pg (~> 1.1) puma (>= 5.0) rails (~> 7.1.5, >= 7.1.5.2) rufus-scheduler + sassc-rails sprockets-rails turbolinks (~> 5) tzinfo-data diff --git a/app/assets/javascript/application.js b/app/assets/javascript/application.js index 989413b..b7603cc 100644 --- a/app/assets/javascript/application.js +++ b/app/assets/javascript/application.js @@ -4,4 +4,6 @@ import "@hotwired/turbo-rails" import * as ActiveStorage from "@rails/activestorage" import "channels" -console.log("HALLO RAILS JS") \ No newline at end of file +import "jquery" +import "jquery_ujs" +import "./jquery_ui" \ No newline at end of file diff --git a/app/assets/javascript/jquery_ui.js b/app/assets/javascript/jquery_ui.js new file mode 100644 index 0000000..233c435 --- /dev/null +++ b/app/assets/javascript/jquery_ui.js @@ -0,0 +1,3 @@ +import "jquery" +import "jquery_ujs" +import "./jquery_ui" \ No newline at end of file diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 288b9ab..96d4125 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,3 +1,6 @@ +@import "jquery-ui.css"; + + /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. diff --git a/app/controllers/entries_controller.rb b/app/controllers/entries_controller.rb index 1f4056d..d4bfd79 100644 --- a/app/controllers/entries_controller.rb +++ b/app/controllers/entries_controller.rb @@ -1,10 +1,10 @@ class EntriesController < ApplicationController before_action :authenticate_user! - before_action :set_entry, only: %i[edit update destroy] + before_action :set_entry, only: %i[edit update destroy stop_timer] def index @entries = current_user.entries.order(date: :desc) - + @running_entry = current_user.entries.find_by(end_time: nil, beschreibung: "Timer") # Gesamtzeit in Minuten @total_minutes = @entries.sum { |e| e.hours.to_i * 60 + e.minutes.to_i } @@ -60,6 +60,34 @@ class EntriesController < ApplicationController @entry = current_user.entries.build end + def start_timer + + typ = params[:typ] + art = params[:art] + + @entry = current_user.entries.create!(start_time: Time.current, beschreibung: "Timer" , praktikums_typ:typ, entry_art: art) + redirect_to entries_path, notice: "Timer gestartet" + end + + # neue Aktion: Timer stoppen + def stop_timer + #@entry = current_user.entries.where(end_time: nil).order(start_time: :desc).first + if @entry + @entry.end_time = Time.current + @entry.lunch_break_minutes = params[:lunch_break_minutes].to_i + + total_minutes = @entry.total_minutes_including_break + @entry.hours = total_minutes / 60 + @entry.minutes = total_minutes % 60 + + @entry.save! + notice = "Eintrag gestoppt – Gesamtzeit: #{@entry.hours} h #{@entry.minutes} min" + else + notice = "Kein laufender Eintrag gefunden" + end + redirect_to entries_path, notice: notice + end + def create @entry = current_user.entries.build(entry_params) if @entry.save diff --git a/app/javascript/application.js b/app/javascript/application.js index e69de29..0f50342 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -0,0 +1,8 @@ + +import "@hotwired/turbo-rails" +import * as ActiveStorage from "@rails/activestorage" +import "channels" + +import "jquery" +import "jquery_ujs" +import "./jquery_ui" \ No newline at end of file diff --git a/app/javascript/jquery_ui.js b/app/javascript/jquery_ui.js new file mode 100644 index 0000000..6b24672 --- /dev/null +++ b/app/javascript/jquery_ui.js @@ -0,0 +1,5 @@ +import jquery from "jquery"; + +window.jQuery = jquery; + +window.$ = jquery; \ No newline at end of file diff --git a/app/models/entry.rb b/app/models/entry.rb index 7c709c7..128e41d 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb @@ -4,9 +4,9 @@ class Entry < ApplicationRecord belongs_to :user - validates :date, :hours, :minutes, presence: true - validates :hours, numericality: { only_integer: true, greater_than_or_equal_to: 0 } - validates :minutes, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 60 } + validates :date, presence: true + validates :hours, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, unless: -> { start_time.present? && end_time.nil? } + validates :minutes, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 60 }, unless: -> { start_time.present? && end_time.nil? } before_save :normalize_time @@ -112,9 +112,28 @@ class Entry < ApplicationRecord end end + + def total_minutes_including_break + return nil unless start_time && end_time + minutes = ((end_time - start_time) / 60).to_i + minutes -= lunch_break_minutes.to_i + minutes = 0 if minutes < 0 + minutes + end + + def hours_and_minutes + mins = total_minutes_including_break + return [0, 0] unless mins + [mins / 60, mins % 60] + end + private def normalize_time + + self.minutes ||= 0 + self.hours ||= 0 + self.hours += minutes / 60 self.minutes = minutes % 60 end diff --git a/app/views/entries/_form.html.erb b/app/views/entries/_form.html.erb index d5c1d1b..33b70d0 100644 --- a/app/views/entries/_form.html.erb +++ b/app/views/entries/_form.html.erb @@ -43,12 +43,12 @@
<%= form.label :start_time, 'Beginn (Uhrzeit)', class: 'form-label' %> - <%= form.time_field :start_time, class: 'form-control', step: 60 %> + <%= form.time_field :start_time, class: 'form-control', step: 60 , discard_month: true, discard_day: true, discard_seconds: true, value: @entry.start_time&.strftime("%H:%M") %>
<%= form.label :end_time, 'Ende (Uhrzeit)', class: 'form-label' %> - <%= form.time_field :end_time, class: 'form-control', step: 60 %> + <%= form.time_field :end_time, class: 'form-control', step: 60 , discard_month: true, discard_day: true, discard_seconds: true, value: @entry.end_time&.strftime("%H:%M") %>
diff --git a/app/views/entries/index.html.erb b/app/views/entries/index.html.erb index 3abb0e4..eea5424 100644 --- a/app/views/entries/index.html.erb +++ b/app/views/entries/index.html.erb @@ -1,4 +1,4 @@ -
+

Meine Einträge

@@ -99,17 +99,67 @@
- <%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-light mt-3 w-100 w-md-auto' %> - -
- +
+

⏱ Timer

+ <% if @running_entry.present? %> +
+
+ Laufender Eintrag: + <%= @running_entry.praktikums_typ %> – <%= @running_entry.entry_art %>
+ Gestartet: <%= l(@running_entry.start_time, format: :short) %>
+ Dauer: Berechne … +
+
+ <%= button_to '⏹️ Stoppen', stop_timer_entry_path(@running_entry), method: :post, class: "btn btn-danger" %> +
+
+ <% end %> + +
+ + + <%= form_with url: start_timer_entries_path, method: :post, local: true do %> +
+
+ <%= label_tag :typ, 'Typ' %> + <%= select_tag :typ, options_for_select(Entry::PRAKTIKUMSTYPEN), class: "form-select" %> +
+ +
+ <%= label_tag :art, 'Art' %> + <%= select_tag :art, options_for_select(Entry::ENTRY_ARTEN), class: "form-select" %> +
+ +
+ <%= submit_tag '▶️ Start', class: "btn btn-success w-100", disabled: @running_entry.present? %> +
+
+ <% end %> + + + +
+ +
+ + <%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-info mt-3 w-100 w-md-auto mb-3' %> + + + + +

📋 Einträge

+ +
+ +
+
@@ -150,8 +200,6 @@ <%= number_to_currency(entry.kosten, unit: "€", separator: ",", delimiter: ".") %> - -
<%= link_to 'Bearbeiten', edit_entry_path(entry), class: 'btn btn-sm btn-outline-primary' %> @@ -240,4 +288,25 @@ }); }); + 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 + }); \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 845afac..5e15f80 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -3,7 +3,8 @@ Praktikumsuhr - + + <%= csrf_meta_tags %> <%= csp_meta_tag %> @@ -45,8 +46,20 @@ }); + function clearNotice(){ + $("#notice").animate({opacity:'0'}, 1500); + } + $(document).ready(ready); + $(document).on('page:load', ready); + + var ready = function() { + setTimeout(clearNotice, 1000); //Flash fade + }; + + + <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>