Browse Source

add fixes

main
Christoph Marzell 1 month ago
parent
commit
ca29316774
  1. 8
      .idea/.gitignore
  2. 5
      .idea/material_theme_project_new.xml
  3. 4
      .idea/misc.xml
  4. 8
      .idea/modules.xml
  5. 28
      .idea/praktikum.iml
  6. 2
      .idea/vcs.xml
  7. 4
      Gemfile
  8. 9
      Gemfile.lock
  9. 9
      app/assets/javascript/application.js
  10. 7
      app/controllers/application_controller.rb
  11. 63
      app/controllers/entries_controller.rb
  12. 16
      app/models/entry.rb
  13. 34
      app/models/user.rb
  14. 45
      app/views/devise/registrations/edit.html.erb
  15. 21
      app/views/entries/_form.html.erb
  16. 117
      app/views/entries/index.html.erb
  17. 8
      app/views/entries/show.html.erb
  18. 11
      app/views/layouts/application.html.erb
  19. 2
      config/initializers/assets.rb
  20. 2
      config/initializers/devise.rb
  21. 7
      db/migrate/20251107034228_add_fields_to_entries.rb
  22. 5
      db/migrate/20251107034403_add_required_hours_matrix_to_users.rb
  23. 5
      db/migrate/20251107054255_add_weekly_target_matrix_to_users.rb
  24. 7
      db/schema.rb

8
.idea/.gitignore

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

5
.idea/material_theme_project_new.xml

@ -3,10 +3,7 @@
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-51add93b:17b1a37f358:-8000" />
<option name="version" value="6.9.1" />
<option name="userId" value="10ad5c1e:19a5c60a0b7:-7ffd" />
</MTProjectMetadataState>
</option>
</component>

4
.idea/misc.xml

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="ruby-3.2.4-p170" project-jdk-type="RUBY_SDK" />
</project>

8
.idea/modules.xml

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/praktikum.iml" filepath="$PROJECT_DIR$/.idea/praktikum.iml" />
</modules>
</component>
</project>

