Fix R15: include room-less devices in home status
HomeStatus aggregated from Room, so a device with room_id = null (a supported state, e.g. after picking "no room") vanished from dashboard totals, KPIs and warnings. Aggregate from all devices instead; the dashboard groups them by room with a trailing "Ohne Raum" group so nothing disappears. Added a feature test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/phase-1-bootstrap
parent
dc27b1686c
commit
1520b95f8f
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Room;
|
||||
use App\Services\HomeStatus;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Component;
|
||||
|
|
@ -11,12 +12,26 @@ class Dashboard extends Component
|
|||
{
|
||||
public function render(HomeStatus $home)
|
||||
{
|
||||
$rooms = $home->load();
|
||||
$devices = $home->devices();
|
||||
|
||||
// Group by room in room order; room-less devices go into a trailing "no room" group
|
||||
// so they never disappear from the overview.
|
||||
$groups = [];
|
||||
foreach (Room::orderBy('sort')->orderBy('name')->get() as $room) {
|
||||
$roomDevices = $devices->where('room_id', $room->id)->values();
|
||||
if ($roomDevices->isNotEmpty()) {
|
||||
$groups[] = ['name' => $room->name, 'devices' => $roomDevices];
|
||||
}
|
||||
}
|
||||
$unassigned = $devices->whereNull('room_id')->values();
|
||||
if ($unassigned->isNotEmpty()) {
|
||||
$groups[] = ['name' => __('dashboard.no_room'), 'devices' => $unassigned];
|
||||
}
|
||||
|
||||
return view('livewire.dashboard', [
|
||||
'rooms' => $rooms,
|
||||
'summary' => $home->summary($rooms),
|
||||
'warningCount' => count($home->warnings($rooms)),
|
||||
'groups' => $groups,
|
||||
'summary' => $home->summary($devices),
|
||||
'warningCount' => count($home->warnings($devices)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class Warnings extends ModalComponent
|
|||
$home = app(HomeStatus::class);
|
||||
|
||||
return view('livewire.modals.warnings', [
|
||||
'warnings' => $home->warnings($home->load()),
|
||||
'warnings' => $home->warnings($home->devices()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Room;
|
||||
use App\Models\Device;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Aggregates the current home state (rooms/devices/entities) into a summary and a
|
||||
* warning list. Shared by the Dashboard (grid + warning count) and the Warnings modal.
|
||||
* Aggregates the current home state (all devices/entities, incl. those with no room)
|
||||
* into a summary and a warning list. Shared by the Dashboard and the Warnings modal.
|
||||
*/
|
||||
class HomeStatus
|
||||
{
|
||||
|
|
@ -15,17 +15,17 @@ class HomeStatus
|
|||
|
||||
public function __construct(private readonly SystemHealth $health) {}
|
||||
|
||||
public function load(): Collection
|
||||
/** Every device (including room-less ones), eager-loaded for aggregation + display. */
|
||||
public function devices(): Collection
|
||||
{
|
||||
return Room::query()
|
||||
->with(['devices' => fn ($q) => $q->orderBy('name'), 'devices.entities.state'])
|
||||
->orderBy('sort')->orderBy('name')
|
||||
return Device::query()
|
||||
->with(['entities.state', 'room'])
|
||||
->orderBy('name')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function summary(Collection $rooms): array
|
||||
public function summary(Collection $devices): array
|
||||
{
|
||||
$devices = $rooms->flatMap->devices;
|
||||
$entities = $devices->flatMap->entities;
|
||||
|
||||
return [
|
||||
|
|
@ -38,9 +38,8 @@ class HomeStatus
|
|||
}
|
||||
|
||||
/** @return array<int, array{level:string,icon:string,title:string,meta:?string,link:?string}> */
|
||||
public function warnings(Collection $rooms): array
|
||||
public function warnings(Collection $devices): array
|
||||
{
|
||||
$devices = $rooms->flatMap->devices;
|
||||
$entities = $devices->flatMap->entities;
|
||||
$warnings = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ return [
|
|||
'kpi_low_battery' => 'Niedriger Akku',
|
||||
|
||||
'rooms_title' => 'Räume',
|
||||
'no_room' => 'Ohne Raum',
|
||||
|
||||
'warn_host' => 'Systemdienst gestört',
|
||||
'warn_host_meta' => 'Details im Host-Bereich',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ return [
|
|||
'kpi_low_battery' => 'Low battery',
|
||||
|
||||
'rooms_title' => 'Rooms',
|
||||
'no_room' => 'No room',
|
||||
|
||||
'warn_host' => 'System service degraded',
|
||||
'warn_host_meta' => 'Details in the Host area',
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@
|
|||
<div>
|
||||
<h2 class="text-[13px] font-bold text-ink-2 uppercase tracking-[0.08em] mb-3">{{ __('dashboard.rooms_title') }}</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||
@foreach ($rooms as $room)
|
||||
<x-panel :title="$room->name" class="reveal">
|
||||
@foreach ($groups as $group)
|
||||
<x-panel :title="$group['name']" class="reveal">
|
||||
<div class="flex flex-col divide-y divide-line-soft -my-1">
|
||||
@foreach ($room->devices as $device)
|
||||
@foreach ($group['devices'] as $device)
|
||||
<a href="{{ route('devices.show', $device) }}" wire:navigate
|
||||
class="flex flex-col gap-2 py-3 first:pt-1 last:pb-1 -mx-4 px-4 hover:bg-raised/50 transition-colors">
|
||||
<div class="flex items-center gap-2.5">
|
||||
|
|
|
|||
|
|
@ -58,6 +58,19 @@ class HomeTest extends TestCase
|
|||
->assertSee('Türsensor');
|
||||
}
|
||||
|
||||
public function test_room_less_device_appears_in_home_status(): void
|
||||
{
|
||||
$device = Device::create(['name' => 'Heimloser Sensor', 'status' => 'active', 'last_seen_at' => now(), 'room_id' => null]);
|
||||
$battery = Entity::create(['device_id' => $device->id, 'type' => 'battery', 'key' => 'battery:0', 'name' => 'Batterie']);
|
||||
DeviceState::create(['entity_id' => $battery->id, 'state' => ['percent' => 5]]);
|
||||
|
||||
$this->actingAs(User::factory()->create())
|
||||
->get('/dashboard')
|
||||
->assertOk()
|
||||
->assertSee('Heimloser Sensor')
|
||||
->assertSee(__('dashboard.no_room'));
|
||||
}
|
||||
|
||||
public function test_device_room_update_rejects_invalid_room(): void
|
||||
{
|
||||
$device = Device::create(['name' => 'Lampe', 'status' => 'active', 'last_seen_at' => now()]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue