# Smart Home Dashboard Development

**Meta-Description:** Smart Home Dashboard mit React und WebSocket. Real-time Updates, Device Control und responsive Design für die Heimautomatisierung.

**Keywords:** Smart Home Dashboard, React, WebSocket, Home Automation, Real-time UI, Device Control, IoT Dashboard

---

## Einführung

Ein **Smart Home Dashboard** ist die zentrale Steuerung für alle vernetzten Geräte. Mit **React**, **WebSocket** und modernem UI-Design bauen wir ein responsives Dashboard für Echtzeit-Monitoring und Gerätesteuerung.

---

## Dashboard Architecture

```
┌─────────────────────────────────────────────────────────────┐
│              SMART HOME DASHBOARD ARCHITECTURE               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Frontend:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 React + Next.js                      │   │
│  │  ┌───────────┐  ┌───────────┐  ┌───────────┐       │   │
│  │  │  Device   │  │   Room    │  │  Scenes   │       │   │
│  │  │  Cards    │  │   View    │  │  Manager  │       │   │
│  │  └───────────┘  └───────────┘  └───────────┘       │   │
│  │  ┌───────────┐  ┌───────────┐  ┌───────────┐       │   │
│  │  │  Energy   │  │  Climate  │  │  Security │       │   │
│  │  │  Monitor  │  │  Control  │  │  Panel    │       │   │
│  │  └───────────┘  └───────────┘  └───────────┘       │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                    WebSocket / REST                         │
│                           │                                 │
│  Backend:                 ▼                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              API Gateway / BFF                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│  ┌────────────┬───────────┴───────────┬────────────┐      │
│  │    MQTT    │     Home Assistant    │   Database │      │
│  │   Broker   │         API           │  (States)  │      │
│  └────────────┴───────────────────────┴────────────┘      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## React Dashboard Components

```typescript
// components/dashboard/DeviceCard.tsx
'use client';

import { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { Slider } from '@/components/ui/slider';
import { cn } from '@/lib/utils';

interface DeviceCardProps {
  device: {
    id: string;
    name: string;
    type: 'light' | 'switch' | 'sensor' | 'thermostat';
    state: 'on' | 'off';
    brightness?: number;
    temperature?: number;
    humidity?: number;
    setpoint?: number;
  };
  onStateChange: (id: string, state: Partial<typeof device>) => void;
}

export function DeviceCard({ device, onStateChange }: DeviceCardProps) {
  const [isLoading, setIsLoading] = useState(false);

  const handleToggle = async (checked: boolean) => {
    setIsLoading(true);
    await onStateChange(device.id, { state: checked ? 'on' : 'off' });
    setIsLoading(false);
  };

  const handleBrightness = async (value: number[]) => {
    await onStateChange(device.id, { brightness: value[0] });
  };

  const handleSetpoint = async (value: number[]) => {
    await onStateChange(device.id, { setpoint: value[0] });
  };

  return (
    <Card className={cn(
      'transition-all duration-200',
      device.state === 'on' && 'ring-2 ring-primary'
    )}>
      <CardHeader className="flex flex-row items-center justify-between pb-2">
        <CardTitle className="text-sm font-medium">{device.name}</CardTitle>
        {(device.type === 'light' || device.type === 'switch') && (
          <Switch
            checked={device.state === 'on'}
            onCheckedChange={handleToggle}
            disabled={isLoading}
          />
        )}
      </CardHeader>

      <CardContent>
        {/* Light with Brightness */}
        {device.type === 'light' && device.brightness !== undefined && (
          <div className="space-y-2">
            <div className="flex justify-between text-sm">
              <span>Brightness</span>
              <span>{device.brightness}%</span>
            </div>
            <Slider
              value={[device.brightness]}
              onValueChange={handleBrightness}
              max={100}
              step={1}
              disabled={device.state === 'off'}
            />
          </div>
        )}

        {/* Sensor Readings */}
        {device.type === 'sensor' && (
          <div className="grid grid-cols-2 gap-4">
            {device.temperature !== undefined && (
              <div className="text-center">
                <div className="text-2xl font-bold">{device.temperature}°C</div>
                <div className="text-xs text-muted-foreground">Temperature</div>
              </div>
            )}
            {device.humidity !== undefined && (
              <div className="text-center">
                <div className="text-2xl font-bold">{device.humidity}%</div>
                <div className="text-xs text-muted-foreground">Humidity</div>
              </div>
            )}
          </div>
        )}

        {/* Thermostat */}
        {device.type === 'thermostat' && (
          <div className="space-y-4">
            <div className="text-center">
              <div className="text-3xl font-bold">{device.temperature}°C</div>
              <div className="text-xs text-muted-foreground">Current</div>
            </div>
            <div className="space-y-2">
              <div className="flex justify-between text-sm">
                <span>Setpoint</span>
                <span>{device.setpoint}°C</span>
              </div>
              <Slider
                value={[device.setpoint || 20]}
                onValueChange={handleSetpoint}
                min={16}
                max={28}
                step={0.5}
              />
            </div>
          </div>
        )}
      </CardContent>
    </Card>
  );
}
```

---

## Room View

```typescript
// components/dashboard/RoomView.tsx
'use client';

import { useState, useEffect } from 'react';
import { DeviceCard } from './DeviceCard';
import { useSmartHome } from '@/hooks/useSmartHome';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
  Home,
  Sofa,
  Bed,
  UtensilsCrossed,
  Bath,
  TreeDeciduous
} from 'lucide-react';

