mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-app.git
synced 2025-12-22 02:00:15 +00:00
✨ Add CRUD support for Cities and Venues
This commit is contained in:
88
resources/views/livewire/cities/create.blade.php
Normal file
88
resources/views/livewire/cities/create.blade.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
use App\Models\City;
|
||||
use App\Models\Country;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public string $name = '';
|
||||
public ?int $country_id = null;
|
||||
public float $latitude = 0;
|
||||
public float $longitude = 0;
|
||||
public ?int $population = null;
|
||||
public ?string $population_date = null;
|
||||
|
||||
public function createCity(): void
|
||||
{
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255', 'unique:cities,name'],
|
||||
'country_id' => ['required', 'exists:countries,id'],
|
||||
'latitude' => ['required', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['required', 'numeric', 'between:-180,180'],
|
||||
'population' => ['nullable', 'integer', 'min:0'],
|
||||
'population_date' => ['nullable', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$validated['slug'] = str($validated['name'])->slug();
|
||||
$validated['created_by'] = auth()->id();
|
||||
|
||||
$city = City::create($validated);
|
||||
|
||||
session()->flash('status', __('City successfully created!'));
|
||||
|
||||
$this->redirect(route_with_country('cities.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'countries' => Country::query()->orderBy('name')->get(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('Create City') }}</flux:heading>
|
||||
</div>
|
||||
|
||||
<form wire:submit="createCity" class="space-y-8">
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Basic Information') }}</flux:legend>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:input label="{{ __('Name') }}" wire:model="name" required />
|
||||
|
||||
<flux:select label="{{ __('Country') }}" wire:model="country_id" required>
|
||||
<option value="">{{ __('Select a country') }}</option>
|
||||
@foreach($countries as $country)
|
||||
<option value="{{ $country->id }}">{{ $country->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Coordinates') }}</flux:legend>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-6">
|
||||
<flux:input label="{{ __('Latitude') }}" type="number" step="any" wire:model="latitude" required />
|
||||
<flux:input label="{{ __('Longitude') }}" type="number" step="any" wire:model="longitude" required />
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Demographics') }}</flux:legend>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-6">
|
||||
<flux:input label="{{ __('Population') }}" type="number" wire:model="population" />
|
||||
<flux:input label="{{ __('Population Date') }}" wire:model="population_date" placeholder="e.g. 2024" />
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<flux:button type="submit" variant="primary">{{ __('Create City') }}</flux:button>
|
||||
<flux:button :href="route_with_country('cities.index')" variant="ghost">{{ __('Cancel') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
99
resources/views/livewire/cities/edit.blade.php
Normal file
99
resources/views/livewire/cities/edit.blade.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
use App\Models\City;
|
||||
use App\Models\Country;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public City $city;
|
||||
public string $name = '';
|
||||
public ?int $country_id = null;
|
||||
public float $latitude = 0;
|
||||
public float $longitude = 0;
|
||||
public ?int $population = null;
|
||||
public ?string $population_date = null;
|
||||
|
||||
public function mount(City $city): void
|
||||
{
|
||||
$this->city = $city;
|
||||
$this->name = $city->name;
|
||||
$this->country_id = $city->country_id;
|
||||
$this->latitude = $city->latitude;
|
||||
$this->longitude = $city->longitude;
|
||||
$this->population = $city->population;
|
||||
$this->population_date = $city->population_date;
|
||||
}
|
||||
|
||||
public function updateCity(): void
|
||||
{
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255', 'unique:cities,name,'.$this->city->id],
|
||||
'country_id' => ['required', 'exists:countries,id'],
|
||||
'latitude' => ['required', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['required', 'numeric', 'between:-180,180'],
|
||||
'population' => ['nullable', 'integer', 'min:0'],
|
||||
'population_date' => ['nullable', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$validated['slug'] = str($validated['name'])->slug();
|
||||
|
||||
$this->city->update($validated);
|
||||
|
||||
session()->flash('status', __('City successfully updated!'));
|
||||
|
||||
$this->redirect(route_with_country('cities.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'countries' => Country::query()->orderBy('name')->get(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('Edit City') }}: {{ $city->name }}</flux:heading>
|
||||
</div>
|
||||
|
||||
<form wire:submit="updateCity" class="space-y-8">
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Basic Information') }}</flux:legend>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:input label="{{ __('Name') }}" wire:model="name" required />
|
||||
|
||||
<flux:select label="{{ __('Country') }}" wire:model="country_id" required>
|
||||
<option value="">{{ __('Select a country') }}</option>
|
||||
@foreach($countries as $country)
|
||||
<option value="{{ $country->id }}">{{ $country->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Coordinates') }}</flux:legend>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-6">
|
||||
<flux:input label="{{ __('Latitude') }}" type="number" step="any" wire:model="latitude" required />
|
||||
<flux:input label="{{ __('Longitude') }}" type="number" step="any" wire:model="longitude" required />
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Demographics') }}</flux:legend>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-6">
|
||||
<flux:input label="{{ __('Population') }}" type="number" wire:model="population" />
|
||||
<flux:input label="{{ __('Population Date') }}" wire:model="population_date" placeholder="e.g. 2024" />
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<flux:button type="submit" variant="primary">{{ __('Update City') }}</flux:button>
|
||||
<flux:button :href="route_with_country('cities.index')" variant="ghost">{{ __('Cancel') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
94
resources/views/livewire/cities/index.blade.php
Normal file
94
resources/views/livewire/cities/index.blade.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
use App\Models\City;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
new class extends Component {
|
||||
use WithPagination;
|
||||
|
||||
public $country = 'de';
|
||||
public $search = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->country = request()->route('country');
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'cities' => City::with(['country', 'createdBy'])
|
||||
->when($this->search, fn($query)
|
||||
=> $query->where('name', 'ilike', '%'.$this->search.'%'),
|
||||
)
|
||||
->orderBy('name')
|
||||
->paginate(15),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<flux:heading size="xl">{{ __('Cities') }}</flux:heading>
|
||||
<div class="flex items-center gap-4">
|
||||
<flux:input
|
||||
wire:model.live="search"
|
||||
:placeholder="__('Search cities...')"
|
||||
clearable
|
||||
/>
|
||||
@auth
|
||||
<flux:button class="cursor-pointer" :href="route_with_country('cities.create')" icon="plus" variant="primary">
|
||||
{{ __('Create City') }}
|
||||
</flux:button>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<flux:table :paginate="$cities" class="mt-6">
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Name') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Country') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Population') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Created By') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Actions') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@foreach ($cities as $city)
|
||||
<flux:table.row :key="$city->id">
|
||||
<flux:table.cell variant="strong">
|
||||
{{ $city->name }}
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($city->country)
|
||||
{{ $city->country->name }}
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($city->population)
|
||||
{{ number_format($city->population) }}
|
||||
@if($city->population_date)
|
||||
<span class="text-xs text-zinc-500">({{ $city->population_date }})</span>
|
||||
@endif
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($city->createdBy)
|
||||
{{ $city->createdBy->name }}
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<div class="flex gap-2">
|
||||
@auth
|
||||
<flux:button size="sm" :href="route('cities.edit',['city' => $city, 'country' => $country])" icon="pencil">
|
||||
{{ __('Edit') }}
|
||||
</flux:button>
|
||||
@endauth
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforeach
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
</div>
|
||||
71
resources/views/livewire/venues/create.blade.php
Normal file
71
resources/views/livewire/venues/create.blade.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Venue;
|
||||
use App\Models\City;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public string $name = '';
|
||||
public ?int $city_id = null;
|
||||
public string $street = '';
|
||||
|
||||
public function createVenue(): void
|
||||
{
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255', 'unique:venues,name'],
|
||||
'city_id' => ['required', 'exists:cities,id'],
|
||||
'street' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$validated['slug'] = str($validated['name'])->slug();
|
||||
$validated['created_by'] = auth()->id();
|
||||
|
||||
$venue = Venue::create($validated);
|
||||
|
||||
session()->flash('status', __('Venue successfully created!'));
|
||||
|
||||
$this->redirect(route_with_country('venues.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'cities' => City::query()->with('country')->orderBy('name')->get(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('Create Venue') }}</flux:heading>
|
||||
</div>
|
||||
|
||||
<form wire:submit="createVenue" class="space-y-8">
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Venue Information') }}</flux:legend>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:input label="{{ __('Name') }}" wire:model="name" required />
|
||||
|
||||
<flux:select label="{{ __('City') }}" wire:model="city_id" required>
|
||||
<option value="">{{ __('Select a city') }}</option>
|
||||
@foreach($cities as $city)
|
||||
<option value="{{ $city->id }}">
|
||||
{{ $city->name }}
|
||||
@if($city->country)
|
||||
({{ $city->country->name }})
|
||||
@endif
|
||||
</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:input label="{{ __('Street') }}" wire:model="street" required />
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<flux:button type="submit" variant="primary">{{ __('Create Venue') }}</flux:button>
|
||||
<flux:button :href="route_with_country('venues.index')" variant="ghost">{{ __('Cancel') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
79
resources/views/livewire/venues/edit.blade.php
Normal file
79
resources/views/livewire/venues/edit.blade.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Venue;
|
||||
use App\Models\City;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public Venue $venue;
|
||||
public string $name = '';
|
||||
public ?int $city_id = null;
|
||||
public string $street = '';
|
||||
|
||||
public function mount(Venue $venue): void
|
||||
{
|
||||
$this->venue = $venue;
|
||||
$this->name = $venue->name;
|
||||
$this->city_id = $venue->city_id;
|
||||
$this->street = $venue->street;
|
||||
}
|
||||
|
||||
public function updateVenue(): void
|
||||
{
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255', 'unique:venues,name,'.$this->venue->id],
|
||||
'city_id' => ['required', 'exists:cities,id'],
|
||||
'street' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$validated['slug'] = str($validated['name'])->slug();
|
||||
|
||||
$this->venue->update($validated);
|
||||
|
||||
session()->flash('status', __('Venue successfully updated!'));
|
||||
|
||||
$this->redirect(route_with_country('venues.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'cities' => City::query()->with('country')->orderBy('name')->get(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('Edit Venue') }}: {{ $venue->name }}</flux:heading>
|
||||
</div>
|
||||
|
||||
<form wire:submit="updateVenue" class="space-y-8">
|
||||
<flux:fieldset>
|
||||
<flux:legend>{{ __('Venue Information') }}</flux:legend>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:input label="{{ __('Name') }}" wire:model="name" required />
|
||||
|
||||
<flux:select label="{{ __('City') }}" wire:model="city_id" required>
|
||||
<option value="">{{ __('Select a city') }}</option>
|
||||
@foreach($cities as $city)
|
||||
<option value="{{ $city->id }}">
|
||||
{{ $city->name }}
|
||||
@if($city->country)
|
||||
({{ $city->country->name }})
|
||||
@endif
|
||||
</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:input label="{{ __('Street') }}" wire:model="street" required />
|
||||
</div>
|
||||
</flux:fieldset>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<flux:button type="submit" variant="primary">{{ __('Update Venue') }}</flux:button>
|
||||
<flux:button :href="route_with_country('venues.index')" variant="ghost">{{ __('Cancel') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
88
resources/views/livewire/venues/index.blade.php
Normal file
88
resources/views/livewire/venues/index.blade.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Venue;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
new class extends Component {
|
||||
use WithPagination;
|
||||
|
||||
public $country = 'de';
|
||||
public $search = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->country = request()->route('country');
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'venues' => Venue::with(['city.country', 'createdBy'])
|
||||
->when($this->search, fn($query)
|
||||
=> $query->where('name', 'ilike', '%'.$this->search.'%'),
|
||||
)
|
||||
->orderBy('name')
|
||||
->paginate(15),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<flux:heading size="xl">{{ __('Venues') }}</flux:heading>
|
||||
<div class="flex items-center gap-4">
|
||||
<flux:input
|
||||
wire:model.live="search"
|
||||
:placeholder="__('Search venues...')"
|
||||
clearable
|
||||
/>
|
||||
@auth
|
||||
<flux:button class="cursor-pointer" :href="route_with_country('venues.create')" icon="plus" variant="primary">
|
||||
{{ __('Create Venue') }}
|
||||
</flux:button>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<flux:table :paginate="$venues" class="mt-6">
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Name') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('City') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Created By') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Actions') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@foreach ($venues as $venue)
|
||||
<flux:table.row :key="$venue->id">
|
||||
<flux:table.cell variant="strong">
|
||||
{{ $venue->name }}
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($venue->city)
|
||||
{{ $venue->city->name }}
|
||||
@if($venue->city->country)
|
||||
<span class="text-xs text-zinc-500">({{ $venue->city->country->name }})</span>
|
||||
@endif
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($venue->createdBy)
|
||||
{{ $venue->createdBy->name }}
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<div class="flex gap-2">
|
||||
@auth
|
||||
<flux:button size="sm" :href="route('venues.edit', ['venue' => $venue, 'country' => $country])" icon="pencil">
|
||||
{{ __('Edit') }}
|
||||
</flux:button>
|
||||
@endauth
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforeach
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
</div>
|
||||
Reference in New Issue
Block a user