move into sonardesk

This commit is contained in:
Mark Gregory
2026-10-12 14:50:13 +01:00
parent 5f0d2ceb72
commit 38b4a7406f
879 changed files with 24021 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
<?php
return ['name' => 'Locations'];
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('locations', function (Blueprint $t): void {
$t->id();
$t->string('name');
$t->string('slug')->unique();
$t->string('type')->default('office');
$t->text('address')->nullable();
$t->string('postcode')->nullable();
$t->string('country')->nullable();
$t->string('phone')->nullable();
$t->string('email')->nullable();
$t->decimal('latitude', 10, 7)->nullable();
$t->decimal('longitude', 10, 7)->nullable();
$t->json('opening_hours')->nullable();
$t->boolean('is_active')->default(true);
$t->unsignedInteger('sort_order')->default(0);
$t->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('locations');
}
};
@@ -0,0 +1,31 @@
<?php
namespace Modules\Locations\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Modules\Locations\Models\Location;
final class LocationController extends Controller
{
public function index(): View
{
return view('locations::admin.index', ['locations' => Location::orderBy('sort_order')->orderBy('name')->paginate(25)]);
}
public function store(Request $r): RedirectResponse
{
Location::create($r->validate(['name' => ['required', 'string', 'max:255'], 'slug' => ['required', 'string', 'max:255', 'unique:locations,slug'], 'type' => ['required', 'in:office,store,warehouse,venue,service'], 'address' => ['nullable', 'string'], 'postcode' => ['nullable', 'string', 'max:30'], 'country' => ['nullable', 'string', 'max:120'], 'phone' => ['nullable', 'string', 'max:80'], 'email' => ['nullable', 'email', 'max:255'], 'latitude' => ['nullable', 'numeric', 'between:-90,90'], 'longitude' => ['nullable', 'numeric', 'between:-180,180'], 'sort_order' => ['nullable', 'integer', 'min:0']]) + ['is_active' => $r->boolean('is_active', true)]);
return back()->with('status', 'Location created.');
}
public function destroy(Location $location): RedirectResponse
{
$location->delete();
return back()->with('status', 'Location deleted.');
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Modules\Locations\Models;
use Illuminate\Database\Eloquent\Model;
final class Location extends Model
{
protected $fillable = ['name', 'slug', 'type', 'address', 'postcode', 'country', 'phone', 'email', 'latitude', 'longitude', 'opening_hours', 'is_active', 'sort_order'];
protected function casts(): array
{
return ['latitude' => 'decimal:7', 'longitude' => 'decimal:7', 'opening_hours' => 'array', 'is_active' => 'boolean'];
}
}
@@ -0,0 +1,20 @@
<?php
namespace Modules\Locations\Providers;
use Illuminate\Support\ServiceProvider;
final class LocationsServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
$this->mergeConfigFrom(module_path('Locations', 'Config/config.php'), 'locations');
}
public function boot(): void
{
$this->loadMigrationsFrom(module_path('Locations', 'Database/Migrations'));
$this->loadViewsFrom(module_path('Locations', 'Resources/views'), 'locations');
}
}
@@ -0,0 +1,15 @@
<?php
namespace Modules\Locations\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
final class RouteServiceProvider extends ServiceProvider
{
public function boot(): void
{
parent::boot();
Route::middleware('web')->group(module_path('Locations', 'Routes/web.php'));
}
}
@@ -0,0 +1 @@
<x-layouts.app :title="__('Locations')"><div class="space-y-8"><div><h1 class="text-2xl font-semibold">Locations</h1><p class="text-sm text-zinc-500">Manage offices, stores, venues and service locations.</p></div>@if(session('status'))<div class="rounded border border-green-300 bg-green-50 p-3 text-green-800">{{ session('status') }}</div>@endif<form method="POST" action="{{ route('administration.locations.store') }}" class="space-y-4 rounded-xl border p-5">@csrf<h2 class="font-semibold">Add location</h2><div class="grid gap-4 md:grid-cols-2"><flux:input name="name" label="Name" required/><flux:input name="slug" label="Slug" required/><flux:select name="type" label="Type"><option value="office">Office</option><option value="store">Store</option><option value="warehouse">Warehouse</option><option value="venue">Venue</option><option value="service">Service</option></flux:select><flux:input name="postcode" label="Postcode"/><flux:input name="phone" label="Phone"/><flux:input name="email" type="email" label="Email"/></div><flux:textarea name="address" label="Address"/><flux:checkbox name="is_active" value="1" label="Active" checked/><flux:button type="submit" variant="primary">Create location</flux:button></form><div class="overflow-hidden rounded-xl border"><table class="w-full text-sm"><thead class="bg-zinc-50"><tr><th class="p-3 text-left">Location</th><th class="p-3 text-left">Type</th><th class="p-3 text-left">Contact</th><th class="p-3 text-left">Status</th><th class="p-3"></th></tr></thead><tbody>@forelse($locations as $location)<tr class="border-t"><td class="p-3"><div class="font-medium">{{ $location->name }}</div><div class="text-zinc-500">{{ $location->address }}</div></td><td class="p-3">{{ ucfirst($location->type) }}</td><td class="p-3">{{ $location->phone ?: $location->email ?: '—' }}</td><td class="p-3">{{ $location->is_active ? 'Active' : 'Inactive' }}</td><td class="p-3 text-right"><form method="POST" action="{{ route('administration.locations.destroy',$location) }}">@csrf @method('DELETE')<flux:button size="sm" variant="danger" type="submit">Delete</flux:button></form></td></tr>@empty<tr><td colspan="5" class="p-6 text-center text-zinc-500">No locations yet.</td></tr>@endforelse</tbody></table></div>{{ $locations->links() }}</div></x-layouts.app>
+10
View File
@@ -0,0 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Locations\Http\Controllers\LocationController;
Route::middleware(['auth', 'verified'])->prefix('administration/locations')->name('administration.locations.')->group(function (): void {
Route::get('/', [LocationController::class, 'index'])->name('index');
Route::post('/', [LocationController::class, 'store'])->name('store');
Route::delete('/{location}', [LocationController::class, 'destroy'])->name('destroy');
});
@@ -0,0 +1,13 @@
<?php
namespace Modules\Locations\Tests\Feature;
use Tests\TestCase;
final class ModuleBootTest extends TestCase
{
public function test_module_configuration_is_available(): void
{
$this->assertSame('Locations', config('locations.name'));
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "Locations",
"alias": "locations",
"version": "1.0.0",
"description": "Office and service location management.",
"keywords": ["business", "locations"],
"priority": 44,
"providers": ["Modules\\Locations\\Providers\\LocationsServiceProvider"],
"aliases": {},
"files": [],
"requires": ["Core", "Administration"]
}