const roomIcons: Record<string, any> = {
  all: Home,
  living_room: Sofa,
  bedroom: Bed,
  kitchen: UtensilsCrossed,
  bathroom: Bath,
  outdoor: TreeDeciduous
};

export function RoomView() {
  const { devices, rooms, updateDevice, isConnected } = useSmartHome();
  const [activeRoom, setActiveRoom] = useState('all');

  const filteredDevices = activeRoom === 'all'
    ? devices
    : devices.filter(d => d.room === activeRoom);

  return (
    <div className="space-y-6">
      {/* Connection Status */}
      <div className="flex items-center gap-2">
        <div className={`w-2 h-2 rounded-full ${
          isConnected ? 'bg-green-500' : 'bg-red-500'
        }`} />
        <span className="text-sm text-muted-foreground">
          {isConnected ? 'Connected' : 'Disconnected'}
        </span>
      </div>

      {/* Room Tabs */}
      <Tabs value={activeRoom} onValueChange={setActiveRoom}>
        <TabsList className="flex-wrap h-auto">
          <TabsTrigger value="all" className="gap-2">
            <Home className="w-4 h-4" />
            All
          </TabsTrigger>
          {rooms.map(room => {
            const Icon = roomIcons[room.id] || Home;
            return (
              <TabsTrigger key={room.id} value={room.id} className="gap-2">
                <Icon className="w-4 h-4" />
                {room.name}
              </TabsTrigger>
            );
          })}
        </TabsList>

        <TabsContent value={activeRoom} className="mt-6">
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
            {filteredDevices.map(device => (
              <DeviceCard
                key={device.id}
                device={device}
                onStateChange={updateDevice}
              />
            ))}
          </div>

          {filteredDevices.length === 0 && (
            <div className="text-center py-12 text-muted-foreground">
              No devices in this room
            </div>
          )}
        </TabsContent>
      </Tabs>
    </div>
  );
}
```

---

## WebSocket Hook

```typescript
// hooks/useSmartHome.ts
'use client';

import { useState, useEffect, useCallback, useRef } from 'react';

interface Device {
  id: string;
  name: string;
  type: 'light' | 'switch' | 'sensor' | 'thermostat';
  room: string;
  state: 'on' | 'off';
  brightness?: number;
  temperature?: number;
  humidity?: number;
  setpoint?: number;
  lastUpdated: string;
}

interface Room {
  id: string;
  name: string;
}

interface SmartHomeState {
  devices: Device[];
  rooms: Room[];
  isConnected: boolean;
}

