diff --git a/Gemfile b/Gemfile index 66dfb77..bbd53b6 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ gem "bootsnap", require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # gem "image_processing", "~> 1.2" - +gem 'administrate' group :development do # Use console on exceptions pages [https://github.com/rails/web-console] diff --git a/Gemfile.lock b/Gemfile.lock index 53d54be..d67e215 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -80,6 +80,11 @@ GEM mutex_m securerandom (>= 0.3) tzinfo (~> 2.0) + administrate (1.0.0) + actionpack (>= 6.0, < 9.0) + actionview (>= 6.0, < 9.0) + activerecord (>= 6.0, < 9.0) + kaminari (~> 1.2.2) base64 (0.3.0) bcrypt (3.1.20) benchmark (0.5.0) @@ -115,6 +120,18 @@ GEM jbuilder (2.14.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) + kaminari (1.2.2) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) + actionview + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) + activerecord + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) logger (1.7.0) loofah (2.24.1) crass (~> 1.0.2) @@ -243,6 +260,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + administrate bootsnap devise jbuilder diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb new file mode 100644 index 0000000..042c9de --- /dev/null +++ b/app/controllers/admin/application_controller.rb @@ -0,0 +1,21 @@ +# All Administrate controllers inherit from this +# `Administrate::ApplicationController`, making it the ideal place to put +# authentication logic or other before_actions. +# +# If you want to add pagination or other controller-level concerns, +# you're free to overwrite the RESTful controller actions. +module Admin + class ApplicationController < Administrate::ApplicationController + before_action :authenticate_admin + + def authenticate_admin + redirect_to root_path, alert: "Kein Zugriff!" unless current_user.email =="christoph@marzell.net" + end + + # Override this value to specify the number of elements to display at a time + # on index pages. Defaults to 20. + # def records_per_page + # params[:per_page] || 20 + # end + end +end diff --git a/app/controllers/admin/entries_controller.rb b/app/controllers/admin/entries_controller.rb new file mode 100644 index 0000000..0418a6c --- /dev/null +++ b/app/controllers/admin/entries_controller.rb @@ -0,0 +1,46 @@ +module Admin + class EntriesController < Admin::ApplicationController + # Overwrite any of the RESTful controller actions to implement custom behavior + # For example, you may want to send an email after a foo is updated. + # + # def update + # super + # send_foo_updated_email(requested_resource) + # end + + # Override this method to specify custom lookup behavior. + # This will be used to set the resource for the `show`, `edit`, and `update` + # actions. + # + # def find_resource(param) + # Foo.find_by!(slug: param) + # end + + # The result of this lookup will be available as `requested_resource` + + # Override this if you have certain roles that require a subset + # this will be used to set the records shown on the `index` action. + # + # def scoped_resource + # if current_user.super_admin? + # resource_class + # else + # resource_class.with_less_stuff + # end + # end + + # Override `resource_params` if you want to transform the submitted + # data before it's persisted. For example, the following would turn all + # empty values into nil values. It uses other APIs such as `resource_class` + # and `dashboard`: + # + # def resource_params + # params.require(resource_class.model_name.param_key). + # permit(dashboard.permitted_attributes(action_name)). + # transform_values { |value| value == "" ? nil : value } + # end + + # See https://administrate-demo.herokuapp.com/customizing_controller_actions + # for more information + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 0000000..2c4ab7d --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -0,0 +1,46 @@ +module Admin + class UsersController < Admin::ApplicationController + # Overwrite any of the RESTful controller actions to implement custom behavior + # For example, you may want to send an email after a foo is updated. + # + # def update + # super + # send_foo_updated_email(requested_resource) + # end + + # Override this method to specify custom lookup behavior. + # This will be used to set the resource for the `show`, `edit`, and `update` + # actions. + # + # def find_resource(param) + # Foo.find_by!(slug: param) + # end + + # The result of this lookup will be available as `requested_resource` + + # Override this if you have certain roles that require a subset + # this will be used to set the records shown on the `index` action. + # + # def scoped_resource + # if current_user.super_admin? + # resource_class + # else + # resource_class.with_less_stuff + # end + # end + + # Override `resource_params` if you want to transform the submitted + # data before it's persisted. For example, the following would turn all + # empty values into nil values. It uses other APIs such as `resource_class` + # and `dashboard`: + # + # def resource_params + # params.require(resource_class.model_name.param_key). + # permit(dashboard.permitted_attributes(action_name)). + # transform_values { |value| value == "" ? nil : value } + # end + + # See https://administrate-demo.herokuapp.com/customizing_controller_actions + # for more information + end +end diff --git a/app/controllers/entries_controller.rb b/app/controllers/entries_controller.rb index 170cefe..6625cc7 100644 --- a/app/controllers/entries_controller.rb +++ b/app/controllers/entries_controller.rb @@ -85,6 +85,14 @@ class EntriesController < ApplicationController redirect_to entries_path, notice: "Eintrag gelöscht" end + def export_csv + @entries = current_user.entries.order(date: :desc) + + respond_to do |format| + format.csv { send_data @entries.to_csv, filename: "eintraege-#{Date.today}.csv" } + end + end + private def set_entry @@ -101,5 +109,7 @@ class EntriesController < ApplicationController :distance_km ) end + + end diff --git a/app/dashboards/entry_dashboard.rb b/app/dashboards/entry_dashboard.rb new file mode 100644 index 0000000..99c7173 --- /dev/null +++ b/app/dashboards/entry_dashboard.rb @@ -0,0 +1,81 @@ +require "administrate/base_dashboard" + +class EntryDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + date: Field::Date, + distance_km: Field::Number, + entry_art: Field::String, + hours: Field::Number, + minutes: Field::Number, + praktikums_typ: Field::String, + user: Field::BelongsTo, + created_at: Field::DateTime, + updated_at: Field::DateTime, + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = %i[ + id + date + distance_km + entry_art + ].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = %i[ + id + date + distance_km + entry_art + hours + minutes + praktikums_typ + user + created_at + updated_at + ].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = %i[ + date + distance_km + entry_art + hours + minutes + praktikums_typ + user + ].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how entries are displayed + # across all pages of the admin dashboard. + # + # def display_resource(entry) + # "Entry ##{entry.id}" + # end +end diff --git a/app/dashboards/user_dashboard.rb b/app/dashboards/user_dashboard.rb new file mode 100644 index 0000000..6a138f1 --- /dev/null +++ b/app/dashboards/user_dashboard.rb @@ -0,0 +1,90 @@ +require "administrate/base_dashboard" + +class UserDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + email: Field::String, + encrypted_password: Field::String, + entries: Field::HasMany, + remember_created_at: Field::DateTime, + required_hours_matrix: Field::String.with_options(searchable: false), + reset_password_sent_at: Field::DateTime, + reset_password_token: Field::String, + total_required_hours: Field::Number, + weekly_target_hours: Field::Number, + weekly_target_matrix: Field::String.with_options(searchable: false), + created_at: Field::DateTime, + updated_at: Field::DateTime, + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = %i[ + id + email + encrypted_password + entries + ].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = %i[ + id + email + encrypted_password + entries + remember_created_at + required_hours_matrix + reset_password_sent_at + reset_password_token + total_required_hours + weekly_target_hours + weekly_target_matrix + created_at + updated_at + ].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = %i[ + email + encrypted_password + entries + remember_created_at + required_hours_matrix + reset_password_sent_at + reset_password_token + total_required_hours + weekly_target_hours + weekly_target_matrix + ].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how users are displayed + # across all pages of the admin dashboard. + # + # def display_resource(user) + # "User ##{user.id}" + # end +end diff --git a/app/models/entry.rb b/app/models/entry.rb index 8965c63..25798af 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb @@ -1,4 +1,8 @@ class Entry < ApplicationRecord + + require 'csv' + + belongs_to :user validates :date, :hours, :minutes, presence: true @@ -26,7 +30,23 @@ class Entry < ApplicationRecord def total_minutes hours * 60 + minutes end - + def self.to_csv + CSV.generate(headers: true, col_sep: ";") do |csv| + csv << %w[Datum Stunden Minuten Typ Art Kilometer User] + + all.each do |entry| + csv << [ + entry.date, + entry.hours, + entry.minutes, + entry.praktikums_typ, + entry.entry_art, + entry.distance_km, + entry.user.email + ] + end + end + end private def normalize_time diff --git a/app/views/entries/_form.html.erb b/app/views/entries/_form.html.erb index 569b801..4602fc7 100644 --- a/app/views/entries/_form.html.erb +++ b/app/views/entries/_form.html.erb @@ -10,37 +10,52 @@ <% end %> -
<%= notice %>
-<% end %> +<%= year %>: <%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
- <% end %> - -Gesamtzeit: <%= @total_minutes / 60 %> h <%= @total_minutes % 60 %> min
+ +<%= year %>: + <%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %> +
+ <% end %> +Gesamtzeit: <%= @total_minutes / 60 %> h <%= @total_minutes % 60 %> min
+| Typ | +Art | +Verbleibend | +Soll (h) | +Wöchentlich | +Vorauss. Ende | +
|---|---|---|---|---|---|
| <%= typ.capitalize %> | +<%= art %> | +<%= remaining ? "#{remaining / 60} h #{remaining % 60} min" : "—" %> | +<%= soll || "—" %> h | +<%= weekly || "—" %> h/Woche | +<%= ende.present? ? ende.strftime("%d.%m.%Y") : "—" %> | +
| Typ | -Art | -Verbleibend | -Soll (h) | -Wöchentlich | -Vorauss. Ende | -
|---|
| Datum | +Zeit | +Typ | +Art | +Kilometer | +Pauschale | +Aktionen | +||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <%= typ.capitalize %> | -<%= art %> | -<%= remaining ? "#{remaining / 60} h #{remaining % 60} min" : "—" %> | -<%= soll || "—" %> h | -<%= weekly || "—" %> h/Woche | -<%= ende.present? ? ende.strftime("%d.%m.%Y") : "—" %> | +<%= entry.date.strftime('%d.%m.%Y') %> | +<%= entry.hours.to_i %>h <%= entry.minutes.to_i %>min | +<%= entry.praktikums_typ %> | +<%= entry.entry_art %> | +<%= entry.distance_km.to_f %> km | +<%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %> | ++ <%= 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' %> + |
| Datum | -Zeit | -Typ | -Art | -Kilometer | -Pauschale | -Aktionen | -
|---|---|---|---|---|---|---|
| <%= entry.date.strftime('%d.%m.%Y') %> | -<%= entry.hours.to_i %>h <%= entry.minutes.to_i %>min | -<%= entry.praktikums_typ %> | -<%= entry.entry_art %> | -<%= entry.distance_km.to_f %> km | -<%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %> | -- <%= link_to '✏️ Bearbeiten', edit_entry_path(entry), class: 'btn btn-sm btn-primary' %> - <%= link_to '🗑️ Löschen', entry_path(entry), class: 'btn btn-sm btn-danger open-delete-modal' %> - | -