diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000..fb16c0d --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,73 @@ +class CategoriesController < ApplicationController + before_action :set_category, only: %i[ show edit update destroy ] + + # GET /categories or /categories.json + def index + @categories = Category.all.order(:name) + end + + # GET /categories/1 or /categories/1.json + def show + @category = Category.find(params[:id]) + @items = @category.items.includes(:user, :room).order(:name) + end + + # GET /categories/new + def new + @category = Category.new + end + + # GET /categories/1/edit + def edit + end + + # POST /categories or /categories.json + def create + @category = Category.new(category_params) + + respond_to do |format| + if @category.save + format.html { redirect_to @category, notice: "Category was successfully created." } + format.json { render :show, status: :created, location: @category } + else + format.html { render :new, status: :unprocessable_content } + format.json { render json: @category.errors, status: :unprocessable_content } + end + end + end + + # PATCH/PUT /categories/1 or /categories/1.json + def update + respond_to do |format| + if @category.update(category_params) + format.html { redirect_to @category, notice: "Category was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @category } + else + format.html { render :edit, status: :unprocessable_content } + format.json { render json: @category.errors, status: :unprocessable_content } + end + end + end + + # DELETE /categories/1 or /categories/1.json + def destroy + @category.destroy! + + respond_to do |format| + format.html { redirect_to categories_path, notice: "Category was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_category + @category = Category.find(params.expect(:id)) + end + + + # Only allow a list of trusted parameters through. + def category_params + params.require(:category).permit(:name, :description) + end +end diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb new file mode 100644 index 0000000..e06f315 --- /dev/null +++ b/app/helpers/categories_helper.rb @@ -0,0 +1,2 @@ +module CategoriesHelper +end diff --git a/app/models/category.rb b/app/models/category.rb index 54cb6ae..6803530 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,2 +1,5 @@ class Category < ApplicationRecord + has_many :items, dependent: :restrict_with_error + + validates :name, presence: true, uniqueness: true end diff --git a/app/views/authentications/events/index.html.erb b/app/views/authentications/events/index.html.erb index 8f30cc1..eec350e 100644 --- a/app/views/authentications/events/index.html.erb +++ b/app/views/authentications/events/index.html.erb @@ -38,28 +38,7 @@
- -
- -
+ <%= render "identity/account_tabs", active_tab: :activities %>
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? %> + + <% 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? %> +
+ + + + + + + + + + + + <% @items.each do |item| %> + + + + + + + + <% end %> + +
Artikel / ModellSeriennummer (SN)Aktueller Standort / InhaberQR-IDWert
+
<%= 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") %> +
+
+ <% 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 @@ + + +
+ + +
+ + + 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? %> + + <% 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 @@
- -
- -
-
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 @@
- -
- -
+ <%= 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