export function useSmartHome() {
  const [state, setState] = useState<SmartHomeState>({
    devices: [],
    rooms: [],
    isConnected: false
  });

  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimeoutRef = useRef<NodeJS.Timeout>();

  const connect = useCallback(() => {
    const wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:3001';
    const ws = new WebSocket(wsUrl);

    ws.onopen = () => {
      console.log('WebSocket connected');
      setState(prev => ({ ...prev, isConnected: true }));

      // Request initial state
      ws.send(JSON.stringify({ type: 'get_state' }));
    };

    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);

      switch (message.type) {
        case 'initial_state':
          setState(prev => ({
            ...prev,
            devices: message.devices,
            rooms: message.rooms
          }));
          break;

        case 'device_update':
          setState(prev => ({
            ...prev,
            devices: prev.devices.map(d =>
              d.id === message.device.id
                ? { ...d, ...message.device, lastUpdated: new Date().toISOString() }
                : d
            )
          }));
          break;

        case 'device_added':
          setState(prev => ({
            ...prev,
            devices: [...prev.devices, message.device]
          }));
          break;

        case 'device_removed':
          setState(prev => ({
            ...prev,
            devices: prev.devices.filter(d => d.id !== message.deviceId)
          }));
          break;
      }
    };

    ws.onclose = () => {
      console.log('WebSocket disconnected');
      setState(prev => ({ ...prev, isConnected: false }));

      // Reconnect after 5 seconds
      reconnectTimeoutRef.current = setTimeout(connect, 5000);
    };

    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    wsRef.current = ws;
  }, []);

  useEffect(() => {
    connect();

    return () => {
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
      wsRef.current?.close();
    };
  }, [connect]);

  const updateDevice = useCallback(async (
    deviceId: string,
    updates: Partial<Device>
  ) => {
    // Optimistic Update
    setState(prev => ({
      ...prev,
      devices: prev.devices.map(d =>
        d.id === deviceId ? { ...d, ...updates } : d
      )
    }));

    // Send to server
    wsRef.current?.send(JSON.stringify({
      type: 'update_device',
      deviceId,
      updates
    }));
  }, []);

  const executeScene = useCallback((sceneId: string) => {
    wsRef.current?.send(JSON.stringify({
      type: 'execute_scene',
      sceneId
    }));
  }, []);

  return {
    ...state,
    updateDevice,
    executeScene
  };
}
```

---

## Scene Manager

```typescript
// components/dashboard/SceneManager.tsx
'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
  Sun,
  Moon,
  Film,
  Coffee,
  PartyPopper,
  Power
} from 'lucide-react';

interface Scene {
  id: string;
  name: string;
  icon: string;
  description: string;
}

const defaultScenes: Scene[] = [
  { id: 'morning', name: 'Good Morning', icon: 'sun', description: 'Wake up routine' },
  { id: 'movie', name: 'Movie Time', icon: 'film', description: 'Dim lights for movies' },
  { id: 'goodnight', name: 'Good Night', icon: 'moon', description: 'All lights off' },
  { id: 'party', name: 'Party Mode', icon: 'party', description: 'Colorful lights' },
  { id: 'away', name: 'Away', icon: 'power', description: 'Energy saving mode' },
  { id: 'coffee', name: 'Coffee Break', icon: 'coffee', description: 'Cozy atmosphere' }
];

const iconMap: Record<string, any> = {
  sun: Sun,
  moon: Moon,
  film: Film,
  coffee: Coffee,
  party: PartyPopper,
  power: Power
};

interface SceneManagerProps {
  onExecute: (sceneId: string) => void;
}

export function SceneManager({ onExecute }: SceneManagerProps) {
  const [activeScene, setActiveScene] = useState<string | null>(null);
  const [isExecuting, setIsExecuting] = useState(false);

  const handleExecute = async (sceneId: string) => {
    setIsExecuting(true);
    setActiveScene(sceneId);

    await onExecute(sceneId);

    setTimeout(() => {
      setIsExecuting(false);
    }, 1000);
  };

  return (
    <Card>
      <CardHeader>
        <CardTitle>Scenes</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
          {defaultScenes.map(scene => {
            const Icon = iconMap[scene.icon] || Sun;
            const isActive = activeScene === scene.id;

            return (
              <Button
                key={scene.id}
                variant={isActive ? 'default' : 'outline'}
                className="h-auto py-4 flex flex-col gap-2"
                onClick={() => handleExecute(scene.id)}
                disabled={isExecuting}
              >
                <Icon className="w-6 h-6" />
                <span className="text-sm font-medium">{scene.name}</span>
              </Button>
            );
          })}
        </div>
      </CardContent>
    </Card>
  );
}
```

---

## Climate Control

```typescript
// components/dashboard/ClimateControl.tsx
'use client';

