23-01-2026
This commit is contained in:
parent
07959c0ba2
commit
854ce02bf6
166 changed files with 32909 additions and 1262 deletions
208
resources/views/livewire/admin/hubs/index.blade.php
Normal file
208
resources/views/livewire/admin/hubs/index.blade.php
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<?php
|
||||
|
||||
use function Livewire\Volt\{state, computed};
|
||||
|
||||
state(['search' => '']);
|
||||
|
||||
$hubs = computed(function () {
|
||||
return \App\Models\Hub::with(['locations', 'partners'])
|
||||
->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"))
|
||||
->get()
|
||||
->map(function ($hub) {
|
||||
$retailers = $hub->partners()->where('type', 'Retailer')->count();
|
||||
$brokers = $hub->partners()->where('type', 'Estate-Agent')->count();
|
||||
|
||||
return [
|
||||
'id' => $hub->id,
|
||||
'name' => $hub->name,
|
||||
'slug' => $hub->slug,
|
||||
'keyvisual' => $hub->keyvisual_url ?? '/images/default-keyvisual.jpg',
|
||||
'emblem' => $hub->emblem_url,
|
||||
'is_active' => $hub->is_active,
|
||||
'locations_count' => $hub->locations->count(),
|
||||
'retailers_count' => $retailers,
|
||||
'brokers_count' => $brokers,
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
?>
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Header mit Suche --}}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Hub-Verwaltung') }} (in Entwicklung)</flux:heading>
|
||||
<flux:subheading>{{ __('Regionale Marktplätze & Postleitzahlen-Zuordnung') }}</flux:subheading>
|
||||
</div>
|
||||
<flux:button variant="primary" icon="plus" :href="route('admin.hubs.create')" wire:navigate>
|
||||
{{ __('Neuer Hub') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
{{-- Info-Banner --}}
|
||||
<flux:card class="p-4 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800">
|
||||
<div class="flex items-start gap-3">
|
||||
<flux:icon.information-circle class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold text-blue-900 dark:text-blue-100">
|
||||
{{ __('Konzept: Heimatgefühl + Weltmarkt') }}
|
||||
</div>
|
||||
<div class="text-xs text-blue-700 dark:text-blue-300 mt-1">
|
||||
{{ __('Jeder Hub filtert lokale Händler heraus, behält aber globale Hersteller bei. Kunden fühlen sich "zuhause" mit Zugriff auf das volle Sortiment.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{-- Suchfeld --}}
|
||||
<flux:input
|
||||
wire:model.live.debounce="search"
|
||||
placeholder="{{ __('Hub suchen...') }}"
|
||||
icon="magnifying-glass"
|
||||
/>
|
||||
|
||||
{{-- Hubs als Karten-Grid --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@forelse($this->hubs as $hub)
|
||||
<a href="{{ route('admin.hubs.edit', $hub['id']) }}"
|
||||
wire:navigate
|
||||
class="block relative overflow-hidden rounded-lg border border-zinc-200 dark:border-zinc-700 hover:shadow-lg hover:border-accent-300 dark:hover:border-accent-700 transition-all group">
|
||||
|
||||
{{-- Keyvisual Hintergrund --}}
|
||||
<div class="relative h-48 bg-cover bg-center"
|
||||
style="background-image: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.6)), url('{{ $hub['keyvisual'] }}')">
|
||||
|
||||
{{-- Wappen oben rechts --}}
|
||||
@if($hub['emblem'])
|
||||
<div class="absolute top-3 right-3 w-12 h-12 bg-white rounded-full p-2 shadow-lg">
|
||||
<img src="{{ $hub['emblem'] }}" alt="Wappen" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Status Badge oben links --}}
|
||||
<div class="absolute top-3 left-3">
|
||||
<flux:badge :color="$hub['is_active'] ? 'green' : 'zinc'" size="sm">
|
||||
{{ $hub['is_active'] ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
|
||||
{{-- Hub Name --}}
|
||||
<div class="absolute bottom-0 left-0 right-0 p-4">
|
||||
<h3 class="text-2xl font-bold text-white group-hover:text-accent-200 transition-colors">
|
||||
{{ $hub['name'] }}
|
||||
</h3>
|
||||
<p class="text-sm text-zinc-200">{{ $hub['slug'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Metriken --}}
|
||||
<div class="p-4 bg-white dark:bg-zinc-900">
|
||||
<div class="grid grid-cols-3 gap-2 text-center">
|
||||
<div class="p-2 bg-zinc-50 dark:bg-zinc-800 rounded group-hover:bg-accent-50 dark:group-hover:bg-accent-900/20 transition-colors">
|
||||
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $hub['locations_count'] }}
|
||||
</div>
|
||||
<div class="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('PLZs') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 bg-zinc-50 dark:bg-zinc-800 rounded group-hover:bg-accent-50 dark:group-hover:bg-accent-900/20 transition-colors">
|
||||
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $hub['retailers_count'] }}
|
||||
</div>
|
||||
<div class="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Händler') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 bg-zinc-50 dark:bg-zinc-800 rounded group-hover:bg-accent-50 dark:group-hover:bg-accent-900/20 transition-colors">
|
||||
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $hub['brokers_count'] }}
|
||||
</div>
|
||||
<div class="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Makler') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@empty
|
||||
<div class="col-span-3">
|
||||
<flux:card class="p-12">
|
||||
<div class="text-center">
|
||||
<flux:icon.map class="w-16 h-16 text-zinc-400 mx-auto mb-4" />
|
||||
<flux:heading size="lg" class="mb-2">{{ __('Noch keine Hubs angelegt') }}</flux:heading>
|
||||
<flux:subheading class="mb-4">
|
||||
{{ __('Erstellen Sie Ihren ersten regionalen Marktplatz') }}
|
||||
</flux:subheading>
|
||||
<flux:button variant="primary" icon="plus" :href="route('admin.hubs.create')" wire:navigate>
|
||||
{{ __('Ersten Hub erstellen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{-- Statistik-Übersicht --}}
|
||||
@if($this->hubs->isNotEmpty())
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mt-8">
|
||||
<flux:card class="p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-3 bg-blue-100 dark:bg-blue-900/20 rounded-lg">
|
||||
<flux:icon.map class="w-6 h-6 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $this->hubs->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">{{ __('Gesamt Hubs') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card class="p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-3 bg-green-100 dark:bg-green-900/20 rounded-lg">
|
||||
<flux:icon.check-circle class="w-6 h-6 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $this->hubs->where('is_active', true)->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">{{ __('Aktive Hubs') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card class="p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-3 bg-purple-100 dark:bg-purple-900/20 rounded-lg">
|
||||
<flux:icon.map-pin class="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $this->hubs->sum('locations_count') }}
|
||||
</div>
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">{{ __('PLZ-Gebiete') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card class="p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-3 bg-orange-100 dark:bg-orange-900/20 rounded-lg">
|
||||
<flux:icon.building-storefront class="w-6 h-6 text-orange-600 dark:text-orange-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $this->hubs->sum('retailers_count') }}
|
||||
</div>
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">{{ __('Partner gesamt') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
482
resources/views/livewire/admin/hubs/manage.blade.php
Normal file
482
resources/views/livewire/admin/hubs/manage.blade.php
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
<?php
|
||||
|
||||
use function Livewire\Volt\{state, mount, computed};
|
||||
|
||||
state([
|
||||
'hubId' => null,
|
||||
'activeTab' => 'identity',
|
||||
'importMethod' => 'single',
|
||||
|
||||
// Identität
|
||||
'name' => '',
|
||||
'slug' => '',
|
||||
'keyvisual' => null,
|
||||
'emblem' => null,
|
||||
'is_active' => false,
|
||||
|
||||
// PLZ-Management
|
||||
'zipSearch' => '',
|
||||
'newZipCode' => '',
|
||||
'newCityName' => '',
|
||||
'rangeStart' => '',
|
||||
'rangeEnd' => '',
|
||||
'csvFile' => null,
|
||||
]);
|
||||
|
||||
mount(function ($hubId = null) {
|
||||
if ($hubId) {
|
||||
$hub = \App\Models\Hub::findOrFail($hubId);
|
||||
$this->hubId = $hub->id;
|
||||
$this->name = $hub->name;
|
||||
$this->slug = $hub->slug;
|
||||
$this->is_active = $hub->is_active;
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-generate slug from name
|
||||
$updatedName = function ($value) {
|
||||
if (!$this->hubId) { // Only auto-generate for new hubs
|
||||
$this->slug = \Illuminate\Support\Str::slug($value);
|
||||
}
|
||||
};
|
||||
|
||||
$locations = computed(function () {
|
||||
if (!$this->hubId) return collect();
|
||||
|
||||
return \App\Models\HubLocation::where('hub_id', $this->hubId)
|
||||
->when($this->zipSearch, fn($q) => $q->where('zip_code', 'like', "%{$this->zipSearch}%")
|
||||
->orWhere('city_name', 'like', "%{$this->zipSearch}%"))
|
||||
->orderBy('zip_code')
|
||||
->paginate(50);
|
||||
});
|
||||
|
||||
$partners = computed(function () {
|
||||
if (!$this->hubId) return collect();
|
||||
|
||||
return \App\Models\Partner::where('hub_id', $this->hubId)
|
||||
->get();
|
||||
});
|
||||
|
||||
// Dummy save function
|
||||
$save = function () {
|
||||
// In production: Validation und Speicherung
|
||||
session()->flash('message', __('Hub gespeichert (Dummy-Funktion)'));
|
||||
};
|
||||
|
||||
// Dummy functions für PLZ-Management
|
||||
$addSingleZip = function () {
|
||||
session()->flash('message', __('PLZ hinzugefügt (Dummy-Funktion)'));
|
||||
};
|
||||
|
||||
$addZipRange = function () {
|
||||
session()->flash('message', __('PLZ-Bereich importiert (Dummy-Funktion)'));
|
||||
};
|
||||
|
||||
$importCsv = function () {
|
||||
session()->flash('message', __('CSV importiert (Dummy-Funktion)'));
|
||||
};
|
||||
|
||||
$deleteLocation = function ($id) {
|
||||
session()->flash('message', __('PLZ gelöscht (Dummy-Funktion)'));
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Header --}}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">
|
||||
{{ $hubId ? __('Hub bearbeiten') : __('Neuer Hub') }} (in Entwicklung)
|
||||
</flux:heading>
|
||||
<flux:subheading>{{ $name ?: __('Regionalen Marktplatz konfigurieren') }}</flux:subheading>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<flux:button variant="ghost" :href="route('admin.hubs.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
<flux:button variant="primary" icon="check" wire:click="save">
|
||||
{{ __('Speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Flash Message --}}
|
||||
@if (session()->has('message'))
|
||||
<flux:card class="p-4 bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<flux:icon.check-circle class="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
<span class="text-sm text-green-900 dark:text-green-100">{{ session('message') }}</span>
|
||||
</div>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
{{-- Tabs --}}
|
||||
<flux:tabs wire:model.live="activeTab" variant="segmented">
|
||||
<flux:tab name="identity" icon="identification">{{ __('Identität & Design') }}</flux:tab>
|
||||
<flux:tab name="geography" icon="map">{{ __('Geografie & PLZ') }}</flux:tab>
|
||||
<flux:tab name="partners" icon="user-group">{{ __('Partner-Monitor') }}</flux:tab>
|
||||
</flux:tabs>
|
||||
|
||||
{{-- TAB 1: Identität & Design --}}
|
||||
@if($activeTab === 'identity')
|
||||
<flux:card class="p-6 space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{{-- Basis-Informationen --}}
|
||||
<div class="space-y-6">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Hub-Name') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model.live="name" placeholder="Ostwestfalen-Lippe" />
|
||||
<flux:description>{{ __('Wird dem Kunden angezeigt, z.B. "Region Ostwestfalen-Lippe"') }}</flux:description>
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('URL-Slug') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="slug" placeholder="owl" />
|
||||
<flux:description>{{ __('Für saubere URLs, z.B. b2in.de/region/owl') }}</flux:description>
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Status') }}</flux:label>
|
||||
<flux:checkbox wire:model="is_active">
|
||||
{{ __('Hub ist aktiv und für Kunden sichtbar') }}
|
||||
</flux:checkbox>
|
||||
<flux:description>
|
||||
{{ __('Inaktive Hubs sind im "Coming Soon"-Modus') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</div>
|
||||
|
||||
{{-- Vorschau --}}
|
||||
<div class="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg">
|
||||
<h4 class="font-semibold mb-3 text-zinc-900 dark:text-zinc-100">{{ __('Vorschau: Kunden-Landingpage') }}</h4>
|
||||
<div class="relative h-48 rounded-lg overflow-hidden bg-zinc-200 dark:bg-zinc-700 shadow-lg">
|
||||
@if($keyvisual)
|
||||
<img src="{{ $keyvisual }}" class="w-full h-full object-cover" alt="Keyvisual" />
|
||||
@else
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<flux:icon.photo class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-sm text-zinc-500">{{ __('Keyvisual hochladen') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/80 to-transparent">
|
||||
<h3 class="text-xl font-bold text-white">
|
||||
{{ $name ?: __('Ihr Hub-Name') }}
|
||||
</h3>
|
||||
<p class="text-sm text-zinc-200">
|
||||
{{ __('Die besten Marken Europas, von Händlern aus Ihrer Nachbarschaft') }}
|
||||
</p>
|
||||
</div>
|
||||
@if($emblem)
|
||||
<div class="absolute top-3 right-3 w-12 h-12 bg-white rounded-full p-2 shadow-lg">
|
||||
<img src="{{ $emblem }}" alt="Emblem" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<flux:separator />
|
||||
|
||||
{{-- Keyvisual Upload --}}
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Keyvisual (Hintergrundbild)') }}</flux:label>
|
||||
{{-- <flux:input type="file" wire:model="keyvisual" accept="image/*" /> --}}
|
||||
<flux:description>
|
||||
{{ __('Atmosphärisches Regionalbild für emotionalen Einstieg') }}
|
||||
<br>
|
||||
<span class="text-xs">
|
||||
{{ __('Beispiele: Hermannsdenkmal (OWL), Skyline (Frankfurt), Hafen (Hamburg)') }}
|
||||
• {{ __('Empfohlen: 1920x800px, max. 2MB') }}
|
||||
</span>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
|
||||
{{-- Wappen Upload --}}
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Wappen / Emblem') }}</flux:label>
|
||||
{{-- <flux:input type="file" wire:model="emblem" accept="image/*" /> --}}
|
||||
<flux:description>
|
||||
{{ __('Offizielles Wappen oder Logo der Region für Vertrauen & Offizialität') }}
|
||||
<br>
|
||||
<span class="text-xs">
|
||||
{{ __('Beispiele: Sparrenburg-Logo, Landeswappen') }}
|
||||
• {{ __('Empfohlen: 256x256px, PNG mit transparentem Hintergrund') }}
|
||||
</span>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
{{-- TAB 2: Geografie & PLZ --}}
|
||||
@if($activeTab === 'geography')
|
||||
<div class="space-y-6">
|
||||
|
||||
{{-- Info-Box --}}
|
||||
<flux:card class="p-4 bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800">
|
||||
<div class="flex items-start gap-3">
|
||||
<flux:icon.light-bulb class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold text-yellow-900 dark:text-yellow-100">
|
||||
{{ __('Die Mapping-Engine') }}
|
||||
</div>
|
||||
<div class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
|
||||
{{ __('Hier ordnen Sie Postleitzahlen diesem Hub zu. Gibt ein Kunde seine PLZ ein, wird er automatisch diesem regionalen Marktplatz zugewiesen und sieht lokale Händler.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{-- PLZ-Import Tools --}}
|
||||
<flux:card class="p-6">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Postleitzahlen hinzufügen') }}</flux:heading>
|
||||
|
||||
<flux:tabs wire:model.live="importMethod" variant="segmented">
|
||||
<flux:tab name="single" icon="plus">{{ __('Einzeln') }}</flux:tab>
|
||||
<flux:tab name="range" icon="arrows-right-left">{{ __('Bereich') }}</flux:tab>
|
||||
<flux:tab name="csv" icon="document">{{ __('CSV-Import') }}</flux:tab>
|
||||
</flux:tabs>
|
||||
|
||||
<div class="mt-6">
|
||||
{{-- Einzelne PLZ --}}
|
||||
@if($importMethod === 'single')
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Postleitzahl') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="newZipCode" placeholder="33602" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Stadt') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="newCityName" placeholder="Bielefeld" />
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:button wire:click="addSingleZip" icon="plus">
|
||||
{{ __('PLZ hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- PLZ-Bereich --}}
|
||||
@if($importMethod === 'range')
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Von PLZ') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="rangeStart" placeholder="33000" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Bis PLZ') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="rangeEnd" placeholder="33999" />
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:button wire:click="addZipRange" icon="arrows-right-left">
|
||||
{{ __('Bereich importieren') }}
|
||||
</flux:button>
|
||||
<flux:description>
|
||||
⚠️ {{ __('Generiert automatisch alle PLZs im Bereich. Kann bei großen Bereichen mehrere Minuten dauern!') }}
|
||||
</flux:description>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- CSV-Import --}}
|
||||
@if($importMethod === 'csv')
|
||||
<div class="space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('CSV-Datei') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input type="file" wire:model="csvFile" accept=".csv" />
|
||||
<flux:description>
|
||||
{{ __('Format: PLZ,Stadt (eine Zeile pro Eintrag)') }}
|
||||
<br>
|
||||
<span class="text-xs">{{ __('Beispiel:') }}</span>
|
||||
<code class="text-xs bg-zinc-100 dark:bg-zinc-800 px-2 py-1 rounded">
|
||||
33602,Bielefeld<br>33603,Bielefeld<br>33604,Bielefeld
|
||||
</code>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
<flux:button wire:click="importCsv" icon="arrow-up-tray">
|
||||
{{ __('CSV importieren') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{-- PLZ-Liste --}}
|
||||
<flux:card>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<flux:heading size="lg">
|
||||
{{ __('Zugeordnete Postleitzahlen') }}
|
||||
@if($hubId)
|
||||
<span class="text-sm font-normal text-zinc-600 dark:text-zinc-400">
|
||||
({{ $this->locations->total() }} {{ __('gesamt') }})
|
||||
</span>
|
||||
@endif
|
||||
</flux:heading>
|
||||
<flux:input
|
||||
wire:model.live.debounce="zipSearch"
|
||||
placeholder="{{ __('PLZ oder Stadt suchen...') }}"
|
||||
icon="magnifying-glass"
|
||||
class="w-64"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if($hubId)
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('PLZ') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Stadt') }}</flux:table.column>
|
||||
<flux:table.column class="text-right w-32">{{ __('Aktion') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@forelse($this->locations as $location)
|
||||
<flux:table.row :key="$location->id">
|
||||
<flux:table.cell>
|
||||
<span class="font-mono">{{ $location->zip_code }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>{{ $location->city_name }}</flux:table.cell>
|
||||
<flux:table.cell class="text-right">
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon="trash"
|
||||
wire:click="deleteLocation({{ $location->id }})"
|
||||
/>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="3" class="text-center py-8">
|
||||
<flux:icon.map-pin class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Noch keine PLZs zugeordnet') }}
|
||||
</p>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
|
||||
{{-- Pagination --}}
|
||||
@if($this->locations->hasPages())
|
||||
<div class="mt-4 border-t border-zinc-200 dark:border-zinc-700 pt-4">
|
||||
{{ $this->locations->links() }}
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="text-center py-8 text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Speichern Sie zuerst den Hub, um PLZs hinzuzufügen') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- TAB 3: Partner-Monitor --}}
|
||||
@if($activeTab === 'partners')
|
||||
<flux:card class="p-6">
|
||||
<flux:heading size="lg" class="mb-4">
|
||||
{{ __('Partner in diesem Hub') }}
|
||||
@if($hubId)
|
||||
<span class="text-sm font-normal text-zinc-600 dark:text-zinc-400">
|
||||
({{ $this->partners->count() }} {{ __('Partner') }})
|
||||
</span>
|
||||
@endif
|
||||
</flux:heading>
|
||||
|
||||
{{-- Info --}}
|
||||
<flux:card class="p-4 mb-4 bg-zinc-50 dark:bg-zinc-800 border-0">
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<strong>{{ __('Logik:') }}</strong>
|
||||
<ul class="list-disc ml-5 mt-2 space-y-1">
|
||||
<li>{{ __('Händler (Retailer) → Werden diesem Hub fest zugeordnet') }}</li>
|
||||
<li>{{ __('Hersteller (Manufacturer) → Sind "Hub-agnostisch", in allen Hubs sichtbar') }}</li>
|
||||
<li>{{ __('Makler (Estate-Agent) → Werden Hub zugeordnet für regionale Kundenakquise') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
@if($hubId)
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Name') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Typ') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Stadt') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Lieferradius') }}</flux:table.column>
|
||||
<flux:table.column class="text-center">{{ __('Status') }}</flux:table.column>
|
||||
<flux:table.column class="text-right">{{ __('Aktion') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@forelse($this->partners as $partner)
|
||||
<flux:table.row :key="$partner->id">
|
||||
<flux:table.cell>
|
||||
<div class="font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $partner->company_name }}
|
||||
</div>
|
||||
@if($partner->display_name && $partner->display_name !== $partner->company_name)
|
||||
<div class="text-xs text-zinc-500">{{ $partner->display_name }}</div>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@php
|
||||
$typeColors = [
|
||||
'Retailer' => 'blue',
|
||||
'Manufacturer' => 'purple',
|
||||
'Estate-Agent' => 'green',
|
||||
];
|
||||
@endphp
|
||||
<flux:badge :color="$typeColors[$partner->type] ?? 'zinc'" size="sm">
|
||||
{{ $partner->type }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
{{ $partner->city ?? '-' }}
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($partner->delivery_radius_km)
|
||||
<span class="text-sm">{{ $partner->delivery_radius_km }} km</span>
|
||||
@else
|
||||
<span class="text-sm text-zinc-400">-</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="text-center">
|
||||
<flux:badge :color="$partner->is_active ? 'green' : 'zinc'" size="sm">
|
||||
{{ $partner->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="text-right">
|
||||
<flux:button variant="ghost" size="sm" icon="eye">
|
||||
{{ __('Details') }}
|
||||
</flux:button>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="6" class="text-center py-8">
|
||||
<flux:icon.user-group class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Noch keine Partner in diesem Hub') }}
|
||||
</p>
|
||||
<p class="text-xs text-zinc-500 mt-2">
|
||||
{{ __('Partner werden automatisch beim Onboarding zugeordnet (basierend auf PLZ)') }}
|
||||
</p>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
@else
|
||||
<div class="text-center py-8 text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Speichern Sie zuerst den Hub, um Partner-Zuordnungen zu sehen') }}
|
||||
</div>
|
||||
@endif
|
||||
</flux:card>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue