diff --git a/app/views/categories/_category.html.erb b/app/views/categories/_category.html.erb
new file mode 100644
index 0000000..f83bc2e
--- /dev/null
+++ b/app/views/categories/_category.html.erb
@@ -0,0 +1,2 @@
+
+
diff --git a/app/views/categories/_category.json.jbuilder b/app/views/categories/_category.json.jbuilder
new file mode 100644
index 0000000..ac43ff4
--- /dev/null
+++ b/app/views/categories/_category.json.jbuilder
@@ -0,0 +1,2 @@
+json.extract! category, :id, :created_at, :updated_at
+json.url category_url(category, format: :json)
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
new file mode 100644
index 0000000..16866be
--- /dev/null
+++ b/app/views/categories/_form.html.erb
@@ -0,0 +1,41 @@
+<%= form_with(model: category, class: "space-y-6 max-w-2xl mx-auto bg-white border border-gray-200 rounded-xl shadow-sm p-6 md:p-8") do |form| %>
+
+ <% if category.errors.any? %>
+
+
<%= pluralize(category.errors.count, "Fehler") %> verhinderten das Speichern:
+
+ <% category.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+
Kategorie-Details
+
Definiere eine übergeordnete Gruppe für dein Inventar (z.B. Laptops oder Bürostühle).
+
+
+
+
+
+
+ <%= form.label :name, "Name der Kategorie", class: "block text-sm font-medium mb-2 text-gray-700" %>
+ <%= form.text_field :name, required: true, class: "py-2.5 px-4 block w-full border border-gray-300 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 bg-gray-50/50", placeholder: "z.B. IT-Infrastruktur" %>
+
+
+
+
+ <%= form.label :description, "Beschreibung / Notizen", class: "block text-sm font-medium mb-2 text-gray-700" %>
+ <%= form.text_area :description, rows: 4, class: "py-2.5 px-4 block w-full border border-gray-300 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 bg-gray-50/50", placeholder: "Welche Art von Gegenständen fällt in diese Kategorie?..." %>
+
+
+
+
+
+
+ <%= link_to "Abbrechen", categories_path, class: "py-2.5 px-4 inline-flex items-center text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition shadow-sm" %>
+ <%= form.submit "Kategorie speichern", class: "py-2.5 px-4 inline-flex items-center text-sm font-semibold rounded-lg bg-blue-600 text-white hover:bg-blue-700 shadow-sm transition cursor-pointer" %>
+
+
+<% end %>
diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb
new file mode 100644
index 0000000..70bff86
--- /dev/null
+++ b/app/views/categories/edit.html.erb
@@ -0,0 +1,14 @@
+<% content_for :title, "Kategorie bearbeiten" %>
+
+
+ <%= render "form", category: @category %>
+
+
+
+
+
Kategorie löschen
+
Dies kann nicht rückgängig gemacht werden. Nur möglich, wenn die Kategorie komplett leer ist.
+
+ <%= link_to "Löschen", @category, data: { turbo_method: :delete, turbo_confirm: "Möchtest du diese Kategorie wirklich unwiderruflich löschen?" }, class: "py-2 px-3 text-xs font-semibold text-white bg-red-600 hover:bg-red-700 rounded-lg shadow-sm transition" %>
+
+
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
new file mode 100644
index 0000000..77aee76
--- /dev/null
+++ b/app/views/categories/index.html.erb
@@ -0,0 +1,44 @@
+
+
+<% content_for :title, "Inventar-Kategorien" %>
+
+
+
+ <% content_for :top_bar_actions do %>
+ <%= link_to new_category_path, class: "py-2 px-4 text-sm font-semibold rounded-lg bg-blue-600 text-white hover:bg-blue-700 flex items-center gap-1.5 shadow-sm transition" do %>
+
+ Kategorie erstellen
+ <% end %>
+ <% end %>
+
+
+
+ <% @categories.each do |category| %>
+
+
+
+
+
+
+ <%= category.items.count %> <%= category.items.count == 1 ? "Objekt" : "Objekte" %>
+
+
+
<%= category.name %>
+
<%= category.description.presence || "Keine Beschreibung hinterlegt." %>
+
+
+
+
+ <%= link_to "Bearbeiten", edit_category_path(category), class: "text-gray-500 hover:text-gray-700 transition" %>
+
+ <%= link_to category_path(category), class: "text-blue-600 hover:text-blue-800 flex items-center gap-0.5 transition" do %>
+ Artikel ansehen →
+ <% end %>
+
+
+ <% end %>
+
+
diff --git a/app/views/categories/index.json.jbuilder b/app/views/categories/index.json.jbuilder
new file mode 100644
index 0000000..aa5baad
--- /dev/null
+++ b/app/views/categories/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @categories, partial: "categories/category", as: :category
diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb
new file mode 100644
index 0000000..b434e06
--- /dev/null
+++ b/app/views/categories/new.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, "Neue Kategorie erstellen" %>
+
+
+ <%= render "form", category: @category %>
+
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000..c0d8985
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,82 @@
+<% content_for :title, "Kategorie: #{@category.name}" %>
+
+
+
+ <% content_for :top_bar_actions do %>
+ <%= link_to categories_path, class: "py-2 px-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 flex items-center gap-1.5 shadow-sm transition" do %>
+
+
+ Alle Kategorien
+ <% end %>
+ <% end %>
+
+
+
+
Beschreibung
+
<%= @category.description.presence || "Keine Beschreibung hinterlegt für diese Kategorie." %>
+
+
+
+
+
+
Registrierte Unikate in dieser Kategorie
+
+
+ <% if @items.any? %>
+
+
+
+
+ Artikel / Modell
+ Seriennummer (SN)
+ Aktueller Standort / Inhaber
+ QR-ID
+ Wert
+
+
+
+ <% @items.each do |item| %>
+
+
+ <%= item.name %>
+ SKU: <%= item.sku %>
+
+
+ <%= item.serial_number %>
+
+
+
+ <% if item.user.present? %>
+
+ 👤 <%= item.user.name %>
+
+ <% elsif item.room.present? %>
+
+ 📍 <%= item.room.name_with_building %>
+
+ <% else %>
+
+ 📦 Im Hauptlager
+
+ <% end %>
+
+
+ #<%= item.sticker_id %>
+
+
+ <%= number_to_currency(item.price, unit: "€", separator: ",", delimiter: ".", format: "%n %u") %>
+
+
+ <% end %>
+
+
+
+ <% else %>
+
+
+
+
Bisher sind keine Artikel in dieser Kategorie registriert.
+
+ <% end %>
+
+
diff --git a/app/views/categories/show.json.jbuilder b/app/views/categories/show.json.jbuilder
new file mode 100644
index 0000000..30e6b47
--- /dev/null
+++ b/app/views/categories/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! "categories/category", category: @category
diff --git a/app/views/identity/_account_tabs.html.erb b/app/views/identity/_account_tabs.html.erb
new file mode 100644
index 0000000..72b982b
--- /dev/null
+++ b/app/views/identity/_account_tabs.html.erb
@@ -0,0 +1,81 @@
+
+
+
+ Navigationsbereich wechseln
+
+
+ >
+ 🔒 Sicherheit
+
+ >
+ ✉️ E-Mail-Adresse
+
+ >
+ 💻 Sitzungen & Geräte
+
+ >
+ 📊 Aktivitäten
+
+
+
+
+
+
+
+
+
+ <% if active_tab == :security %>
+
+
+ Sicherheit
+
+ <% else %>
+ <%= link_to edit_password_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
+
+ Sicherheit
+ <% end %>
+ <% end %>
+
+
+ <% if active_tab == :email %>
+
+
+ E-Mail-Adresse
+
+ <% else %>
+ <%= link_to edit_identity_email_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
+
+ E-Mail-Adresse
+ <% end %>
+ <% end %>
+
+
+ <% if active_tab == :sessions %>
+
+
+ Sitzungen & Geräte
+
+ <% else %>
+ <%= link_to sessions_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
+
+ Sitzungen & Geräte
+ <% end %>
+ <% end %>
+
+
+ <% if active_tab == :activities %>
+
+
+ Aktivitäten
+
+ <% else %>
+ <%= link_to authentications_events_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
+
+ Aktivitäten
+ <% end %>
+ <% end %>
+
+
+
diff --git a/app/views/identity/emails/edit.html.erb b/app/views/identity/emails/edit.html.erb
index d04e55a..08b5e7b 100644
--- a/app/views/identity/emails/edit.html.erb
+++ b/app/views/identity/emails/edit.html.erb
@@ -1,4 +1,4 @@
-
<%= alert %>
+
+<% content_for :title, "E-Mail-Adresse ändern" %>
+
+
+
+ <%= render "identity/account_tabs", active_tab: :email %>
+
+
+
+
+
+
E-Mail-Adresse aktualisieren
+
Ändere deine primäre Login-Adresse. Aus Sicherheitsgründen musst du diese Aktion mit deinem Passwort bestätigen.
+
+
+
+ <%= form_with(url: identity_email_path, method: :patch, class: "bg-white border border-gray-200 rounded-xl shadow-sm p-6 md:p-8 space-y-6") do |form| %>
+
+ <% if Current.user.errors.any? %>
+
+
Fehler beim Aktualisieren:
+
+ <% Current.user.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+
+ <%= form.label :email, "Neue E-Mail-Adresse", class: "block text-sm font-medium mb-1.5 text-gray-700" %>
+
+
+ <%= form.email_field :email, value: Current.user.email, required: true, autofocus: true, autocomplete: "email", class: "block w-full pl-10 pr-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-gray-50/50" %>
+
+
+
+
+
+ <%= form.label :password_challenge, "Aktuelles Passwort zur Bestätigung", class: "block text-sm font-medium mb-1.5 text-gray-700" %>
+
+
+ <%= form.password_field :password_challenge, required: true, autocomplete: "current-password", class: "block w-full pl-10 pr-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-gray-50/50" %>
+
+
+
+
+
+
+
+ <%= form.submit "E-Mail-Adresse speichern", class: "py-2.5 px-5 text-sm font-semibold rounded-lg bg-blue-600 text-white hover:bg-blue-700 shadow-sm transition cursor-pointer" %>
+
+ <% end %>
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 1bce242..a307620 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -76,7 +76,7 @@
Wareneingang
<% end %>
- <%= link_to "", class: nav_link_class("categories") do %>
+ <%= link_to categories_path, class: nav_link_class("categories") do %>
Kategorien
<% end %>
diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb
index 2cb3628..a6bef50 100644
--- a/app/views/passwords/edit.html.erb
+++ b/app/views/passwords/edit.html.erb
@@ -47,29 +47,8 @@
-
-
-
-
-
-
-
- Sicherheit
-
-
- <%= link_to sessions_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
-
-
- Sitzungen & Geräte
- <% end %>
- <%= link_to authentications_events_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
-
- Aktivitäten
- <% end %>
+ <%= render "identity/account_tabs", active_tab: :security %>
-
-
-
diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb
index 6d7fcdc..e2e638c 100644
--- a/app/views/sessions/index.html.erb
+++ b/app/views/sessions/index.html.erb
@@ -38,29 +38,7 @@
-
-
-
-
-
- <%= link_to edit_password_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
-
- Sicherheit
- <% end %>
-
-
-
-
- Sitzungen & Geräte
-
-
- <%= link_to authentications_events_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-b-2 py-4 px-1 text-sm font-medium flex items-center gap-2 transition" do %>
-
- Aktivitäten
- <% end %>
-
-
-
+ <%= render "identity/account_tabs", active_tab: :sessions %>
diff --git a/config/routes.rb b/config/routes.rb
index 72feb4c..cfc9516 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
Rails.application.routes.draw do
+ resources :categories
namespace :authentications do
resources :events, only: :index
end
diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb
new file mode 100644
index 0000000..7dc061e
--- /dev/null
+++ b/test/controllers/categories_controller_test.rb
@@ -0,0 +1,48 @@
+require "test_helper"
+
+class CategoriesControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @category = categories(:one)
+ end
+
+ test "should get index" do
+ get categories_url
+ assert_response :success
+ end
+
+ test "should get new" do
+ get new_category_url
+ assert_response :success
+ end
+
+ test "should create category" do
+ assert_difference("Category.count") do
+ post categories_url, params: { category: {} }
+ end
+
+ assert_redirected_to category_url(Category.last)
+ end
+
+ test "should show category" do
+ get category_url(@category)
+ assert_response :success
+ end
+
+ test "should get edit" do
+ get edit_category_url(@category)
+ assert_response :success
+ end
+
+ test "should update category" do
+ patch category_url(@category), params: { category: {} }
+ assert_redirected_to category_url(@category)
+ end
+
+ test "should destroy category" do
+ assert_difference("Category.count", -1) do
+ delete category_url(@category)
+ end
+
+ assert_redirected_to categories_url
+ end
+end