import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Thermometer, Droplets, Wind, Plus, Minus } from 'lucide-react';

interface ClimateControlProps {
  temperature: number;
  humidity: number;
  setpoint: number;
  mode: 'heat' | 'cool' | 'auto' | 'off';
  onSetpointChange: (value: number) => void;
  onModeChange: (mode: string) => void;
}

export function ClimateControl({
  temperature,
  humidity,
  setpoint,
  mode,
  onSetpointChange,
  onModeChange
}: ClimateControlProps) {
  const adjustSetpoint = (delta: number) => {
    const newValue = Math.max(16, Math.min(28, setpoint + delta));
    onSetpointChange(newValue);
  };

  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex items-center gap-2">
          <Thermometer className="w-5 h-5" />
          Climate
        </CardTitle>
      </CardHeader>
      <CardContent className="space-y-6">
        {/* Current Values */}
        <div className="grid grid-cols-2 gap-4">
          <div className="text-center p-4 bg-muted rounded-lg">
            <Thermometer className="w-6 h-6 mx-auto mb-2 text-orange-500" />
            <div className="text-3xl font-bold">{temperature}°C</div>
            <div className="text-sm text-muted-foreground">Current</div>
          </div>
          <div className="text-center p-4 bg-muted rounded-lg">
            <Droplets className="w-6 h-6 mx-auto mb-2 text-blue-500" />
            <div className="text-3xl font-bold">{humidity}%</div>
            <div className="text-sm text-muted-foreground">Humidity</div>
          </div>
        </div>

        {/* Setpoint Control */}
        <div className="flex items-center justify-center gap-6">
          <Button
            variant="outline"
            size="icon"
            className="w-12 h-12 rounded-full"
            onClick={() => adjustSetpoint(-0.5)}
          >
            <Minus className="w-6 h-6" />
          </Button>

          <div className="text-center">
            <div className="text-5xl font-bold">{setpoint}°C</div>
            <div className="text-sm text-muted-foreground">Target</div>
          </div>

          <Button
            variant="outline"
            size="icon"
            className="w-12 h-12 rounded-full"
            onClick={() => adjustSetpoint(0.5)}
          >
            <Plus className="w-6 h-6" />
          </Button>
        </div>

        {/* Mode Selection */}
        <div className="flex gap-2">
          {['heat', 'cool', 'auto', 'off'].map(m => (
            <Button
              key={m}
              variant={mode === m ? 'default' : 'outline'}
              className="flex-1"
              onClick={() => onModeChange(m)}
            >
              {m.charAt(0).toUpperCase() + m.slice(1)}
            </Button>
          ))}
        </div>
      </CardContent>
    </Card>
  );
}
```

---

## Energy Monitor

```typescript
// components/dashboard/EnergyMonitor.tsx
'use client';

import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { Zap, TrendingDown, TrendingUp } from 'lucide-react';

interface EnergyData {
  currentPower: number;  // Watts
  todayUsage: number;    // kWh
  monthlyUsage: number;  // kWh
  monthlyBudget: number; // kWh
  trend: 'up' | 'down' | 'stable';
  trendPercent: number;
}

interface EnergyMonitorProps {
  data: EnergyData;
}

