CMS: Standorte an der Marke, Seitenbaum mit Vorschau, Referenzen-Modul
Nachtrag 11: Herkunfts-Sektion liest Ort, Entfernung und Rolle aus der Markenverwaltung (drei neue Felder an cms_brands, BrandOriginSeeder), Kartenpositionen im Code, „Marken“ in der Sidebar. Nachtrag 12: Inhalte-Editor zeigt die Seiten der Website mit ihren Sektionen in Reihenfolge (config/cms_pages.php, CmsPageTree), Felder in Klartext, fehlende Sektionen aus der Sprachdatei anlegbar, Vorschau der echten Seite im iframe mit Sprung zur Sektion; Anker an allen lebenden Sektionen nachgezogen. Nachtrag 13: Referenzen als eigenes Modul (CmsReference, Portal-Seite, /referenzen mit Fallback auf die Markengalerien); Immobilien-Projekte aus Sidebar und CMS-Übersicht genommen, Route bleibt. Dazu .gitignore für _b2in_interior_shop/ und docs/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7953a8c9a1
commit
90aa079cf7
50 changed files with 2580 additions and 324 deletions
62
database/factories/CmsReferenceFactory.php
Normal file
62
database/factories/CmsReferenceFactory.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\CmsReference;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<CmsReference>
|
||||
*/
|
||||
class CmsReferenceFactory extends Factory
|
||||
{
|
||||
protected $model = CmsReference::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$title = fake()->unique()->words(3, true);
|
||||
|
||||
return [
|
||||
'title' => ['de' => ucfirst($title), 'en' => ucfirst($title)],
|
||||
'location' => fake()->city(),
|
||||
'year' => (string) fake()->numberBetween(2022, 2026),
|
||||
'description' => ['de' => fake()->paragraph(2), 'en' => fake()->paragraph(2)],
|
||||
'image' => 'referenzen/'.fake()->unique()->slug(2).'.webp',
|
||||
'gallery' => [],
|
||||
'brand_slugs' => [],
|
||||
'is_published' => true,
|
||||
'order' => fake()->numberBetween(1, 20),
|
||||
];
|
||||
}
|
||||
|
||||
public function published(): static
|
||||
{
|
||||
return $this->state(fn () => ['is_published' => true]);
|
||||
}
|
||||
|
||||
public function unpublished(): static
|
||||
{
|
||||
return $this->state(fn () => ['is_published' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $slugs
|
||||
*/
|
||||
public function forBrands(array $slugs): static
|
||||
{
|
||||
return $this->state(fn () => ['brand_slugs' => $slugs]);
|
||||
}
|
||||
|
||||
public function withGallery(int $count = 2): static
|
||||
{
|
||||
return $this->state(function () use ($count) {
|
||||
$items = [];
|
||||
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
$items[] = ['image' => 'referenzen/bild-'.$i.'.webp', 'caption' => 'Bild '.$i];
|
||||
}
|
||||
|
||||
return ['gallery' => $items];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Fertigungsort je Kernmarke (Nachtrag 11 zu Arbeitsplan 14.09.2026, Punkt 1.3).
|
||||
*
|
||||
* Die Herkunfts-Sektion „Aus der Region gefertigt“ holt ihre Standorte nicht mehr aus einem
|
||||
* JSON-Eintrag im generischen Inhalte-Editor, sondern aus der Markenverwaltung: Marcel pflegt
|
||||
* Ort, Entfernung und Rolle direkt bei der Marke. Die Kartenpositionen bleiben im Code.
|
||||
*
|
||||
* - `origin_city` Fertigungsort, z. B. „Bünde“ – leer bedeutet: Marke erscheint nicht in der Sektion
|
||||
* - `origin_distance` Entfernung ab Bielefeld als Text, z. B. „rund 30 km“
|
||||
* - `origin_role` Ein Satz zur Rolle des Standorts
|
||||
*
|
||||
* Alle drei sind übersetzbar wie die übrigen Textfelder der Marke.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cms_brands', function (Blueprint $table) {
|
||||
$table->json('origin_city')->nullable()->after('showroom');
|
||||
$table->json('origin_distance')->nullable()->after('origin_city');
|
||||
$table->json('origin_role')->nullable()->after('origin_distance');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cms_brands', function (Blueprint $table) {
|
||||
$table->dropColumn(['origin_city', 'origin_distance', 'origin_role']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Referenzprojekte der Interior-Welt (Nachtrag 13).
|
||||
*
|
||||
* AP5 hatte die Referenzen aus den Markengalerien zusammengesammelt, um keine zweite Pflegestelle
|
||||
* zu schaffen. Abstimmung 15.09.: Marcel soll Referenzen als eigene Einträge pflegen – ein
|
||||
* realisiertes Projekt hat mehr als ein Bild und mehr als eine Marke. Solange kein Eintrag
|
||||
* existiert, greift das Frontend weiter auf die Markengalerien zurück.
|
||||
*
|
||||
* - `title`, `description` übersetzbar
|
||||
* - `location`, `year` Ort und Jahr als Text („Bielefeld“, „2025“)
|
||||
* - `image` Titelbild (Dateiname aus der Medienbibliothek)
|
||||
* - `gallery` weitere Bilder mit Bildunterschrift
|
||||
* - `brand_slugs` beteiligte Kernmarken (Slugs aus cms_brands), verlinkt auf die Markenseiten
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cms_references', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->json('title');
|
||||
$table->string('location')->nullable();
|
||||
$table->string('year', 20)->nullable();
|
||||
$table->json('description')->nullable();
|
||||
$table->string('image')->nullable();
|
||||
$table->json('gallery')->nullable();
|
||||
$table->json('brand_slugs')->nullable();
|
||||
$table->boolean('is_published')->default(true);
|
||||
$table->integer('order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cms_references');
|
||||
}
|
||||
};
|
||||
87
database/seeders/BrandOriginSeeder.php
Normal file
87
database/seeders/BrandOriginSeeder.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\CmsBrand;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Trägt den Fertigungsort der Kernmarken nach (Nachtrag 11 zu Arbeitsplan 14.09.2026, Punkt 1.3).
|
||||
*
|
||||
* Die Herkunfts-Sektion „Aus der Region gefertigt“ liest Ort, Entfernung und Rolle aus der
|
||||
* Marke. {@see CmsBrandSeeder} bringt dieselben Werte für frische Installationen mit,
|
||||
* überspringt aber bereits angelegte Marken. Dieser Seeder fasst nur die drei Felder an und
|
||||
* auch nur bei Marken, deren Fertigungsort noch leer ist – ein im Admin eingetragener Ort
|
||||
* bleibt stehen. Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* Entfernungen gerundet, Straße ab Bielefeld Mitte. ⚠️ Rolle CABINET nach Kevins Verständnis
|
||||
* (Fertigung Köln, Beratung im Store Bielefeld) – von Marcel bestätigen lassen.
|
||||
*
|
||||
* php artisan db:seed --class=BrandOriginSeeder
|
||||
*/
|
||||
class BrandOriginSeeder extends Seeder
|
||||
{
|
||||
/** @var array<string, array{origin_city: array<string, string>, origin_distance: array<string, string>, origin_role: array<string, string>}> */
|
||||
private const ORIGINS = [
|
||||
'cabinet' => [
|
||||
'origin_city' => ['de' => 'Köln', 'en' => 'Cologne'],
|
||||
'origin_distance' => ['de' => 'rund 200 km', 'en' => 'about 200 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Gefertigt in der Kölner Manufaktur, beraten und geplant im CABINET Store Bielefeld.',
|
||||
'en' => 'Made in the Cologne manufactory, advised and planned at the CABINET Store Bielefeld.',
|
||||
],
|
||||
],
|
||||
'rotpunkt' => [
|
||||
'origin_city' => ['de' => 'Bünde', 'en' => 'Bünde'],
|
||||
'origin_distance' => ['de' => 'rund 30 km', 'en' => 'about 30 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Küchen aus eigener Fertigung in Bünde – geplant bei uns im Küchenstudio.',
|
||||
'en' => 'Kitchens from their own production in Bünde – planned with us in the kitchen studio.',
|
||||
],
|
||||
],
|
||||
'februe' => [
|
||||
'origin_city' => ['de' => 'Herford', 'en' => 'Herford'],
|
||||
'origin_distance' => ['de' => 'rund 20 km', 'en' => 'about 20 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Büro- und Objektmöbel aus eigener Fertigung in Herford.',
|
||||
'en' => 'Office and contract furniture from their own production in Herford.',
|
||||
],
|
||||
],
|
||||
'tischlerei' => [
|
||||
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
|
||||
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
|
||||
'en' => 'Our workshop for assembly, custom solutions and finishing.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$set = 0;
|
||||
$kept = 0;
|
||||
|
||||
foreach (self::ORIGINS as $slug => $origin) {
|
||||
$brand = CmsBrand::query()->where('slug', $slug)->first();
|
||||
|
||||
if (! $brand) {
|
||||
$this->command?->warn("Marke „{$slug}“ nicht gefunden – übersprungen.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trim((string) $brand->getTranslation('origin_city', 'de', false)) !== '') {
|
||||
$kept++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$brand->fill($origin);
|
||||
$brand->save();
|
||||
$set++;
|
||||
}
|
||||
|
||||
$this->command?->info("Fertigungsorte gesetzt: {$set}, vorhandene behalten: {$kept}.");
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ use Illuminate\Database\Seeder;
|
|||
* Partnerbetriebs gehört laut Plan auf die Detailseite, nicht in die Marke.
|
||||
*
|
||||
* Bestehende Marken werden nicht überschrieben – Marcel pflegt sie im Admin. Für bereits
|
||||
* angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach.
|
||||
* angelegte Marken setzt {@see BrandLogoSeeder} die Logos nach und {@see BrandOriginSeeder}
|
||||
* den Fertigungsort für die Herkunfts-Sektion (Nachtrag 11).
|
||||
*
|
||||
* Stimmungsbilder und Galerien fehlen als Zulieferung und bleiben leer; die Kacheln und
|
||||
* Detailseiten zeigen so lange einen Platzhalter.
|
||||
|
|
@ -77,6 +78,12 @@ Experience the possibilities in our Bielefeld showroom.',
|
|||
['title' => 'Montage durch Fachleute', 'description' => 'Wir liefern und montieren – bis zum letzten Detail.'],
|
||||
],
|
||||
'showroom' => 'Showroom Bielefeld',
|
||||
'origin_city' => ['de' => 'Köln', 'en' => 'Cologne'],
|
||||
'origin_distance' => ['de' => 'rund 200 km', 'en' => 'about 200 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Gefertigt in der Kölner Manufaktur, beraten und geplant im CABINET Store Bielefeld.',
|
||||
'en' => 'Made in the Cologne manufactory, advised and planned at the CABINET Store Bielefeld.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
[
|
||||
|
|
@ -112,6 +119,12 @@ Consultation and planning in our kitchen studio.',
|
|||
['title' => 'Lieferung und Aufbau', 'description' => 'Koordiniert aus einer Hand, inklusive Anschluss.'],
|
||||
],
|
||||
'showroom' => 'Küchenstudio',
|
||||
'origin_city' => ['de' => 'Bünde', 'en' => 'Bünde'],
|
||||
'origin_distance' => ['de' => 'rund 30 km', 'en' => 'about 30 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Küchen aus eigener Fertigung in Bünde – geplant bei uns im Küchenstudio.',
|
||||
'en' => 'Kitchens from their own production in Bünde – planned with us in the kitchen studio.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
[
|
||||
|
|
@ -143,6 +156,12 @@ Consultation and planning in our kitchen studio.',
|
|||
['title' => 'Akustik und Ordnung', 'description' => 'Stauraum und Raumgliederung, die Konzentration ermöglichen.'],
|
||||
],
|
||||
'showroom' => '',
|
||||
'origin_city' => ['de' => 'Herford', 'en' => 'Herford'],
|
||||
'origin_distance' => ['de' => 'rund 20 km', 'en' => 'about 20 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Büro- und Objektmöbel aus eigener Fertigung in Herford.',
|
||||
'en' => 'Office and contract furniture from their own production in Herford.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
[
|
||||
|
|
@ -223,6 +242,12 @@ It complements CABINET and Rotpunkt wherever making to drawing is required: asse
|
|||
],
|
||||
],
|
||||
'showroom' => '',
|
||||
'origin_city' => ['de' => 'Rheda-Wiedenbrück', 'en' => 'Rheda-Wiedenbrück'],
|
||||
'origin_distance' => ['de' => 'rund 35 km', 'en' => 'about 35 km'],
|
||||
'origin_role' => [
|
||||
'de' => 'Unsere Werkstatt für Aufbau, Sonderlösungen und Veredelung.',
|
||||
'en' => 'Our workshop for assembly, custom solutions and finishing.',
|
||||
],
|
||||
'is_published' => true,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,13 +2,21 @@
|
|||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use FluxCms\Core\Models\CmsContent;
|
||||
use FluxCms\Core\Services\CmsContentService;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Herkunfts-Sektion „Aus der Region gefertigt“ ins CMS legen (Arbeitsplan 14.09.2026, Punkt 1.3).
|
||||
*
|
||||
* Legt die Sektion nur an, wenn sie fehlt. Ein bestehender Eintrag bleibt unangetastet,
|
||||
* damit Korrekturen aus dem Admin (Entfernungen, Ablauf CABINET) nicht verloren gehen.
|
||||
* Legt die Sektion nur an, wenn sie fehlt. Ein bestehender Eintrag bleibt inhaltlich unangetastet,
|
||||
* damit Korrekturen aus dem Admin (Ablauf CABINET) nicht verloren gehen.
|
||||
*
|
||||
* Einzige Ausnahme (Nachtrag 11): Der Schlüssel `locations` stammt aus dem ersten Stand, als die
|
||||
* Standorte noch im Sektions-JSON lagen. Seitdem kommen sie aus den Marken
|
||||
* ({@see BrandOriginSeeder}), das Frontend liest den Schlüssel nicht mehr. Er wird aus einem
|
||||
* bestehenden Eintrag entfernt, damit er im Inhalte-Editor nicht als Pflegefeld auftaucht.
|
||||
* Mehrfaches Ausführen ist unbedenklich.
|
||||
*
|
||||
* php artisan db:seed --class=OriginSectionCmsSeeder
|
||||
*/
|
||||
|
|
@ -24,6 +32,38 @@ class OriginSectionCmsSeeder extends Seeder
|
|||
$seeder->onlySections = self::SECTIONS;
|
||||
$seeder->run();
|
||||
|
||||
$this->dropLegacyLocations();
|
||||
|
||||
$this->command?->info('Herkunfts-Sektion: angelegt, falls sie fehlte.');
|
||||
}
|
||||
|
||||
private function dropLegacyLocations(): void
|
||||
{
|
||||
$row = CmsContent::query()->where('group', 'home')->where('key', 'origin_section')->first();
|
||||
|
||||
if (! $row) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $row->getTranslations('value');
|
||||
$changed = false;
|
||||
|
||||
foreach ($value as $locale => $section) {
|
||||
if (is_array($section) && array_key_exists('locations', $section)) {
|
||||
unset($section['locations']);
|
||||
$value[$locale] = $section;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$row->value = $value;
|
||||
$row->save();
|
||||
app(CmsContentService::class)->clearCache('home');
|
||||
|
||||
$this->command?->info('Herkunfts-Sektion: veralteten Schlüssel „locations“ entfernt – die Standorte kommen aus den Marken.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue