Fixes sidebar logo and adds logo

This commit is contained in:
litoral05
2026-06-02 10:18:32 +01:00
parent ae15b8d3f6
commit bd8eef7f04
60 changed files with 84 additions and 544 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+1 -1
View File
@@ -12,7 +12,7 @@
"app": { "app": {
"windows": [ "windows": [
{ {
"title": "Litoral Central", "title": "Central LRX",
"width": 800, "width": 800,
"height": 600 "height": 600
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 KiB

+59 -37
View File
@@ -17,7 +17,7 @@ import {
Wind, Wind,
} from "lucide-react"; } from "lucide-react";
import logo from "../../assets/logo.png"; import logo from "../../assets/logo5.png";
import type { AppPage } from "../../app/App"; import type { AppPage } from "../../app/App";
type SidebarProps = { type SidebarProps = {
@@ -120,49 +120,65 @@ export function Sidebar({
<aside <aside
className={ className={
isDark isDark
? `${collapsed ? "w-20" : "w-[245px]"} flex h-full min-h-0 flex-col border-r border-[#263247] bg-[#0B1220] text-slate-100 transition-all duration-200` ? `${collapsed ? "w-20" : "w-[280px]"} flex h-full min-h-0 flex-col border-r border-[#263247] bg-[#0B1220] text-slate-100 transition-all duration-200`
: `${collapsed ? "w-20" : "w-[245px]"} flex h-full min-h-0 flex-col border-r border-[#D7DEE8] bg-[#F3F6FA] text-[#0F172A] transition-all duration-200` : `${collapsed ? "w-20" : "w-[280px]"} flex h-full min-h-0 flex-col border-r border-[#D7DEE8] bg-[#F3F6FA] text-[#0F172A] transition-all duration-200`
} }
> >
<div className="shrink-0 px-4 pt-5"> <div className={collapsed ? "shrink-0 px-3 pt-5" : "shrink-0 px-4 pt-5"}>
<div <div
className={ className={
collapsed collapsed
? "mb-6 flex items-center justify-center" ? "mb-7 flex justify-center"
: "mb-6 flex items-center gap-3 px-1" : isDark
? `relative mb-8 overflow-hidden ${RADIUS} border border-[#22314A] bg-[#101A2C] px-3.5 py-3.5`
: `relative mb-8 overflow-hidden ${RADIUS} border border-slate-200 bg-white px-3.5 py-3.5 shadow-sm`
} }
> >
<div
className={
isDark
? `${RADIUS} flex h-12 w-12 shrink-0 items-center justify-center border border-[#2A3950] bg-[#111A2B] p-1.5`
: `${RADIUS} flex h-12 w-12 shrink-0 items-center justify-center border border-[#CBD5E1] bg-white p-1.5`
}
>
<img
src={logo}
alt="Litoral Central"
className="h-full w-full object-contain"
/>
</div>
{!collapsed && ( {!collapsed && (
<div className="min-w-0 flex-1"> <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(79,209,197,0.18),transparent_42%)]" />
<div
className={
isDark
? "truncate text-[15px] font-extrabold tracking-[-0.02em] text-white"
: "truncate text-[15px] font-extrabold tracking-[-0.02em] text-[#0F172A]"
}
>
Litoral Central
</div>
<div className="mt-1 whitespace-nowrap text-[10px] font-bold uppercase tracking-[0.18em] text-[#7D8EA8]">
Operações agrícolas
</div>
</div>
)} )}
<div className={collapsed ? "relative flex justify-center" : "relative flex items-center gap-3"}>
<div
className={
isDark
? "flex h-12 w-12 shrink-0 items-center justify-center rounded-[6px] bg-[#0B1220] ring-1 ring-white/10"
: "flex h-12 w-12 shrink-0 items-center justify-center rounded-[6px] bg-slate-50 ring-1 ring-slate-200"
}
>
<img
src={logo}
alt="Litoral Regas"
className="h-14 w-14 object-contain"
/>
</div>
{!collapsed && (
<div className="min-w-0 flex-1">
<div
className={
isDark
? "truncate text-[18px] font-black leading-none tracking-[-0.04em] text-white"
: "truncate text-[18px] font-black leading-none tracking-[-0.04em] text-slate-950"
}
>
Central LRX
</div>
<div className="mt-2 flex items-center gap-2">
<span
className={
isDark
? "truncate text-[11px] font-semibold text-[#8FA3BF]"
: "truncate text-[11px] font-semibold text-slate-500"
}
>
CENTRO DE OPERAÇÕES
</span>
</div>
</div>
)}
</div>
</div> </div>
</div> </div>
@@ -443,8 +459,14 @@ function navButtonClass(isDark: boolean, active: boolean, collapsed: boolean) {
if (active) { if (active) {
return isDark return isDark
? `relative flex w-full items-center gap-3 ${RADIUS} border border-[#2C3D56] bg-[#131D2F] ${alignment} py-3.5 text-left text-[14px] font-extrabold text-white` ? `relative flex w-full items-center gap-3 ${RADIUS}
: `relative flex w-full items-center gap-3 ${RADIUS} border border-[#CBD5E1] bg-white ${alignment} py-3.5 text-left text-[14px] font-extrabold text-[#0F172A]`; ${alignment}
py-3.5 text-left text-[14px]
font-bold text-white`
: `relative flex w-full items-center gap-3 ${RADIUS}
${alignment}
py-3.5 text-left text-[14px]
font-bold text-[#0F172A]`;
} }
return isDark return isDark
@@ -1,105 +0,0 @@
import { Gauge, Thermometer, Waves } from "lucide-react";
import type { DashboardOverview } from "../../dashboard/types/DashboardOverview";
import { StatusPill } from "../../dashboard/components/StatusPill";
type Props = {
zones: DashboardOverview["climate"]["zones"];
};
function formatValue(value: number | null, unit: string, decimals = 1) {
if (value === null) return "--";
return `${value.toFixed(decimals)} ${unit}`;
}
export function DashboardClimateSection({ zones }: Props) {
return (
<section className="space-y-4">
<div>
<h2 className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Clima por Zona
</h2>
<p className="text-sm text-slate-500 dark:text-slate-400">
Temperatura, humidade, CO e estados principais dos equipamentos.
</p>
</div>
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
{zones.map((zone) => (
<div
key={zone.zoneNumber}
className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<div className="mb-4 flex items-center justify-between">
<h3 className="text-base font-semibold text-slate-900 dark:text-slate-50">
Zona {zone.zoneNumber}
</h3>
<div className="flex gap-2">
<StatusPill active={zone.fansOn} activeLabel="Vent." inactiveLabel="Vent." />
<StatusPill active={zone.extractorsOn} activeLabel="Extr." inactiveLabel="Extr." />
</div>
</div>
<div className="grid grid-cols-2 gap-3 md:grid-cols-3">
<MiniValue icon={Thermometer} label="Temperatura" value={formatValue(zone.temperature, "°C")} />
<MiniValue icon={Waves} label="Humidade" value={formatValue(zone.humidity, "%", 0)} />
<MiniValue icon={Gauge} label="CO₂" value={formatValue(zone.co2, "ppm", 0)} />
</div>
<div className="mt-5 grid grid-cols-2 gap-3 md:grid-cols-4">
<OpeningBar label="Zenital E" value={zone.zenitalLeftPercent} />
<OpeningBar label="Zenital D" value={zone.zenitalRightPercent} />
<OpeningBar label="Lateral E" value={zone.lateralLeftPercent} />
<OpeningBar label="Lateral D" value={zone.lateralRightPercent} />
</div>
</div>
))}
</div>
</section>
);
}
type MiniValueProps = {
icon: React.ElementType;
label: string;
value: string;
};
function MiniValue({ icon: Icon, label, value }: MiniValueProps) {
return (
<div className="rounded-xl bg-slate-50 p-3 dark:bg-slate-950">
<div className="mb-2 flex items-center gap-2 text-slate-500 dark:text-slate-400">
<Icon className="h-4 w-4" />
<span className="text-xs">{label}</span>
</div>
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
{value}
</p>
</div>
);
}
type OpeningBarProps = {
label: string;
value: number | null;
};
function OpeningBar({ label, value }: OpeningBarProps) {
const safeValue = value ?? 0;
return (
<div>
<div className="mb-1 flex justify-between text-xs text-slate-500 dark:text-slate-400">
<span>{label}</span>
<span>{value === null ? "--" : `${value.toFixed(0)}%`}</span>
</div>
<div className="h-2 rounded-full bg-slate-200 dark:bg-slate-800">
<div
className="h-2 rounded-full bg-blue-500"
style={{ width: `${safeValue}%` }}
/>
</div>
</div>
);
}
@@ -57,16 +57,20 @@ export function DashboardPage({ theme, onOpenMeteo, onNavigate }: DashboardPageP
<section className="grid grid-cols-[minmax(0,1fr)_340px] items-start gap-6 xl:grid-cols-[minmax(0,1fr)_360px] 2xl:grid-cols-[minmax(0,1fr)_380px]"> <section className="grid grid-cols-[minmax(0,1fr)_340px] items-start gap-6 xl:grid-cols-[minmax(0,1fr)_360px] 2xl:grid-cols-[minmax(0,1fr)_380px]">
<div className="min-w-0 max-w-[640px]"> <div className="min-w-0 max-w-[640px]">
<h1 className={isDark ? "text-[42px] font-black leading-[1.02] tracking-[-0.06em] text-white xl:text-[48px] 2xl:text-[54px]" : "text-[42px] font-black leading-[1.02] tracking-[-0.06em] text-[#0F172A] xl:text-[48px] 2xl:text-[54px]"}> <h1 className={isDark ? "text-[42px] font-black leading-[1.02] tracking-[-0.06em] text-white xl:text-[48px] 2xl:text-[54px]" : "text-[42px] font-black leading-[1.02] tracking-[-0.06em] text-[#0F172A] xl:text-[48px] 2xl:text-[54px]"}>
Bem-vindo ao Bem-vindo à
<br /> <br />
<span className={isDark ? "text-[#4FD1C5]" : "text-[#0F766E]"}>Litoral Central</span> <span className={isDark ? "text-[#4FD1C5]" : "text-[#0F766E]"}>
Central LRX
</span>
</h1> </h1>
<div className={isDark ? "mt-5 h-[2px] w-14 bg-[#4FD1C5]" : "mt-5 h-[2px] w-14 bg-[#0F766E]"} /> <div className={isDark ? "mt-5 h-[2px] w-14 bg-[#4FD1C5]" : "mt-5 h-[2px] w-14 bg-[#0F766E]"} />
<p className={isDark ? "mt-5 max-w-[560px] text-[14px] leading-6 text-[#A8B3C7] xl:text-[15px] xl:leading-7" : "mt-5 max-w-[560px] text-[14px] leading-6 text-slate-700 xl:text-[15px] xl:leading-7"}> <p className={isDark ? "mt-5 max-w-[560px] text-[14px] leading-6 text-[#A8B3C7] xl:text-[15px] xl:leading-7" : "mt-5 max-w-[560px] text-[14px] leading-6 text-slate-700 xl:text-[15px] xl:leading-7"}>
A sua plataforma inteligente para gestão de operações agrícolas. A plataforma central da Litoral Regas, onde dados, meteorologia,
Acompanhe as condições, controle os sistemas e maximize a eficiência no campo. rega e operações convergem num único sistema inteligente.
O <strong>LRX</strong> representa o ponto de ligação entre tecnologia,
decisão e eficiência agrícola.
</p> </p>
<button <button
@@ -1,388 +0,0 @@
import { useMemo, useState } from "react";
import { BarChart3, Table2, X } from "lucide-react";
import {
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import type { ModuleSensorResponse } from "../../../types/meteo";
import type {
AccumulatedBucket,
AccumulatedRange,
} from "../hooks/useAccumulatedHistory";
type Props = {
sensor: ModuleSensorResponse | null;
title: string;
theme: "dark" | "light";
buckets: AccumulatedBucket[];
loading: boolean;
range: AccumulatedRange;
onRangeChange: (range: AccumulatedRange) => void;
onClose: () => void;
};
const RADIUS = "rounded-[5px]";
const RANGE_OPTIONS: Array<{ label: string; value: AccumulatedRange }> = [
{ label: "7D", value: "7d" },
{ label: "30D", value: "30d" },
{ label: "Mês", value: "month" },
{ label: "Ano", value: "year" },
];
export function AccumulatedHistoryModal({
sensor,
title,
theme,
buckets,
loading,
range,
onRangeChange,
onClose,
}: Props) {
const isDark = theme === "dark";
const palette = accumulatedPalette(isDark);
const [mode, setMode] = useState<"chart" | "table">("chart");
const stats = useMemo(() => {
if (buckets.length === 0) {
return { total: 0, average: 0, max: 0 };
}
const values = buckets.map((bucket) => bucket.total);
return {
total: values.reduce((sum, value) => sum + value, 0),
average: values.reduce((sum, value) => sum + value, 0) / values.length,
max: Math.max(...values),
};
}, [buckets]);
if (!sensor) return null;
const unit = buckets[0]?.unit ?? sensor.unit ?? "";
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm">
<div
className={
isDark
? `${RADIUS} flex h-[82vh] w-full max-w-6xl flex-col overflow-hidden border border-white/10 bg-[#111827] text-slate-100 shadow-2xl`
: `${RADIUS} flex h-[82vh] w-full max-w-6xl flex-col overflow-hidden border border-slate-200 bg-white text-slate-950 shadow-[0_24px_70px_rgba(15,23,42,0.18)]`
}
>
<header className="flex items-start justify-between gap-5 px-6 py-4">
<div>
<p
className={
isDark
? "mb-1 text-[11px] font-bold uppercase tracking-[0.22em] text-slate-500"
: "mb-1 text-[11px] font-bold uppercase tracking-[0.22em] text-slate-400"
}
>
Acumulado
</p>
<h2 className="text-xl font-black tracking-[-0.03em]">
{title}
</h2>
<p className="mt-1 text-xs text-slate-500">
Chave: {sensor.key}
</p>
</div>
<button
type="button"
onClick={onClose}
className={
isDark
? `${RADIUS} p-2 text-slate-400 transition hover:bg-white/5 hover:text-white`
: `${RADIUS} p-2 text-slate-400 transition hover:bg-slate-100 hover:text-slate-900`
}
>
<X className="h-5 w-5" />
</button>
</header>
<main className="flex min-h-0 flex-1 flex-col px-6 pb-5">
<section
className={
isDark
? `${RADIUS} flex min-h-0 flex-1 flex-col border border-white/10 bg-[#0b1220] p-4`
: `${RADIUS} flex min-h-0 flex-1 flex-col border border-slate-200 bg-slate-50 p-4`
}
>
<div className="mb-4 flex flex-wrap items-center gap-2">
{RANGE_OPTIONS.map((option) => (
<button
key={option.value}
type="button"
onClick={() => onRangeChange(option.value)}
className={
range === option.value
? activeButtonClass(isDark)
: buttonClass(isDark)
}
>
{option.label}
</button>
))}
<div className="ml-auto flex gap-2">
<button
type="button"
onClick={() => setMode("chart")}
className={toggleButtonClass(isDark, mode === "chart")}
>
<BarChart3 className="mr-2 inline h-4 w-4" />
Gráfico
</button>
<button
type="button"
onClick={() => setMode("table")}
className={toggleButtonClass(isDark, mode === "table")}
>
<Table2 className="mr-2 inline h-4 w-4" />
Tabela
</button>
</div>
</div>
<div className="mb-4 grid grid-cols-1 gap-3 sm:grid-cols-3">
<StatCard theme={theme} label="Total" value={formatValue(stats.total, unit)} />
<StatCard theme={theme} label="Média" value={formatValue(stats.average, unit)} />
<StatCard theme={theme} label="Máximo" value={formatValue(stats.max, unit)} />
</div>
<div className="min-h-0 flex-1">
{loading ? (
<EmptyState>A carregar acumulados...</EmptyState>
) : buckets.length === 0 ? (
<EmptyState>Sem dados acumulados para este período.</EmptyState>
) : mode === "chart" ? (
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={buckets}
margin={{ top: 16, right: 18, bottom: 8, left: 0 }}
>
<CartesianGrid
stroke={palette.grid}
strokeDasharray="4 6"
vertical={false}
/>
<XAxis
dataKey="label"
tick={{ fill: palette.axis, fontSize: 11 }}
tickLine={false}
axisLine={{ stroke: palette.axisLine }}
minTickGap={20}
/>
<YAxis
tick={{ fill: palette.axis, fontSize: 11 }}
tickLine={false}
axisLine={{ stroke: palette.axisLine }}
width={56}
/>
<Tooltip
cursor={{ fill: palette.cursor }}
contentStyle={{
background: palette.tooltipBg,
border: `1px solid ${palette.tooltipBorder}`,
borderRadius: "5px",
color: palette.tooltipText,
boxShadow: isDark
? "0 18px 45px rgba(0,0,0,0.30)"
: "0 18px 45px rgba(15,23,42,0.14)",
}}
formatter={(value) => [
formatValue(Number(value), unit),
"Acumulado",
]}
/>
<Bar
dataKey="total"
fill={palette.bar}
radius={[3, 3, 0, 0]}
isAnimationActive={false}
/>
</BarChart>
</ResponsiveContainer>
) : (
<div
className={
isDark
? `${RADIUS} h-full overflow-auto border border-white/10 bg-[#111827]`
: `${RADIUS} h-full overflow-auto border border-slate-200 bg-white`
}
>
<table className="w-full text-left text-sm">
<thead
className={
isDark
? "sticky top-0 bg-[#111827] text-xs uppercase tracking-[0.14em] text-slate-500"
: "sticky top-0 bg-slate-50 text-xs uppercase tracking-[0.14em] text-slate-500"
}
>
<tr>
<th className="px-4 py-3">Período</th>
<th className="px-4 py-3">Início</th>
<th className="px-4 py-3">Fim</th>
<th className="px-4 py-3 text-right">Total</th>
</tr>
</thead>
<tbody>
{buckets.map((bucket) => (
<tr
key={`${bucket.from}-${bucket.to}`}
className={
isDark
? "border-t border-white/10 text-slate-300"
: "border-t border-slate-200 text-slate-700"
}
>
<td className="px-4 py-3 font-semibold">
{bucket.label}
</td>
<td className="px-4 py-3 text-slate-500">
{formatDate(bucket.from)}
</td>
<td className="px-4 py-3 text-slate-500">
{formatDate(bucket.to)}
</td>
<td
className={
isDark
? "px-4 py-3 text-right font-black text-slate-100"
: "px-4 py-3 text-right font-black text-slate-950"
}
>
{formatValue(bucket.total, unit)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</section>
</main>
</div>
</div>
);
}
function accumulatedPalette(isDark: boolean) {
return isDark
? {
bar: "#94a3b8",
grid: "rgba(148,163,184,0.16)",
axis: "#64748b",
axisLine: "rgba(148,163,184,0.18)",
cursor: "rgba(148,163,184,0.08)",
tooltipBg: "#111827",
tooltipBorder: "rgba(255,255,255,0.10)",
tooltipText: "#e5e7eb",
}
: {
bar: "#475569",
grid: "#e2e8f0",
axis: "#64748b",
axisLine: "#cbd5e1",
cursor: "rgba(148,163,184,0.12)",
tooltipBg: "#ffffff",
tooltipBorder: "#e2e8f0",
tooltipText: "#0f172a",
};
}
function EmptyState({ children }: { children: string }) {
return (
<div className="flex h-full items-center justify-center text-sm text-slate-400">
{children}
</div>
);
}
function StatCard({
theme,
label,
value,
}: {
theme: "dark" | "light";
label: string;
value: string;
}) {
const isDark = theme === "dark";
return (
<div
className={
isDark
? `${RADIUS} border border-white/10 bg-[#111827] p-3`
: `${RADIUS} border border-slate-200 bg-white p-3`
}
>
<p className="text-xs text-slate-500">{label}</p>
<p
className={
isDark
? "mt-1 text-xl font-black text-slate-100"
: "mt-1 text-xl font-black text-slate-950"
}
>
{value}
</p>
</div>
);
}
function buttonClass(isDark: boolean) {
return isDark
? `${RADIUS} border border-white/10 bg-white/[0.03] px-3 py-2 text-xs font-semibold text-slate-300 transition hover:bg-white/[0.06] hover:text-slate-100`
: `${RADIUS} border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-600 transition hover:bg-slate-100 hover:text-slate-950`;
}
function activeButtonClass(isDark: boolean) {
return isDark
? `${RADIUS} border border-white/10 bg-slate-200 px-3 py-2 text-xs font-black text-slate-950`
: `${RADIUS} border border-slate-300 bg-slate-900 px-3 py-2 text-xs font-black text-white`;
}
function toggleButtonClass(isDark: boolean, active: boolean) {
if (active) return activeButtonClass(isDark);
return buttonClass(isDark);
}
function formatValue(value: number, unit: string) {
if (unit === "Wh/m²" && value >= 1000) {
return `${(value / 1000).toFixed(2)} kWh/m²`;
}
return `${value.toFixed(1)}${unit ? ` ${unit}` : ""}`;
}
function formatDate(value: string) {
return new Date(value).toLocaleString("pt-PT", {
day: "2-digit",
month: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
+7 -9
View File
@@ -1246,13 +1246,15 @@ function RealtimeChartPanel({
const hasAnyChartData = chart.variables.some( const hasAnyChartData = chart.variables.some(
(variable) => variable.data.length > 0, (variable) => variable.data.length > 0,
); );
const isDark = theme === "dark";
return ( return (
<div className={theme === "dark" ? "relative flex min-h-0 flex-1 flex-col text-slate-100" : "relative flex min-h-0 flex-1 flex-col text-slate-950"}> <section className={`${panelClass(isDark)} relative min-h-0 p-0`}>
<div className="absolute right-3 top-3 z-20"> <div className="absolute right-3 top-3 z-20">
<button <button
type="button" type="button"
className={ className={
theme === "dark" isDark
? "rounded-[5px] border border-sky-400/20 bg-sky-400/10 px-3 py-2 text-xs font-black text-sky-200 transition hover:bg-sky-400/15" ? "rounded-[5px] border border-sky-400/20 bg-sky-400/10 px-3 py-2 text-xs font-black text-sky-200 transition hover:bg-sky-400/15"
: "rounded-[5px] border border-sky-300 bg-sky-50 px-3 py-2 text-xs font-black text-sky-700 transition hover:bg-sky-100" : "rounded-[5px] border border-sky-300 bg-sky-50 px-3 py-2 text-xs font-black text-sky-700 transition hover:bg-sky-100"
} }
@@ -1274,7 +1276,7 @@ function RealtimeChartPanel({
onIntervalChange={setInterval} onIntervalChange={setInterval}
/> />
</CompactMeteoChart> </CompactMeteoChart>
</div> </section>
); );
} }
@@ -1293,13 +1295,9 @@ function chartColor(accent: Accent) {
} }
} }
function CompactMeteoChart({ function CompactMeteoChart({ children }: { children: React.ReactNode }) {
children,
}: {
children: React.ReactNode;
}) {
return ( return (
<div className="h-full [&>section]:h-full [&>section>header]:px-4 [&>section>header]:py-3 [&>section>main]:px-3 [&>section>main]:pb-3 [&_main_section>div.relative]:h-[235px] [&_main_section>div.relative]:min-h-[235px]"> <div className="h-full [&>section]:h-full [&>section]:border-0 [&>section]:bg-transparent [&>section]:shadow-none [&>section>header]:px-4 [&>section>header]:py-3 [&>section>main]:px-3 [&>section>main]:pb-3 [&_main_section>div.relative]:h-[235px] [&_main_section>div.relative]:min-h-[235px]">
{children} {children}
</div> </div>
); );