export function EnergyMonitor({ data }: EnergyMonitorProps) {
  const budgetUsage = (data.monthlyUsage / data.monthlyBudget) * 100;
  const TrendIcon = data.trend === 'up' ? TrendingUp : TrendingDown;

  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex items-center gap-2">
          <Zap className="w-5 h-5 text-yellow-500" />
          Energy
        </CardTitle>
      </CardHeader>
      <CardContent className="space-y-6">
        {/* Current Power */}
        <div className="text-center">
          <div className="text-4xl font-bold">
            {data.currentPower.toLocaleString()} W
          </div>
          <div className="text-sm text-muted-foreground">Current Power</div>
        </div>

        {/* Usage Stats */}
        <div className="grid grid-cols-2 gap-4">
          <div className="text-center p-3 bg-muted rounded-lg">
            <div className="text-xl font-semibold">
              {data.todayUsage.toFixed(1)} kWh
            </div>
            <div className="text-xs text-muted-foreground">Today</div>
          </div>
          <div className="text-center p-3 bg-muted rounded-lg">
            <div className="text-xl font-semibold">
              {data.monthlyUsage.toFixed(0)} kWh
            </div>
            <div className="text-xs text-muted-foreground">This Month</div>
          </div>
        </div>

        {/* Budget Progress */}
        <div className="space-y-2">
          <div className="flex justify-between text-sm">
            <span>Monthly Budget</span>
            <span>{budgetUsage.toFixed(0)}%</span>
          </div>
          <Progress value={budgetUsage} className="h-2" />
          <div className="text-xs text-muted-foreground text-right">
            {data.monthlyUsage.toFixed(0)} / {data.monthlyBudget} kWh
          </div>
        </div>

        {/* Trend */}
        <div className="flex items-center justify-center gap-2">
          <TrendIcon className={`w-5 h-5 ${
            data.trend === 'up' ? 'text-red-500' : 'text-green-500'
          }`} />
          <span className={`font-medium ${
            data.trend === 'up' ? 'text-red-500' : 'text-green-500'
          }`}>
            {data.trend === 'up' ? '+' : '-'}{data.trendPercent}%
          </span>
          <span className="text-sm text-muted-foreground">vs last week</span>
        </div>
      </CardContent>
    </Card>
  );
}
```

---

## Main Dashboard Layout

```typescript
// app/dashboard/page.tsx
'use client';

import { RoomView } from '@/components/dashboard/RoomView';
import { SceneManager } from '@/components/dashboard/SceneManager';
import { ClimateControl } from '@/components/dashboard/ClimateControl';
import { EnergyMonitor } from '@/components/dashboard/EnergyMonitor';
import { useSmartHome } from '@/hooks/useSmartHome';

export default function DashboardPage() {
  const { devices, executeScene, updateDevice } = useSmartHome();

  // Aggregate climate data
  const climateDevice = devices.find(d => d.type === 'thermostat');

  // Mock energy data (in production: from API)
  const energyData = {
    currentPower: 1234,
    todayUsage: 12.5,
    monthlyUsage: 245,
    monthlyBudget: 300,
    trend: 'down' as const,
    trendPercent: 8
  };

  return (
    <div className="min-h-screen bg-background p-6">
      <header className="mb-8">
        <h1 className="text-3xl font-bold">Smart Home</h1>
        <p className="text-muted-foreground">Welcome back!</p>
      </header>

      <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
        {/* Main Area - Devices */}
        <div className="lg:col-span-2 space-y-6">
          <RoomView />
        </div>

        {/* Sidebar */}
        <div className="space-y-6">
          <SceneManager onExecute={executeScene} />

          {climateDevice && (
            <ClimateControl
              temperature={climateDevice.temperature || 21}
              humidity={50}
              setpoint={climateDevice.setpoint || 21}
              mode="auto"
              onSetpointChange={(value) =>
                updateDevice(climateDevice.id, { setpoint: value })
              }
              onModeChange={(mode) =>
                console.log('Mode:', mode)
              }
            />
          )}

          <EnergyMonitor data={energyData} />
        </div>
      </div>
    </div>
  );
}
```

---

## Fazit

Smart Home Dashboard mit React bietet:

1. **Real-time Updates**: WebSocket für Live-Daten
2. **Intuitive UI**: Room-basierte Navigation
3. **Responsive Design**: Desktop & Mobile
4. **Szenen & Automation**: One-Click Control

Das perfekte Interface für Smart Home Control.

---

## Bildprompts

1. "Smart home dashboard on tablet, dark mode with glowing device cards"
2. "Room view with connected devices, modern UI design"
3. "Energy monitoring graph with real-time power consumption"

---

## Quellen

- [React Documentation](https://react.dev/)
- [shadcn/ui Components](https://ui.shadcn.com/)
- [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
- [Home Assistant Dashboard](https://www.home-assistant.io/dashboards/)
