IKHSAN
EN Read in EnglishID

homeio

Publik

Fork dari doctor-io/homeio

Branch
main
Commit terakhir
958d74616 Oktober 2026

use-status-bar-data.ts

Buka raw
"use client";

import { useMemo } from "react";
import { useCurrentUser } from "@/hooks/useCurrentUser";
import { useDesktopPreferences } from "@/hooks/useDesktopPreferences";
import { useInstalledApps } from "@/modules/apps/hooks/useInstalledApps";
import { useNetworkStatus } from "@/modules/system/hooks/useNetworkStatus";
import { useSystemUpdateStatus } from "@/modules/system/hooks/useSystemUpdateStatus";
import { useSystemMetrics } from "@/modules/system/hooks/useSystemMetrics";
import { formatRelativeTime, safePercent } from "@/modules/system/components/status-bar/utils";
import { useStatusNotifications } from "@/modules/system/components/status-bar/use-status-notifications";
import { useNotifications } from "@/modules/system/hooks/useNotifications";
import type { Notification } from "@/modules/system/components/status-bar/types";
import type { NotificationRecord } from "@/lib/shared/contracts/notifications";

const MAX_MERGED_NOTIFICATIONS = 20;

/**
 * Persisted notifications and the client-side status ones are each ordered on
 * their own. Concatenating them produced two sorted runs end to end — app
 * events from yesterday sitting above a snapshot from minutes ago — so the
 * join has to be ordered too, not just the parts.
 */
export function mergeNotifications(
  persistent: NotificationRecord[],
  ephemeral: Notification[],
): Notification[] {
  const persistentIds = new Set(persistent.map((item) => item.id));
  const persistentMapped = persistent.map((item) => ({
    id: item.id,
    title: item.title,
    message: item.body,
    time: formatRelativeTime(item.createdAt),
    createdAt: item.createdAt,
    read: item.read,
  }));

  return [...persistentMapped, ...ephemeral.filter((item) => !persistentIds.has(item.id))]
    .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))
    .slice(0, MAX_MERGED_NOTIFICATIONS);
}

export function useStatusBarData() {
  const { data: metrics, isError: isMetricsError } = useSystemMetrics();
  const { data: networkStatus, isError: isNetworkError } = useNetworkStatus();
  const { data: apps } = useInstalledApps();
  const { data: currentUser } = useCurrentUser();
  const { notificationPreferences } = useDesktopPreferences();
  const { data: systemUpdates } = useSystemUpdateStatus();

  const serverName = metrics?.hostname ?? "ServerLab";
  const cpuPercent = safePercent(metrics?.cpu.normalizedPercent);
  const memoryPercent = safePercent(metrics?.memory.usedPercent);
  const diskPercent = safePercent(metrics?.storage?.usedPercent);
  const stoppedAppsCount = (apps ?? []).filter(
    (app) => app.status === "stopped" || app.status === "paused",
  ).length;

  const batteryPercent =
    typeof metrics?.battery.percent === "number"
      ? safePercent(metrics.battery.percent)
      : null;
  const batteryText = metrics?.battery.hasBattery ? `${batteryPercent ?? "--"}%` : "AC";

  const isWifiConnected = Boolean(
    networkStatus?.connected ?? metrics?.wifi.connected,
  );
  const isEthernet = Boolean(
    (networkStatus?.connected ?? metrics?.wifi.connected) &&
      !(networkStatus?.ssid ?? metrics?.wifi.ssid),
  );
  const showWifiError = (isMetricsError && isNetworkError) || !isWifiConnected;
  const wifiIconClassName = showWifiError
    ? "size-4 text-status-red"
    : "size-4 text-status-green";

  const { notifications: persistentNotifications, unreadCount: persistentUnreadCount, markAllRead: markPersistentAllRead, clearAll: clearPersistentAll } = useNotifications();

  const { notifications: ephemeralNotifications, unreadCount: ephemeralUnreadCount, markAllRead: markEphemeralAllRead, clearAll: clearEphemeralAll } = useStatusNotifications({
    metricsTimestamp: metrics?.timestamp ?? null,
    cpuPercent,
    memoryPercent,
    diskPercent,
    temperatureCelsius:
      typeof metrics?.temperature.mainCelsius === "number"
        ? metrics.temperature.mainCelsius
        : null,
    hostname: metrics?.hostname ?? "ServerLab",
    uptimeSeconds: metrics?.uptimeSeconds ?? 0,
    stoppedAppsCount,
    username: currentUser?.username,
    updateAvailable: Boolean(systemUpdates?.updateAvailable),
    latestVersion: systemUpdates?.latestVersion ?? null,
    preferences: notificationPreferences,
  });

  const mergedNotifications = useMemo(
    () => mergeNotifications(persistentNotifications, ephemeralNotifications),
    [persistentNotifications, ephemeralNotifications],
  );

  const mergedUnreadCount = persistentUnreadCount + ephemeralUnreadCount;

  function markAllRead() {
    markPersistentAllRead();
    markEphemeralAllRead();
  }

  function clearAll() {
    clearPersistentAll();
    clearEphemeralAll();
  }

  return useMemo(
    () => ({
      metrics,
      networkStatus,
      serverName,
      isDemoMode: currentUser?.isDemoMode ?? false,
      username: currentUser?.isDemoMode ? "homeio" : (currentUser?.username ?? null),
      batteryText,
      isWifiConnected,
      isEthernet,
      isMetricsError,
      wifiIconClassName,
      notifications: mergedNotifications,
      unreadCount: mergedUnreadCount,
      markAllRead,
      clearNotifications: clearAll,
    }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [
      batteryText,
      isMetricsError,
      networkStatus,
      isWifiConnected,
      isEthernet,
      metrics,
      mergedNotifications,
      mergedUnreadCount,
      serverName,
      currentUser?.username,
      currentUser?.isDemoMode,
      wifiIconClassName,
    ],
  );
}
byte
5.504
baris
148
Jenis
ts
Commit terakhir
16 Oktober 2026