28
.idea/praktikum.iml

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RUBY_MODULE" version="4">
<component name="ModuleRunConfigurationManager">
<shared />
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="PROVIDED" name="bcrypt (v3.1.20, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="builder (v3.3.0, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.5, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="crass (v1.0.6, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="devise (v4.9.4, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="mini_mime (v1.1.5, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="net-pop (v0.1.2, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="net-protocol (v0.2.2, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="orm_adapter (v0.5.0, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="racc (v1.8.1, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.6, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="warden (v1.2.9, ruby-3.2.4-p170) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="websocket-extensions (v0.1.5, ruby-3.2.4-p170) [gem]" level="application" />
</component>
</module>

2
.idea/vcs.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

4
Gemfile

@ -13,10 +13,10 @@ gem "pg", "~> 1.1"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
gem 'tzinfo-data'
# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"

9
Gemfile.lock

@ -218,8 +218,13 @@ GEM
thor (1.4.0)
timeout (0.4.4)
tsort (0.2.0)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.2)
tzinfo (>= 1.0.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@ -245,8 +250,12 @@ DEPENDENCIES
puma (>= 5.0)
rails (~> 7.1.5, >= 7.1.5.2)
sprockets-rails
turbolinks (~> 5)
tzinfo-data
web-console
RUBY VERSION
ruby 3.0.7p220
BUNDLED WITH
2.5.23

9
app/assets/javascript/application.js

@ -0,0 +1,9 @@
//= require rails-ujs
//= require turbolinks
//= require_tree .
import "@hotwired/turbo-rails"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
console.log("HALLO RAILS JS")

7
app/controllers/application_controller.rb

@ -1,2 +1,9 @@
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update, keys: [:total_required_hours, :weekly_target_hours, required_hours_matrix: {}, required_hours_matrix: {}])
end
end

63
app/controllers/entries_controller.rb

@ -4,17 +4,50 @@ class EntriesController < ApplicationController
def index
@entries = current_user.entries.order(date: :desc)
@total_minutes = @entries.sum(&:total_minutes)
@remaining_minutes = [current_user.total_required_hours * 60 - @total_minutes, 0].max
if current_user.weekly_target_hours.positive?
remaining_hours = @remaining_minutes / 60.0
weeks_remaining = (remaining_hours / current_user.weekly_target_hours).ceil
@estimated_end_date = Date.today + (weeks_remaining * 7)
else
@estimated_end_date = nil
# Gesamtzeit in Minuten
@total_minutes = @entries.sum { |e| e.hours.to_i * 60 + e.minutes.to_i }
# Gesamtbetrag der Kilometerpauschale
@total_kilometer_pauschale = @entries.sum(&:kilometer_pauschale)
# Zeitverbrauch je Kombination (typ + art)
@time_by_typ_art = @entries.group_by(&:praktikums_typ).transform_values do |group|
group.group_by(&:entry_art).transform_values do |entries|
entries.sum { |e| e.hours.to_i * 60 + e.minutes.to_i }
end
end
# Verbleibende Minuten je Kombination
@remaining_minutes_matrix = {}
@time_by_typ_art.each do |typ, arts|
@remaining_minutes_matrix[typ] ||= {}
arts.each do |art, spent_minutes|
target = current_user.required_hours_matrix.dig(typ, art).to_i * 60
remaining = [target - spent_minutes, 0].max
@remaining_minutes_matrix[typ][art] = remaining
end
end
# Voraussichtliches Ende je Kombination basierend auf weekly_target_matrix
@estimated_end_by_typ_art = {}
@remaining_minutes_matrix.each do |typ, arts|
@estimated_end_by_typ_art[typ] ||= {}
arts.each do |art, remaining_minutes|
hours_remaining = remaining_minutes / 60.0
weekly_hours = current_user.weekly_target_matrix.dig(typ, art).to_f
if weekly_hours > 0
weeks_remaining = (hours_remaining / weekly_hours).ceil
@estimated_end_by_typ_art[typ][art] = Date.today + weeks_remaining.weeks
else
@estimated_end_by_typ_art[typ][art] = nil
end
end
end
end
def new
@entry = current_user.entries.build
@ -29,7 +62,9 @@ class EntriesController < ApplicationController
end
end
def edit; end
def edit;
@entry
end
def update
if @entry.update(entry_params)
@ -51,6 +86,14 @@ class EntriesController < ApplicationController
end
def entry_params
params.require(:entry).permit(:date, :hours, :minutes)
params.require(:entry).permit(
:date,
:hours,
:minutes,
:praktikums_typ,
:entry_art,
:distance_km
)
end
end

16
app/models/entry.rb

@ -7,6 +7,22 @@ class Entry < ApplicationRecord
before_save :normalize_time
PRAKTIKUMSTYPEN = %w[propädeutikum fachspezifikum]
ENTRY_ARTEN = %w[Praktikum Selbsterfahrung Supervision]
validates :praktikums_typ, inclusion: { in: PRAKTIKUMSTYPEN }
validates :entry_art, inclusion: { in: ENTRY_ARTEN }
validates :distance_km, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
def kilometer_pauschale
return 0 unless distance_km.present?
distance_km * 0.42
end
def jahr
date.year
end
def total_minutes
hours * 60 + minutes
end

34
app/models/user.rb

@ -5,4 +5,38 @@ class User < ApplicationRecord
:recoverable, :rememberable, :validatable
has_many :entries, dependent: :destroy
PRAKTIKUMSTYPEN = %w[propädeutikum fachspezifikum]
ENTRY_ARTEN = %w[Praktikum Selbsterfahrung Supervision]
after_initialize :set_default, if: :new_record?
def set_default
self.required_hours_matrix = PRAKTIKUMSTYPEN.to_h do |typ|
[typ, ENTRY_ARTEN.to_h { |art| [art, default_hours_for(typ, art)] }]
end
self.weekly_target_matrix = {
"propädeutikum" => { "Praktikum" => 12, "Selbsterfahrung" => 1, "Supervision" => 1 },
"fachspezifikum" => { "Praktikum" => 10, "Selbsterfahrung" => 2, "Supervision" => 2 }
}
end
def default_hours_for(typ, art)
case [typ, art]
when ["propädeutikum", "Praktikum"] then 480
when ["propädeutikum", "Selbsterfahrung"] then 50
when ["propädeutikum", "Supervision"] then 20
when ["fachspezifikum", "Praktikum"] then 600
when ["fachspezifikum", "Selbsterfahrung"] then 80
when ["fachspezifikum", "Supervision"] then 40
else 0
end
end
def required_hours_for(typ, art)
required_hours_matrix.dig(typ, art) || 0
end
end

45
app/views/devise/registrations/edit.html.erb

@ -8,17 +8,44 @@
<hr>
<h3>🔧 Praktikumsziele</h3>
<h3>🎯 Zielstunden individuell</h3>
<div class="mb-3">
<%= f.label :total_required_hours, "Zielstunden insgesamt", class: "form-label" %>
<%= f.number_field :total_required_hours, class: "form-control", min: 1 %>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Typ</th>
<th>Art</th>
<th>Zielstunden</th>
</tr>
</thead>
<tbody>
<% User::PRAKTIKUMSTYPEN.product(User::ENTRY_ARTEN).each do |(typ, art)| %>
<tr>
<td><%= typ.capitalize %></td>
<td><%= art %></td>
<td>
<%= number_field_tag(
"user[required_hours_matrix][#{typ}][#{art}]",
current_user.required_hours_matrix.dig(typ, art),
class: "form-control", min: 0
) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<div class="mb-3">
<%= f.label :weekly_target_hours, "Stunden pro Woche", class: "form-label" %>
<%= f.number_field :weekly_target_hours, class: "form-control", min: 1 %>
</div>
<h5>Wöchentliche Zielstunden (weekly_target_matrix)</h5>
<% ["propädeutikum", "fachspezifikum"].each do |typ| %>
<% ["Praktikum", "Selbsterfahrung", "Supervision"].each do |art| %>
<div class="mb-2">
<%= label_tag "user[weekly_target_matrix][#{typ}][#{art}]", "#{typ.capitalize} – #{art}" %>
<%= number_field_tag "user[weekly_target_matrix][#{typ}][#{art}]",
current_user.weekly_target_matrix.dig(typ, art),
class: "form-control", step: 1 %>
</div>
<% end %>
<% end %>
<hr>

21
app/views/entries/_form.html.erb

@ -12,17 +12,32 @@
<div class="mb-3" style="width: 33%">
<%= form.label :date, 'Datum', class: 'form-label' %>
<%= form.text_field :date, class: 'form-control flatpickr', data: { enable_time: false } %>
<%= form.text_field :date, class: 'form-control flatpickr', data: { enable_time: false } , value: (form.object.date || Date.today)%>
</div>
<div class="mb-3" style="width: 33%">
<%= form.label :hours, 'Stunden', class: 'form-label' %>
<%= form.number_field :hours, class: 'form-control', min: 0 %>
<%= form.number_field :hours, class: 'form-control', min: 0 , value: form.object.minutes || 2%>
</div>
<div class="mb-3" style="width: 33%">
<%= form.label :minutes, 'Minuten', class: 'form-label' %>
<%= form.number_field :minutes, class: 'form-control', min: 0, max: 59 %>
<%= form.number_field :minutes, class: 'form-control', min: 0, max: 59, value: form.object.minutes || 0 %>
</div>
<div class="mb-3">
<%= form.label :praktikums_typ, 'Praktikumstyp', class: 'form-label' %>
<%= form.select :praktikums_typ, Entry::PRAKTIKUMSTYPEN, {}, class: 'form-control' %>
</div>
<div class="mb-3">
<%= form.label :entry_art, 'Art', class: 'form-label' %>
<%= form.select :entry_art, Entry::ENTRY_ARTEN, {}, class: 'form-control' %>
</div>
<div class="mb-3">
<%= form.label :distance_km, 'Entfernung (km) Gesamt', class: 'form-label' %>
<%= form.number_field :distance_km, class: 'form-control', min: 0 %>
</div>
<div class="mb-3">

117
app/views/entries/index.html.erb

@ -1,35 +1,124 @@
<h1 class="mb-4">🕒 Praktikumszeit Tracker</h1>
<h1>Meine Einträge</h1>
<% if notice %>
<p class="alert alert-success"><%= notice %></p>
<% end %>
<!-- 🔢 Zusammenfassung -->
<div class="mb-3">
<p><strong>Gesamtzeit:</strong> <%= @total_minutes / 60 %> h <%= @total_minutes % 60 %> min</p>
<p><strong>Fehlend:</strong> <%= @remaining_minutes / 60 %> h <%= @remaining_minutes % 60 %> min</p>
<p><strong>Geplante Stunden/Woche:</strong> <%= current_user.weekly_target_hours %> h</p>
<% if @estimated_end_date.present? %>
<p><strong>Voraussichtliches Ende:</strong> <%= @estimated_end_date.strftime("%d.%m.%Y") %></p>
<% end %>
<%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-primary' %>
<p><strong>Fahrtkosten gesamt:</strong>
<%= number_to_currency(@total_kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %>
</p>
<hr>
<h4 class="mt-4">📊 Übersicht je Kombination</h4>
<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 %></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>
<%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-light mt-3' %>
</div>
<table class="table table-striped">
<!-- 📋 Tabelle -->
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Datum</th>
<th>Stunden</th>
<th>Minuten</th>
<th>Zeit</th>
<th>Typ</th>
<th>Art</th>
<th>Kilometer</th>
<th>Pauschale</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td><%= entry.date %></td>
<td><%= entry.hours %></td>
<td><%= entry.minutes %></td>
<td><%= entry.date.strftime('%d.%m.%Y') %></td>
<td><%= entry.hours.to_i %>h <%= entry.minutes.to_i %>min</td>
<td><%= entry.praktikums_typ %></td>
<td><%= entry.entry_art %></td>
<td><%= entry.distance_km.to_f %> km</td>
<td><%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %></td>
<td>
<%= link_to '✏️ Bearbeiten', edit_entry_path(entry), class: 'btn btn-sm btn-primary' %>
<%= link_to '🗑️ Löschen', entry, method: :delete, data: { confirm: 'Sicher?' }, class: 'btn btn-sm btn-danger' %>
<%= link_to '🗑️ Löschen', entry_path(entry), class: 'btn btn-sm btn-danger open-delete-modal' %>
</td>
</tr>
<% end %>
</tbody>
</table>
<!-- 🔒 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>

8
app/views/entries/show.html.erb

@ -1,9 +1,15 @@
<p style="color: green"><%= notice %></p>
<%= render @entry %>
<% if @entry.present? %>
<%= render @entry %>
<% else %>
<p>⚠️ Kein Eintrag gefunden.</p>
<% end %>
<div>
<% if @entry.present? %>
<%= link_to "Edit this entry", edit_entry_path(@entry) %> |
<% end %>
<%= link_to "Back to entries", entries_path %>
<%= button_to "Destroy this entry", @entry, method: :delete %>

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

@ -1,5 +1,14 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/material_blue.css">
<!-- Bootstrap CSS (optional, falls noch nicht eingebunden) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Bundle JS (inkl. Popper & Modal Support) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" defer></script>
<%= javascript_include_tag "application.js", "data-turbo-track": "reload", defer: true %>
<body class="container mt-4">
<% if notice %><div class="alert alert-success"><%= notice %></div><% end %>
<% if alert %><div class="alert alert-danger"><%= alert %></div><% end %>
@ -10,7 +19,7 @@
<p class="text-end">
Eingeloggt als <%= current_user.email %> |
<%= link_to "Profil", edit_user_registration_path %> |
<%= link_to "Logout", destroy_user_session_path, method: :delete %>
<%= link_to "Logout", destroy_user_session_path, method: :delete, data: { turbo: false } %>
</p>
<% end %>

2
config/initializers/assets.rb

@ -2,7 +2,7 @@
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "1.0"
Rails.application.config.assets.precompile += %w( application.js )
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path

2
config/initializers/devise.rb

@ -266,7 +266,7 @@ Devise.setup do |config|
# config.navigational_formats = ['*/*', :html, :turbo_stream]
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete
config.sign_out_via = :get
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting

7
db/migrate/20251107034228_add_fields_to_entries.rb

@ -0,0 +1,7 @@
class AddFieldsToEntries < ActiveRecord::Migration[7.1]
def change
add_column :entries, :praktikums_typ, :string, null: false, default: "propädeutikum"
add_column :entries, :entry_art, :string, null: false, default: "Praktikum"
add_column :entries, :distance_km, :integer, null: false, default: 0
end
end

5
db/migrate/20251107034403_add_required_hours_matrix_to_users.rb

@ -0,0 +1,5 @@
class AddRequiredHoursMatrixToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :required_hours_matrix, :jsonb, default: {}, null: false
end
end

5
db/migrate/20251107054255_add_weekly_target_matrix_to_users.rb

@ -0,0 +1,5 @@
class AddWeeklyTargetMatrixToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :weekly_target_matrix, :jsonb, default: {}, null: false
end
end

7
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2025_11_06_161110) do
ActiveRecord::Schema[7.1].define(version: 2025_11_07_054255) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -21,6 +21,9 @@ ActiveRecord::Schema[7.1].define(version: 2025_11_06_161110) do
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "praktikums_typ", default: "propädeutikum", null: false
t.string "entry_art", default: "Praktikum", null: false
t.integer "distance_km", default: 0, null: false
t.index ["user_id"], name: "index_entries_on_user_id"
end
@ -34,6 +37,8 @@ ActiveRecord::Schema[7.1].define(version: 2025_11_06_161110) do
t.datetime "updated_at", null: false
t.integer "total_required_hours", default: 480, null: false
t.integer "weekly_target_hours", default: 12, null: false
t.jsonb "required_hours_matrix", default: {}, null: false
t.jsonb "weekly_target_matrix", default: {}, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

Loading…
Cancel
Save