From f4d8f27db44c643960eff1679e1869137d4172a2 Mon Sep 17 00:00:00 2001 From: HomeOS Bootstrap Date: Sat, 18 Jul 2026 00:05:30 +0200 Subject: [PATCH] Rooms pages: index (per-room status) + show (devices with live control) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activates the Räume nav tab. Index lists rooms with online/lights/open counts; show lists a room's devices with entity chips and switch/light toggles (through DeviceCommandService), live via Echo. Zero console errors (nav check: 4/4 clean). Co-Authored-By: Claude Opus 4.8 --- .gitignore | 1 + app/Livewire/Rooms/Index.php | 25 +++++++++++ app/Livewire/Rooms/Show.php | 45 +++++++++++++++++++ lang/de/rooms.php | 10 +++++ lang/en/rooms.php | 10 +++++ resources/views/components/sidebar.blade.php | 2 +- .../views/livewire/rooms/index.blade.php | 40 +++++++++++++++++ resources/views/livewire/rooms/show.blade.php | 38 ++++++++++++++++ routes/web.php | 4 ++ 9 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 app/Livewire/Rooms/Index.php create mode 100644 app/Livewire/Rooms/Show.php create mode 100644 lang/de/rooms.php create mode 100644 lang/en/rooms.php create mode 100644 resources/views/livewire/rooms/index.blade.php create mode 100644 resources/views/livewire/rooms/show.blade.php diff --git a/.gitignore b/.gitignore index 379c748..7fe66ca 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ Thumbs.db # generated broker credentials + data (not committed) /docker/mosquitto/config/passwd /docker/mosquitto/data/ +/.claude/ diff --git a/app/Livewire/Rooms/Index.php b/app/Livewire/Rooms/Index.php new file mode 100644 index 0000000..cbad63c --- /dev/null +++ b/app/Livewire/Rooms/Index.php @@ -0,0 +1,25 @@ +with(['devices.entities.state']) + ->orderBy('sort')->orderBy('name') + ->get(); + + return view('livewire.rooms.index', ['rooms' => $rooms]); + } +} diff --git a/app/Livewire/Rooms/Show.php b/app/Livewire/Rooms/Show.php new file mode 100644 index 0000000..b7a3da5 --- /dev/null +++ b/app/Livewire/Rooms/Show.php @@ -0,0 +1,45 @@ +room = $room; + } + + #[On('echo-private:home,.DeviceStateChanged')] + public function onDeviceStateChanged(): void {} + + public function toggleEntity(int $entityId): void + { + $entity = Entity::query() + ->whereHas('device', fn ($q) => $q->where('room_id', $this->room->id)) + ->with('device', 'state') + ->find($entityId); + + if ($entity === null || ! in_array($entity->type, ['switch', 'light'], true)) { + return; + } + + app(DeviceCommandService::class)->toggle($entity); + } + + public function render() + { + $this->room->load(['devices' => fn ($q) => $q->orderBy('name'), 'devices.entities.state']); + + return view('livewire.rooms.show'); + } +} diff --git a/lang/de/rooms.php b/lang/de/rooms.php new file mode 100644 index 0000000..3203c92 --- /dev/null +++ b/lang/de/rooms.php @@ -0,0 +1,10 @@ + 'Alle Räume im Überblick.', + 'empty' => 'Noch keine Räume.', + 'no_devices' => 'Keine Geräte in diesem Raum.', + 'online' => 'online', + 'lights' => 'Licht an', + 'open' => 'offen', +]; diff --git a/lang/en/rooms.php b/lang/en/rooms.php new file mode 100644 index 0000000..92b0f98 --- /dev/null +++ b/lang/en/rooms.php @@ -0,0 +1,10 @@ + 'All rooms at a glance.', + 'empty' => 'No rooms yet.', + 'no_devices' => 'No devices in this room.', + 'online' => 'online', + 'lights' => 'lights on', + 'open' => 'open', +]; diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index 1a1e2f6..fa6b724 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -2,7 +2,7 @@ $groups = [ 'nav.section_overview' => [ ['key' => 'dashboard', 'icon' => 'dashboard', 'route' => 'dashboard', 'lock' => false], - ['key' => 'rooms', 'icon' => 'rooms', 'route' => null, 'lock' => false], + ['key' => 'rooms', 'icon' => 'rooms', 'route' => 'rooms.index', 'active' => 'rooms.*', 'lock' => false], ['key' => 'devices', 'icon' => 'devices', 'route' => 'devices.index', 'active' => 'devices.*', 'lock' => false], ], 'nav.section_persons' => [ diff --git a/resources/views/livewire/rooms/index.blade.php b/resources/views/livewire/rooms/index.blade.php new file mode 100644 index 0000000..1c313de --- /dev/null +++ b/resources/views/livewire/rooms/index.blade.php @@ -0,0 +1,40 @@ +
+ + +
+ @if ($rooms->isEmpty()) +

{{ __('rooms.empty') }}

+ @else +
+ @foreach ($rooms as $room) + @php + $devices = $room->devices; + $entities = $devices->flatMap->entities; + $online = $devices->filter->isOnline()->count(); + $lightsOn = $entities->filter(fn ($e) => in_array($e->type, ['light', 'switch']) && data_get($e->state, 'state.on') === true)->count(); + $open = $entities->filter(fn ($e) => $e->type === 'contact' && data_get($e->state, 'state.open') === true)->count(); + @endphp + +
+ + {{ $room->name }} + +
+
+ + {{ $online }}/{{ $devices->count() }} {{ __('rooms.online') }} + + @if ($lightsOn > 0) + {{ $lightsOn }} {{ __('rooms.lights') }} + @endif + @if ($open > 0) + {{ $open }} {{ __('rooms.open') }} + @endif +
+
+ @endforeach +
+ @endif +
+
diff --git a/resources/views/livewire/rooms/show.blade.php b/resources/views/livewire/rooms/show.blade.php new file mode 100644 index 0000000..8b595fc --- /dev/null +++ b/resources/views/livewire/rooms/show.blade.php @@ -0,0 +1,38 @@ +
+ + + + + + + + + +
+ @forelse ($room->devices as $device) + +
+
+ + {{ $device->name }} + {{ $device->model }} +
+
+ @foreach ($device->entities as $entity) +
+ + @if (in_array($entity->type, ['switch', 'light'])) + + @endif +
+ @endforeach +
+
+
+ @empty +

{{ __('rooms.no_devices') }}

+ @endforelse +
+
diff --git a/routes/web.php b/routes/web.php index 32d7d0e..5edb949 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,6 +5,8 @@ use App\Livewire\Dashboard; use App\Livewire\Devices\Index as DeviceIndex; use App\Livewire\Devices\Show as DeviceShow; use App\Livewire\Host; +use App\Livewire\Rooms\Index as RoomIndex; +use App\Livewire\Rooms\Show as RoomShow; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; @@ -16,6 +18,8 @@ Route::middleware('guest')->group(function () { Route::middleware('auth')->group(function () { Route::get('/dashboard', Dashboard::class)->name('dashboard'); + Route::get('/rooms', RoomIndex::class)->name('rooms.index'); + Route::get('/rooms/{room}', RoomShow::class)->name('rooms.show'); Route::get('/devices', DeviceIndex::class)->name('devices.index'); Route::get('/devices/{device}', DeviceShow::class)->name('devices.show'); Route::get('/host', Host::class)->name('host');