Store Display: Bilddauer mit Nachkommastelle (1,5 Sekunden)

Das Feld „Dauer (Sekunden)“ im b2in-Display nimmt Komma und Punkt an,
erlaubt 0,5 bis 120 Sekunden und speichert eine Zahl; der Player rechnet
unverändert mal 1000. Anzeige in der Liste mit Komma. Nachtrag 14.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-17 09:52:33 +00:00
parent 90aa079cf7
commit 3211569fee
9 changed files with 354 additions and 8 deletions

View file

@ -53,7 +53,8 @@ class DisplayVersionEditor extends Component
public string $mediaSubline = '';
public int $mediaDuration = 10;
/** Anzeigedauer eines Bildes in Sekunden als Eingabetext, Komma oder Punkt erlaubt („1,5“). */
public string $mediaDuration = '10';
public bool $mediaIsActive = true;
@ -247,6 +248,12 @@ class DisplayVersionEditor extends Component
public function saveItem(): void
{
if ($this->itemType === 'media' && $this->mediaType === 'image' && self::parseDuration($this->mediaDuration) === null) {
$this->addError('mediaDuration', 'Bitte eine Dauer zwischen 0,5 und 120 Sekunden angeben, z. B. 1,5.');
return;
}
$content = $this->buildItemContent();
$isActive = $this->getActiveFlag();
@ -279,6 +286,41 @@ class DisplayVersionEditor extends Component
$this->refreshModulePreview();
}
/**
* Sekunden aus der Eingabe lesen: „1,5“, „1.5“ und „10“ sind gültig, gerundet auf Zehntel.
* Erlaubt sind 0,5 bis 120 Sekunden; alles andere ergibt null.
*/
public static function parseDuration(string|int|float $input): ?float
{
$normalized = str_replace(',', '.', trim((string) $input));
if ($normalized === '' || ! is_numeric($normalized)) {
return null;
}
$seconds = round((float) $normalized, 1);
if ($seconds < 0.5 || $seconds > 120) {
return null;
}
return $seconds;
}
/**
* Sekunden für die Anzeige: ganze Zahlen ohne Nachkommastelle, sonst mit Komma („1,5“).
*/
public static function formatDuration(int|float|string|null $seconds): string
{
$seconds = (float) str_replace(',', '.', (string) ($seconds ?? 10));
if (floor($seconds) === $seconds) {
return (string) (int) $seconds;
}
return str_replace('.', ',', (string) round($seconds, 1));
}
public function deleteItem(int $id): void
{
DisplayVersionItem::findOrFail($id)->delete();
@ -437,7 +479,7 @@ class DisplayVersionEditor extends Component
$this->mediaUrl = $content['media_url'] ?? '';
$this->mediaHeadline = $content['headline'] ?? '';
$this->mediaSubline = $content['subline'] ?? '';
$this->mediaDuration = $content['duration_seconds'] ?? 10;
$this->mediaDuration = self::formatDuration($content['duration_seconds'] ?? 10);
$this->mediaIsActive = $isActive;
}
@ -501,7 +543,7 @@ class DisplayVersionEditor extends Component
'media_url' => $this->mediaUrl,
'headline' => $this->mediaHeadline,
'subline' => $this->mediaSubline,
'duration_seconds' => $this->mediaDuration,
'duration_seconds' => self::parseDuration($this->mediaDuration) ?? 10,
],
'slide' => [
'type' => 'detail',
@ -663,7 +705,7 @@ class DisplayVersionEditor extends Component
$this->mediaUrl = '';
$this->mediaHeadline = '';
$this->mediaSubline = '';
$this->mediaDuration = 10;
$this->mediaDuration = '10';
$this->mediaIsActive = true;
$this->slideShowLogo = true;
$this->slideLogoUrl = '';

View file

@ -1790,3 +1790,22 @@ bleiben dort, wo sie sind. Ob die Bilder aus den Markengalerien später automati
`tests/Feature/ReferenzenPageTest.php` (Referenzen vor Galerien, Entwurf ausgeblendet und
Fallback, unveröffentlichte Marke und Referenz ohne Bild, Galeriebild als Titelbild),
Dashboard-Statistik in `CmsAdminTest` auf Referenzen umgestellt.
---
## Nachtrag 14 · Bilddauer im b2in-Display mit Nachkommastelle (17.09.2026)
Kevins Wunsch: Im Store-Display-Modul „b2in Display“ soll die „Dauer (Sekunden)“ eines Bildes
auch 1,5 annehmen. Das Feld war ganzzahlig (`int`, Zahlenfeld mit `min=1`).
- **Eingabe als Text** mit Dezimal-Tastatur: „10“, „1,5“ und „1.5“ sind gültig, Leerzeichen
egal, gerundet auf Zehntel. Erlaubt sind 0,5 bis 120 Sekunden; alles andere bringt beim
Speichern eine Fehlermeldung am Feld und speichert nichts. Bei Videos wird das Feld ignoriert.
- **Gespeichert wird eine Zahl** (`duration_seconds` = 1.5). Der Player in
`public/_cabinet/b2in/index.html` rechnet ohnehin `duration_seconds × 1000`, kommt also ohne
Änderung mit 1500 ms klar. Bestehende Einträge mit ganzen Sekunden bleiben unverändert.
- Anzeige in der Eintragsliste mit Komma („1,5s“), ganze Zahlen ohne Nachkommastelle.
- Doku `public/_cabinet/_docs/b2in-displays.md` ergänzt.
**Tests:** vier neue Fälle in `tests/Feature/DisplayVersionTest.php` (Komma-Eingabe,
Parser/Formatierer, Ablehnung unter 0,5 s, Video ignoriert das Feld). Kein Deployment-Schritt.

View file

@ -0,0 +1,83 @@
#1
Die Richtung passt deutlich besser: locker, verständlich und ohne typischen Werbeprospekt-Ton.
Besonders gut finde ich den Einstieg „Für den Anfang reicht uns ein Bild und ein grobes Maß völlig aus.“ Der nimmt sofort eine Hürde. Auch „Sie müssen also nicht erst alles millimetergenau ausmessen“ spricht genau die Frage an, die ein Kunde wahrscheinlich im Kopf hat.
Nur zwei Stellen würde ich noch ändern: „valides Angebot“ klingt im ansonsten lockeren Text etwas technisch. Ich würde „ein wirklich brauchbares Angebot“ oder „ein konkretes Angebot“ schreiben. Und statt „zu Ihnen nach Hause“ lieber „bei Ihnen vor Ort“, weil ihr ja nicht ausschließlich Privatwohnungen plant.
Dann wäre meine Fassung:
Für den Anfang reicht uns ein Bild und ein grobes Maß völlig aus.
Sie müssen also nicht erst alles millimetergenau ausmessen. Ein paar Fotos von der Raumsituation und die ungefähren Maße reichen, damit wir schon eine gute Planung machen und Ihnen ein konkretes Angebot erstellen können.
Danach gehen wir gemeinsam ins Detail: Aufteilung, Ausstattung, Materialien, Farben und natürlich der Preis.
Wenn alles grundsätzlich passt, kommen wir zum professionellen Aufmaß bei Ihnen vor Ort. Danach machen wir die Feinplanung fertig und geben den Schrank zur Bestellung frei.
So läuft’s:
Foto + grobe Maße → Beratung → Planung & Angebot → Aufmaß vor Ort → Feinplanung → Bestellung
Das wäre für mich ziemlich genau die richtige Mischung aus persönlich, unkompliziert und trotzdem hochwertig.
#2 Febrü
Ja. Ich würde wirklich nur die vereinbarten Stellen ändern und den Rest der Seite lassen. Die aktuelle Seite hat diese Passagen an den entsprechenden Stellen.
Bei uns gibt es Kunden – keine Schubladen
Ob Privatkunde oder Unternehmen: Für uns gibt es zunächst einmal Kunden mit unterschiedlichen Räumen, Anforderungen und Stückzahlen.
Natürlich beeinflusst die Menge den Preis. Fünfzig Arbeitsplätze werden anders kalkuliert als ein einzelner Schreibtisch fürs Homeoffice. Der Anspruch an Produkt, Beratung und Service bleibt trotzdem derselbe.
Febrü bleibt Febrü. B2in bleibt B2in. Und jeder Kunde erhält eine Lösung, die zu seinem tatsächlichen Bedarf passt.
Ob im Kleinen oder im Großen – die Detailtreue entscheidet.
Aus Herford. Für moderne Arbeitswelten.
Febrü entwickelt und produziert hochwertige Büromöbel direkt im ostwestfälischen Herford – nur wenige Kilometer von Bielefeld entfernt. Eine sehr hohe Fertigungstiefe ermöglicht kurze Wege, verlässliche Qualität und hohe Flexibilität bei Materialien, Farben und individuellen Anforderungen.
Das ist auch sachlich sauber: Febrü selbst spricht aktuell von einer hohen Fertigungstiefe und nennt auf der Unternehmensseite weiterhin 95 %. Mit „sehr hohe Fertigungstiefe“ seid ihr also auf der sicheren Seite, ohne euch an eine konkrete Zahl zu binden.
Wertschätzung ist keine Frage der Stückzahl
Gerade in Zeiten des Fachkräftemangels können Unternehmen es sich nicht leisten, Arbeitsbedingungen als Nebensache zu behandeln. Wer Top-Leistung erwartet, muss die passenden Voraussetzungen schaffen. Ein Obstkorb und kostenloses Wasser sind freundlich – ersetzen aber keinen guten Arbeitsplatz.
Besonders deutlich wird das bei Menschen, die Beruf und Familie miteinander organisieren. Teilzeit bedeutet nicht halbe Leistung. Oft wird in einem kürzeren Zeitfenster hoch konzentriert gearbeitet, während gleichzeitig Schule, Termine, Betreuung und der ganz normale Familienalltag organisiert werden müssen.
Gerade hier darf der Arbeitsplatz kein Provisorium aus Küchentisch, Beistelltisch oder Schlafzimmerecke sein. Er muss sofort funktionieren, Bewegung ermöglichen, technisch durchdacht sein und sich trotzdem vernünftig in das Zuhause einfügen. Für den Menschen, der ihn täglich nutzt, ist ein einzelner Arbeitsplatz keine kleine Lösung. Er ist seine vollständige Arbeitswelt.
Unverändert lassen
„Nicht drei Marken. Ein durchgängiger Raum.“ würde ich komplett so stehen lassen. Das Zusammenspiel von Febrü, Rotpunkt, CABINET und B2in ist dort sehr gut erklärt.
„Das Wir und das Ich“ ebenfalls unverändert lassen. Gerade der Einstieg „Ja, ja – das könnte man unter dem üblichen Verkaufs-Blabla abheften“ gibt der Seite Persönlichkeit und passt zum restlichen Ton.
Beim doppelten Satz „Ob im Kleinen oder im Großen – die Detailtreue entscheidet.“ würde ich die erste Version oben stehen lassen und die zweite Version nach „Erleben statt nur aus dem Katalog auswählen“ löschen. Aktuell steht er tatsächlich zweimal auf der Seite.
Den mitlaufenden CTA würde ich ebenfalls nicht verändern.
#3 Rotpunk
#a text erstezen "Mehr als Küchen"
Mehr als Küchen
Die Möglichkeiten von Rotpunkt enden nicht bei der klassischen Küche. Das Möbel- und Planungssystem lässt sich auch für Hauswirtschaftsräume, Badezimmer, Sideboards, Wohnmöbel und weitere Bereiche rund um das Wohnen einsetzen.
Materialien, Farben und Gestaltungselemente können dabei über mehrere Räume hinweg aufgegriffen werden. So entsteht ein stimmiges Gesamtbild, statt in jedem Raum wieder ganz von vorne anzufangen.
Bei Bedarf denken wir den Innenraum deshalb auch über die Küche hinaus – von Möbeln und Beleuchtung bis zu Tapeten, Fußböden und, wenn es sein soll, der letzten Blume.
#b Thema Handwerk
Das Thema Handwerk am Ende von Rotpunkt sollte so formuliert sein, dass es bei jeder Marke als letzte Ergänzung angezeigt wird
#3 Cabinet
"Die Fragen die wir vorher stellen"
zweiter Absatz ersetzen:
Auch die Dinge, die man nicht jeden Tag braucht, gehören in die Planung: Wäschekörbe, Bügelbrett, Staubsauger, Karnevalskostüme oder besondere Sportsachen. Wenn wir vorher wissen, was untergebracht werden soll, können wir den Platz direkt sinnvoll dafür einplanen.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,131 @@
Otterpohl – nicht „easy to click & buy“, aber trotzdem easy to get
Vieles kann man heute online finden, konfigurieren und direkt bestellen. Und das ist auch gut so.
Aber manchmal möchte man etwas Besonderes. Eine eigene Idee. Ein Möbel, das genau so aussehen soll, wie man es im Kopf hat. Ein ungewöhnliches Detail. Eine besondere Kombination aus Materialien. Oder einfach etwas, das man so auf keiner Webseite und auch nicht im Handel findet.
Und manchmal beginnt es nur mit einem Foto und dem Satz:
„So ungefähr – aber eben für mich.“
Das ist vielleicht nicht „easy to click & buy“.
Aber es darf trotzdem easy to get sein.
Denn genau dafür sind wir da: Sie bringen die Idee mit – und wir kümmern uns gemeinsam mit Otterpohl um Planung, handwerkliche Umsetzung und Montage.
Mit unseren Möbelpartnern können wir bereits sehr individuell planen. Mit Otterpohl kommen zusätzlich Tischlerhandwerk, individuelle Fertigung, besondere Oberflächen und Innenausbau dazu.
Und genau diese Kombination erweitert unsere Möglichkeiten noch einmal erheblich.
Ziemlich nah an der eierlegenden Wollmilchsau
Der Begriff ist vielleicht nicht besonders elegant.
Aber ganz falsch ist er nicht.
Individuelle Möbel und Sonderanfertigungen. Lackierungen nach Wunsch. Echtholzfurniere. Verkleidungen. Ergänzungen. Anpassungen. Innenausbau. Trockenbauwände. Individuelle Lösungen direkt vor Ort.
Und ganz wichtig:
inklusive Montage.
Damit endet die Arbeit eben nicht am Möbel.
Wenn für eine gute Lösung noch eine Trockenbauwand entstehen muss, Technik verschwinden soll, eine Verkleidung gebraucht wird oder verschiedene Elemente sauber miteinander verbunden werden müssen, kann auch das Teil des Projekts sein.
Für uns ist genau das der große Vorteil:
Planung, Tischlerhandwerk, Innenausbau und Montage greifen ineinander.
Nicht erst das Möbel planen, danach einen Trockenbauer suchen, anschließend jemanden für die Anpassung und am Ende noch ein Montageteam koordinieren.
Sondern möglichst alles aus einer Hand – und am Ende auch optisch aus einem Guss.
Seit 2014 an unserer Seite
Otterpohl ist für uns kein Partner, den wir irgendwann für diese Webseite entdeckt haben.
Wir arbeiten bereits seit 2014 bei der Montage zusammen.
Und das ist für uns wesentlich mehr als eine kleine Fußnote.
Denn ein hochwertiges Möbel ist am Ende nur so gut wie seine Montage.
Saubere Fugen. Präzise Anschlüsse. Passleisten, die tatsächlich zur Wand passen. Fronten, die vernünftig eingestellt sind. Und Menschen vor Ort, die eine Situation nicht nur montieren, sondern auch handwerklich verstehen.
Gerade wenn Möbelbau, Tischlerarbeiten, Trockenbau und Innenausbau zusammenkommen, ist diese Erfahrung besonders wertvoll.
Nach so vielen Jahren weiß man ziemlich genau, mit wem man arbeitet.
Und bei einem Partner, den wir zu unseren Kunden schicken, finden wir das durchaus wichtig.
Farbe darf auch genau die Farbe sein
Bei manchen Projekten reichen drei ähnliche Farbtöne eben nicht.
Vielleicht soll ein Möbel exakt zu einer vorhandenen Wandfarbe passen. Zu einem anderen Möbel. Zu einem bestimmten Material. Oder Sie haben einfach diesen einen Farbton im Kopf und möchten genau den.
Dann ist „fast passend“ manchmal eben nicht passend genug.
Mit individuellen Lackierungen entstehen Möglichkeiten, die weit über eine normale Farbpalette hinausgehen.
Und genauso ist es bei den Materialien.
Manchmal ist eine hochwertige Dekoroberfläche genau richtig.
Manchmal soll es Lack sein.
Und manchmal möchte man eben echtes Holz – mit echter Maserung, echter Struktur und einem Echtholzfurnier, bei dem kein Möbelstück exakt aussieht wie das andere.
Das ist dann eben der echte Charakter eines Möbels. Nicht perfekt gleich – sondern genau deshalb besonders.
Manchmal gibt es die Idee schon – nur das Möbel noch nicht
Vielleicht haben Sie eine ziemlich genaue Vorstellung.
Vielleicht nur ein Foto, eine Skizze oder eine Idee, die Sie irgendwo gesehen haben.
Das reicht völlig.
Dann überlegen wir gemeinsam: Wie könnte man das umsetzen? Welche Proportionen passen? Welche Materialien? Lack oder Furnier? Welche Funktion soll das Möbel übernehmen? Und wie fügt sich alles in den Raum und in das ein, was bereits vorhanden ist?
Nicht jede gute Idee beginnt schließlich mit einer Artikelnummer.
Manchmal beginnt sie einfach im Kopf.
Aus der Idee wird eine Planung.
Aus der Planung ein Möbel.
Und irgendwann steht es tatsächlich da.
Nicht unbedingt easy to click & buy – aber am Ende trotzdem ziemlich easy to get.
Individuelle Planung kann viele Formen haben
Wir arbeiten bewusst mit unterschiedlichen Herstellern und Partnern.
CABINET, Rotpunkt und Febrü bieten bereits enorme Möglichkeiten, Möbel individuell zu planen. Maße, Farben, Materialien, Ausstattung und Funktionen können auf den Raum und auf die Menschen abgestimmt werden, die ihn später nutzen.
Mit Otterpohl erweitern wir diese Möglichkeiten um die handwerkliche Fertigung.
Ein Möbel nach eigenem Entwurf. Eine besondere Konstruktion. Eine individuelle Verkleidung. Eine Bank. Ein Tisch. Ein Wandpaneel. Eine spezielle Lackierung. Ein Echtholzfurnier. Oder einfach dieses eine Detail, das sich handwerklich besser lösen lässt.
Tischlerhandwerk ist damit für uns keine Alternative zu unserer Planung.
Es ist eine zusätzliche Möglichkeit innerhalb unserer Planung.
Nicht alles muss individuell gebaut werden
Auch das gehört für uns zu einer vernünftigen Beratung.
Nur weil ein Tischler etwas bauen kann, muss man nicht automatisch jedes Möbel individuell fertigen lassen.
Wenn ein hochwertiges Möbelsystem eine Aufgabe perfekt löst, nutzen wir dieses System.
Wenn eine individuelle Ergänzung sinnvoll ist, wird genau diese Ergänzung gebaut.
Und wenn die beste Lösung tatsächlich eine komplett freie Anfertigung ist, geht eben auch das.
Wir glauben nicht besonders an entweder oder.
Wir glauben eher daran, für jede Aufgabe die sinnvollste Lösung zu finden.
Ein Raum hört nicht am Möbel auf
Ein Interior-Projekt hält sich erstaunlich selten sauber an einzelne Gewerke.
Da steht vielleicht irgendwann das perfekte Möbel – aber daneben fehlt noch eine Wand. Eine Nische soll entstehen. Technik muss verschwinden. Eine Verkleidung wird gebraucht. Ein Übergang soll so aussehen, als wäre er von Anfang an genau so geplant gewesen.
Dann hilft es wenig, wenn jeder nur bis zur Grenze seines eigenen Gewerks schaut.
Wir möchten lieber, dass am Ende alles zusammenpasst.
Möbel, Wand, Verkleidung, Oberfläche und Montage sollen nicht wie fünf einzelne Entscheidungen aussehen, sondern wie eine einzige Planung.
Oder einfacher:
Alles aus einem Guss.
Der letzte Zentimeter kann ziemlich wichtig sein
Tischlerhandwerk zeigt seine Stärke übrigens nicht nur bei spektakulären Einzelstücken.
Manchmal geht es um Dinge, die auf einer Visualisierung kaum auffallen.
Eine Wand läuft etwas weg. Ein Boden ist nicht vollkommen eben. Ein vorhandenes Bauteil muss integriert werden. Ein Übergang braucht eine saubere Lösung.
Auf dem Bildschirm sieht so etwas meistens ziemlich einfach aus.
In einem echten Raum manchmal weniger.
Genau dort entscheidet sich häufig, ob etwas am Ende nur irgendwie passt – oder ob es wirklich so aussieht, als wäre es schon immer genau so gedacht gewesen.
Unser Anspruch ist deshalb ziemlich einfach:
Man soll am Ende nicht sehen, wo es kompliziert war.
Gute Planung endet nicht am Bildschirm
Wir planen, visualisieren, bemustern und verändern.
Aber irgendwann muss jede Idee den Bildschirm verlassen.
Dann gibt es echte Wände, echte Böden, echtes Material und meistens mindestens eine Kleinigkeit, die auf keiner Zeichnung so richtig auffallen wollte.
Deshalb gehören für uns Planung und Handwerk zusammen.
Denn am Ende interessiert niemanden, welche technische Herausforderung wir irgendwo lösen mussten.
Es soll funktionieren.
Es soll gut aussehen.
Und es soll möglichst so wirken, als wäre es genau so immer gedacht gewesen.
Handwerk darf trotzdem modern sein
Beim Wort Tischlerei denken manche immer noch an dunkles Holz, schwere Möbel und eine Hobelbank.
Kann alles schön sein.
Ist aber nur ein kleiner Ausschnitt dessen, was heute möglich ist.
Modernes Tischlerhandwerk kann genauso geradlinig, reduziert und architektonisch sein.
Lackierte Oberflächen. Echtholzfurniere. Moderne Plattenwerkstoffe. Kombinationen verschiedener Materialien. Filigrane Konstruktionen oder ganz bewusst massive Elemente.
Am Ende entscheidet nicht die Vorstellung davon, wie ein „Tischlermöbel“ auszusehen hat.
Sondern die Idee dahinter.
Und genau das finden wir spannend.
Eine kleine Idee bleibt trotzdem eine gute Idee
Es muss übrigens nicht immer gleich der komplette Innenausbau sein.
Vielleicht fehlt nur ein Tisch.
Ein Regal.
Eine Bank.
Eine besondere Ablage.
Oder dieses eine Möbel, das man weder online noch im Handel so findet, wie man es eigentlich gerne hätte.
Dann sprechen wir darüber.
Nicht jede gute Idee muss automatisch ein großes Projekt werden.
Manchmal ist ein einziges besonderes Möbel genau das, was einem Raum noch gefehlt hat.
Aus Rheda-Wiedenbrück. Und ziemlich nah dran.
Otterpohl kommt aus Rheda-Wiedenbrück.
Das ist für uns nicht nur geografisch angenehm.
Gerade bei individuellen Projekten sind kurze Wege und direkte Kommunikation ziemlich wertvoll.
Man kennt sich. Man spricht miteinander. Man kann Situationen gemeinsam beurteilen und Lösungen entwickeln.
Und wenn etwas vor Ort anders aussieht als auf dem Papier, braucht es nicht erst fünf E-Mails und drei Weiterleitungen.
Dann braucht es jemanden, der sich die Sache anschaut und sagt:
„Okay. Kriegen wir hin.“
Nach mehr als zehn Jahren gemeinsamer Projekte wissen wir ziemlich gut, was damit gemeint ist.
Planung trifft Handwerk
Am Ende geht es uns nicht darum, möglichst viele Marken und Partner auf unserer Webseite unterzubringen.
Es geht darum, möglichst viele gute Möglichkeiten zu haben.
Mit unseren Möbelpartnern können wir sehr individuell planen.
Mit Otterpohl kommen Tischlerhandwerk, freie Fertigung, individuelle Lackierungen, Echtholzfurniere, Innenausbau, Trockenbau und die handwerkliche Umsetzung inklusive Montage dazu.
Alles aus einer Hand. Und am Ende möglichst alles aus einem Guss.
Manchmal gibt es die passende Lösung schon.
Manchmal muss man sie planen.
Und manchmal muss man sie eben bauen.
Nicht immer easy to click & buy. Aber trotzdem easy to get.

View file

@ -90,7 +90,7 @@ Jedes Playlist-Item hat folgende Eigenschaften:
| `media_url` | URL (optional) | Alternative zu Upload: Externe Video-/Bild-URL. Wird bevorzugt wenn vorhanden. |
| `headline` | Text | Max. 40 Zeichen. Wird im Textfeld angezeigt. |
| `subline` | Text | Max. 80 Zeichen. Erklärender Text unter der Headline. |
| `duration_seconds` | Zahl | Anzeigedauer in Sekunden. Für Bilder: empfohlen 8–12 Sek. Für Videos: wird ignoriert, Dauer ergibt sich aus Videolänge. |
| `duration_seconds` | Zahl | Anzeigedauer in Sekunden, auch mit Nachkommastelle (z. B. `1.5`). Für Bilder: empfohlen 8–12 Sek. Für Videos: wird ignoriert, Dauer ergibt sich aus Videolänge. |
| `sort_order` | Zahl | Reihenfolge in der Playlist. Niedrigste Zahl zuerst. |
| `is_active` | Toggle | Aktiv/Inaktiv. Inaktive Items werden übersprungen. |

View file

@ -190,8 +190,10 @@
<flux:input wire:model="mediaHeadline" label="Überschrift" placeholder="z.B. Ihr Zuhause. Weltweit." />
<flux:input wire:model="mediaSubline" label="Unterzeile" placeholder="z.B. Beratung und Vermittlung." />
@if($mediaType === 'image')
<flux:input wire:model="mediaDuration" type="number" min="1" max="120" label="Dauer (Sekunden)"
description="Nur für Bilder – Videos spielen bis zum Ende." />
<flux:input wire:model="mediaDuration" type="text" inputmode="decimal" label="Dauer (Sekunden)"
placeholder="z. B. 10 oder 1,5"
description="Nur für Bilder – Videos spielen bis zum Ende. Halbe Sekunden sind möglich, z. B. 1,5." />
@error('mediaDuration') <span class="text-sm text-red-600">{{ $message }}</span> @enderror
@endif
<flux:checkbox wire:model="mediaIsActive" label="Aktiv" />
@endif

View file

@ -65,7 +65,7 @@
<div class="text-xs text-zinc-600 dark:text-zinc-400 space-x-4">
<span>{{ $item->content['subline'] ?? '' }}</span>
@if(($item->content['media_type'] ?? 'image') === 'image')
<span>{{ $item->content['duration_seconds'] ?? 10 }}s</span>
<span>{{ \App\Livewire\Admin\Cms\DisplayVersionEditor::formatDuration($item->content['duration_seconds'] ?? 10) }}s</span>
@endif
@if($formatStatus = $this->displayMediaFormatStatus($item->content['media_url'] ?? ''))
<span>{{ $formatStatus['description'] }}</span>

View file

@ -938,3 +938,72 @@ test('offer slide editor manages logo and brand text per slide', function () {
expect($item->content['brand_text'])->toBe('Musterstadt');
expect($item->content['brand_tagline'])->toBe('Beratung • Lieferung');
});
// ========================================
// Bilddauer mit Nachkommastelle (Nachtrag 14)
// ========================================
test('image duration accepts a comma decimal like 1,5 and stores it as seconds', function () {
$user = User::factory()->create();
$version = DisplayVersion::factory()->create(['type' => 'b2in']);
Livewire::actingAs($user)
->test(DisplayVersionEditor::class, ['displayVersion' => $version])
->call('openItemModal', null, 'media')
->set('mediaType', 'image')
->set('mediaUrl', '../assets/test.jpg')
->set('mediaDuration', '1,5')
->call('saveItem')
->assertHasNoErrors()
->assertSet('mediaDuration', '1,5');
$item = DisplayVersionItem::where('display_version_id', $version->id)->first();
expect($item->content['duration_seconds'])->toBe(1.5);
});
test('image duration accepts a dot decimal and whole numbers', function () {
expect(DisplayVersionEditor::parseDuration('1.5'))->toBe(1.5)
->and(DisplayVersionEditor::parseDuration('10'))->toBe(10.0)
->and(DisplayVersionEditor::parseDuration(' 2,25 '))->toBe(2.3)
->and(DisplayVersionEditor::parseDuration('0,4'))->toBeNull()
->and(DisplayVersionEditor::parseDuration('121'))->toBeNull()
->and(DisplayVersionEditor::parseDuration('abc'))->toBeNull()
->and(DisplayVersionEditor::parseDuration(''))->toBeNull();
expect(DisplayVersionEditor::formatDuration(10))->toBe('10')
->and(DisplayVersionEditor::formatDuration(1.5))->toBe('1,5')
->and(DisplayVersionEditor::formatDuration('7'))->toBe('7')
->and(DisplayVersionEditor::formatDuration(null))->toBe('10');
});
test('image duration below half a second is rejected with a message', function () {
$user = User::factory()->create();
$version = DisplayVersion::factory()->create(['type' => 'b2in']);
Livewire::actingAs($user)
->test(DisplayVersionEditor::class, ['displayVersion' => $version])
->call('openItemModal', null, 'media')
->set('mediaType', 'image')
->set('mediaUrl', '../assets/test.jpg')
->set('mediaDuration', '0,2')
->call('saveItem')
->assertHasErrors(['mediaDuration']);
expect(DisplayVersionItem::where('display_version_id', $version->id)->count())->toBe(0);
});
test('video items ignore the duration field', function () {
$user = User::factory()->create();
$version = DisplayVersion::factory()->create(['type' => 'b2in']);
Livewire::actingAs($user)
->test(DisplayVersionEditor::class, ['displayVersion' => $version])
->call('openItemModal', null, 'media')
->set('mediaType', 'video')
->set('mediaUrl', '../assets/test.mp4')
->set('mediaDuration', 'egal')
->call('saveItem')
->assertHasNoErrors();
expect(DisplayVersionItem::where('display_version_id', $version->id)->count())->toBe(1);
});