diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index fb16c0d..a319965 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -9,7 +9,17 @@ class CategoriesController < ApplicationController # GET /categories/1 or /categories/1.json def show @category = Category.find(params[:id]) - @items = @category.items.includes(:user, :room).order(:name) + + # Basis-Abfrage: Alle Items dieser Kategorie laden + @items = @category.items.includes(:user, :room).order(created_at: :desc) + + if params[:query].present? + query_str = "%#{params[:query]}%" + @items = @items.where( + "items.name LIKE :q OR items.sku LIKE :q OR items.serial_number LIKE :q OR items.sticker_id LIKE :q", + q: query_str + ) + end end # GET /categories/new diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 98241e8..77bfea8 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -5,6 +5,15 @@ class ItemsController < ApplicationController def index @items = Item.all.includes(:category, :user, :room).order(created_at: :desc) + if params[:query].present? + query_str = "%#{params[:query]}%" + # Durchsucht Name, SKU, Seriennummer und deine Sticker-ID gleichzeitig + @items = @items.where( + "items.name LIKE :q OR items.sku LIKE :q OR items.serial_number LIKE :q OR items.sticker_id LIKE :q", + q: query_str + ) + end + respond_to do |format| format.html # Rendert ganz normal deine Bestandsliste im Browser format.csv do diff --git a/app/javascript/controllers/assignment_controller.js b/app/javascript/controllers/assignment_controller.js index 5410755..dd02d15 100644 --- a/app/javascript/controllers/assignment_controller.js +++ b/app/javascript/controllers/assignment_controller.js @@ -1,27 +1,29 @@ -import { Controller } from "@hotwire/stimulus" +// Wird der controller überhaupt gebraucht? Funktioniert auch ohne.. ;) + +import { Controller } from "@hotwired/stimulus"; export default class extends Controller { - static targets = [ "userSection", "roomSection" ] + static targets = ["userSection", "roomSection"]; toggle(event) { - const value = event.target.value - const userDropdown = this.userSectionTarget.querySelector('select') - const roomDropdown = this.roomSectionTarget.querySelector('select') + const value = event.target.value; + const userDropdown = this.userSectionTarget.querySelector("select"); + const roomDropdown = this.roomSectionTarget.querySelector("select"); if (value === "user") { - this.userSectionTarget.classList.remove("hidden") - this.roomSectionTarget.classList.add("hidden") - roomDropdown.value = "" // Raum-ID löschen, da ein Artikel nur einen Inhaber haben kann + this.userSectionTarget.classList.remove("hidden"); + this.roomSectionTarget.classList.add("hidden"); + roomDropdown.value = ""; // Raum-ID löschen, da ein Artikel nur einen Inhaber haben kann } else if (value === "room") { - this.roomSectionTarget.classList.remove("hidden") - this.userSectionTarget.classList.add("hidden") - userDropdown.value = "" // User-ID löschen + this.roomSectionTarget.classList.remove("hidden"); + this.userSectionTarget.classList.add("hidden"); + userDropdown.value = ""; // User-ID löschen } else { // Hauptlager ausgewählt -> Beide ausblenden und Werte in der DB nullen - this.userSectionTarget.classList.add("hidden") - this.roomSectionTarget.classList.add("hidden") - userDropdown.value = "" - roomDropdown.value = "" + this.userSectionTarget.classList.add("hidden"); + this.roomSectionTarget.classList.add("hidden"); + userDropdown.value = ""; + roomDropdown.value = ""; } } } diff --git a/app/javascript/controllers/scanner_controller.js b/app/javascript/controllers/scanner_controller.js index 69bffd3..69102ae 100644 --- a/app/javascript/controllers/scanner_controller.js +++ b/app/javascript/controllers/scanner_controller.js @@ -1,49 +1,58 @@ -import { Controller } from "@hotwire/stimulus" +import { Controller } from "@hotwired/stimulus"; export default class extends Controller { - static targets = [ "input", "preview", "modal" ] + static targets = ["input", "preview", "modal"]; connect() { - this.html5QrCode = null + this.html5QrCode = null; } // Öffnet das Modal und startet den Kamera-Stream startCamera(event) { - event.preventDefault() - + event.preventDefault(); + // Modal anzeigen - this.modalTarget.classList.remove("hidden") + this.modalTarget.classList.remove("hidden"); // Neue Instanz auf dem Preview-Div mit der ID des Elements erzeugen - this.html5QrCode = new Html5Qrcode(this.previewTarget.id) + this.html5QrCode = new Html5Qrcode(this.previewTarget.id); // FPS-Rate und Scan-Rahmen (250x250px) festlegen - const config = { fps: 10, qrbox: { width: 250, height: 250 } } + const config = { fps: 10, qrbox: { width: 250, height: 250 } }; - this.html5QrCode.start( - { facingMode: "environment" }, // Erzwingt die rückseitige Hauptkamera bei Handys - config, - (decodedText, decodedResult) => { - // SUCCESS: Code erkannt! - this.inputTarget.value = decodedText // Trägt die ID (z.B. 10024) ins Textfeld ein - this.stopCamera() // Stoppt die Kamera und schließt das Fenster - }, - (errorMessage) => { - // Kontinuierlicher Scan-Loop (Fehler ignorieren, wenn kein QR-Code im Bild ist) - } - ).catch((err) => { - console.error("Kamera-Zugriff verweigert oder blockiert:", err) - }) + this.html5QrCode + .start( + { facingMode: "environment" }, // Erzwingt die rückseitige Hauptkamera bei Handys + config, + (decodedText, decodedResult) => { + // SUCCESS: Code erkannt! + this.inputTarget.value = decodedText; // Trägt die ID (z.B. 10024) ins Textfeld ein + + // NEU: Simuliert das Tippen, damit dein search-form Controller die Live-Suche sofort startet! + this.inputTarget.dispatchEvent(new Event("input", { bubbles: true })); + + this.stopCamera(); // Stoppt die Kamera und schließt das Fenster + }, + (errorMessage) => { + // Kontinuierlicher Scan-Loop (Fehler ignorieren, wenn kein QR-Code im Bild ist) + }, + ) + .catch((err) => { + console.error("Kamera-Zugriff verweigert oder blockiert:", err); + }); } // Schließt das Modal und beendet den Stream sauber stopCamera() { if (this.html5QrCode && this.html5QrCode.isScanning) { - this.html5QrCode.stop().then(() => { - this.modalTarget.classList.add("hidden") - }).catch((err) => console.error("Fehler beim Beenden des Streams:", err)) + this.html5QrCode + .stop() + .then(() => { + this.modalTarget.classList.add("hidden"); + }) + .catch((err) => console.error("Fehler beim Beenden des Streams:", err)); } else { - this.modalTarget.classList.add("hidden") + this.modalTarget.classList.add("hidden"); } } } diff --git a/app/javascript/controllers/search_form_controller.js b/app/javascript/controllers/search_form_controller.js new file mode 100644 index 0000000..d706c80 --- /dev/null +++ b/app/javascript/controllers/search_form_controller.js @@ -0,0 +1,11 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + submit() { + clearTimeout(this.timeout); + // Wartet 200ms nach dem letzten Tastendruck, bevor gesucht wird + this.timeout = setTimeout(() => { + this.element.requestSubmit(); + }, 200); + } +} diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb index f5a9917..549c86c 100644 --- a/app/views/categories/show.html.erb +++ b/app/views/categories/show.html.erb @@ -8,28 +8,25 @@ <% end %>
<%= @category.description.presence || "Keine Beschreibung hinterlegt." %>
Bisher sind keine Inventargegenstände erfasst.
-Klicke oben rechts auf "Artikel hinzufügen", um das erste Gerät einzubuchen.
-Keine passenden Artikel in dieser Kategorie gefunden.
+<%= item.category.name %>
Halte den QR-Code des Geräts ruhig in den Scan-Rahmen.
+Bisher sind keine Inventargegenstände erfasst.
-Klicke oben rechts auf "Artikel hinzufügen", um das erste Gerät einzubuchen.
-Keine passenden Inventargegenstände gefunden.
+