feat: update dashboard table headers

This commit is contained in:
miguels44
2026-04-16 10:32:39 -03:00
parent f1ab30161f
commit b160df6f76
8 changed files with 584 additions and 79 deletions
+18 -26
View File
@@ -8,9 +8,10 @@
"name": "graphics-analitics", "name": "graphics-analitics",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"lucide-react": "^1.8.0",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-router-dom": "^7.14.0" "react-router-dom": "^7.14.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.4", "@eslint/js": "^9.39.4",
@@ -735,9 +736,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -755,9 +753,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -775,9 +770,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -795,9 +787,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -815,9 +804,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -835,9 +821,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2458,6 +2441,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lucide-react": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz",
"integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2942,9 +2934,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.14.0", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz",
"integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", "integrity": "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^1.0.1", "cookie": "^1.0.1",
@@ -2964,12 +2956,12 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "7.14.0", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.1.tgz",
"integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==", "integrity": "sha512-ZkrQuwwhGibjQLqH1eCdyiZyLWglPxzxdl5tgwgKEyCSGC76vmAjleGocRe3J/MLfzMUIKwaFJWpFVJhK3d2xA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"react-router": "7.14.0" "react-router": "7.14.1"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
+2 -1
View File
@@ -10,9 +10,10 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"lucide-react": "^1.8.0",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-router-dom": "^7.14.0" "react-router-dom": "^7.14.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.4", "@eslint/js": "^9.39.4",
+2
View File
@@ -4,12 +4,14 @@ import Home from "./pages/home";
import Login from "./pages/login"; import Login from "./pages/login";
import About from "./pages/about"; import About from "./pages/about";
import Header from "./components/header"; import Header from "./components/header";
import MainPage from "./pages/mainPage"
const App = () => ( const App = () => (
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center" }}>
<Header /> <Header />
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/mainPage" element={<MainPage />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/about" element={<About />} /> <Route path="/about" element={<About />} />
</Routes> </Routes>
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+1 -1
View File
@@ -7,7 +7,7 @@ const Header = () => {
<header className="sm:h-32 flex items-center z-30 w-full dark:bg-gray-800 bg-white relative overflow-hidden h-screen"> <header className="sm:h-32 flex items-center z-30 w-full dark:bg-gray-800 bg-white relative overflow-hidden h-screen">
<div className="container mx-auto px-6 flex items-center justify-between"> <div className="container mx-auto px-6 flex items-center justify-between">
<div className="uppercase text-gray-800 dark:text-white font-black text-3xl"> <div className="uppercase text-gray-800 dark:text-white font-black text-3xl">
Watch.ME Graphics Analytics
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<nav className="font-sen text-gray-800 dark:text-white uppercase text-lg lg:flex items-center hidden"> <nav className="font-sen text-gray-800 dark:text-white uppercase text-lg lg:flex items-center hidden">
+36 -27
View File
@@ -1,48 +1,57 @@
import React from "react"; import React from "react"
import { Link } from "react-router-dom"
import graficoExemplo from "../assets/grafico-exemplo.png"
function Home() { function Home() {
return ( return (
<> <>
<main className="dark:bg-gray-800 bg-white relative overflow-hidden h-screen"> <main className="dark:bg-slate-900 bg-slate-50 relative overflow-hidden h-screen font-sans">
<div className="bg-white dark:bg-gray-800 flex relative z-20 items-center overflow-hidden"> <div className="bg-slate-50 dark:bg-slate-900 flex relative z-20 items-center overflow-hidden h-full">
<div className="container mx-auto px-6 flex relative py-16"> <div className="container mx-auto px-6 flex relative py-16">
<div className="sm:w-2/3 lg:w-2/5 flex flex-col relative z-20"> <div className="sm:w-2/3 lg:w-2/5 flex flex-col relative z-20">
<span className="w-20 h-2 bg-gray-800 dark:bg-white mb-12"></span> <span className="w-20 h-1 bg-blue-600 mb-8 rounded-full"></span>
<h1 className="font-bebas-neue uppercase text-6xl sm:text-8xl font-black flex flex-col leading-none dark:text-white text-gray-800"> <h1 className="uppercase text-5xl sm:text-7xl font-black flex flex-col leading-tight dark:text-white text-slate-900 tracking-tight">
Be on Dados em
<span className="text-5xl sm:text-7xl">Time</span> <span className="text-blue-600">Tempo Real</span>
</h1> </h1>
<p className="text-sm sm:text-base text-gray-700 dark:text-white">
Dimension of reality that makes change possible and <p className="text-sm sm:text-base text-slate-600 dark:text-slate-300 mt-6 leading-relaxed">
understandable. An indefinite and homogeneous environment in A plataforma definitiva para receber, armazenar e visualizar
which natural events and human existence take place. dados dos seus dispositivos inteligentes via API. Gere gráficos
instantâneos e monitore suas Trends com endpoints seguros.
</p> </p>
<div className="flex mt-8">
<a <div className="flex mt-10 space-x-4">
href="#" <Link
className="uppercase py-2 px-4 rounded-lg bg-pink-500 border-2 border-transparent text-white text-md mr-4 hover:bg-pink-400" to="/login"
> className="uppercase py-3 px-6 rounded-lg bg-blue-600 border-2 border-transparent text-white text-sm font-bold shadow-lg shadow-blue-500/30 hover:bg-blue-700 transition-all">
Get started Acessar Plataforma
</a> </Link>
<a <Link
href="#" to="/about"
className="uppercase py-2 px-4 rounded-lg bg-transparent border-2 border-pink-500 text-pink-500 dark:text-white hover:bg-pink-500 hover:text-white text-md" className="uppercase py-3 px-6 rounded-lg bg-transparent border-2 border-slate-300 text-slate-700 dark:text-white dark:border-slate-700 hover:border-blue-600 hover:text-blue-600 dark:hover:text-blue-400 transition-all text-sm font-bold">
> Como Funciona
Read more </Link>
</a>
</div> </div>
</div> </div>
<div className="hidden sm:block sm:w-1/3 lg:w-3/5 relative"> <div className="hidden sm:block sm:w-1/3 lg:w-3/5 relative">
<p className="text-slate-500 font-medium">
Exemplificação do Dashboard
</p>
<div className="w-full h-80 bg-slate-200 dark:bg-slate-800 rounded-2xl shadow-2xl border border-slate-300 dark:border-slate-700 flex items-center justify-center">
<img <img
src="https://www.tailwind-kit.com/images/object/10.png" src={graficoExemplo}
className="max-w-xs md:max-w-sm m-auto" alt="Exemplo de Gráfico"
className="w-full h-full rounded-2xl"
/> />
</div> </div>
</div> </div>
</div> </div>
</div>
</main> </main>
</> </>
); )
} }
export default Home; export default Home;
+162 -7
View File
@@ -1,10 +1,165 @@
import React from "react"; import React, { useState } from "react";
import { Eye, EyeOff } from "lucide-react";
import { useNavigate } from 'react-router-dom'
const Login = () => ( export default function Login() {
<div> const [email, setEmail] = useState("")
<h1>Login</h1> const [password, setPassword] = useState("")
<p>This is the Login page of GeeksForGeeks.</p> const [showPassword, setShowPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const navigate = useNavigate()
const handleLogin = async (e) => {
e.preventDefault()
setIsLoading(true)
setTimeout(() => {
setIsLoading(false)
console.log("Login simulado com email:", email, "e senha:", password)
navigate("/mainPage")
}, 1500)
}
return (
<div className="min-h-screen bg-slate-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8 font-sans">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="mt-6 text-center text-3xl font-extrabold text-slate-100">
Acesse sua conta
</h2>
<p className="mt-2 text-center text-sm text-slate-600">
Ou{" "}
<a
href="/#"
className="font-medium text-blue-600 hover:text-blue-500 transition-colors"
>
crie sua conta
</a>
</p>
</div> </div>
);
export default Login; <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow-xl sm:rounded-xl sm:px-10 border border-slate-200">
<form className="space-y-6" onSubmit={handleLogin}>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-slate-700"
>
Endereço de e-mail
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="appearance-none block w-full px-3 py-2 border border-slate-300 rounded-lg shadow-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm transition-shadow"
placeholder="seu@email.com"
/>
</div>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-slate-700"
>
Senha
</label>
<div className="mt-1 relative">
<input
id="password"
name="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none block w-full pr-10 px-3 py-2 border border-slate-300 rounded-lg shadow-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm transition-shadow"
placeholder="digite sua senha"
type={showPassword ? "text" : "password"}
/>
<button
type="button"
onClick={() => setShowPassword((prev) => !prev)}
className="absolute inset-y-0 right-0 flex items-center px-3 text-gray-500"
>
{showPassword ? (
<EyeOff className="w-5 h-5 " />
) : (
<Eye className="w-5 h-5 " />
)}
</button>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center">
<input
id="remember-me"
name="remember-me"
type="checkbox"
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-slate-300 rounded cursor-pointer"
/>
<label
htmlFor="remember-me"
className="ml-2 block text-sm text-slate-900 cursor-pointer">
Lembrar-me
</label>
</div>
<div className="text-sm">
<a
href="/forgot-password"
className="font-medium text-blue-600 hover:text-blue-500 transition-colors">
Esqueceu a senha?
</a>
</div>
</div>
<div>
<button
type="submit"
disabled={isLoading}
className={`w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white ${
isLoading
? "bg-blue-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700 focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
} transition-all`}
>
{isLoading ? (
<span className="flex items-center">
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
Autenticando...
</span>
) : (
"Entrar na plataforma"
)}
</button>
</div>
</form>
</div>
</div>
</div>
)
}
+346
View File
@@ -0,0 +1,346 @@
import React, { useState } from "react";
export default function Dashboard() {
// Estado para simular o feedback de cópia da URL
const [copiedId, setCopiedId] = useState(null);
// Mock de dados simulando o retorno da API
const [trends] = useState([
{
id: "trend_1a2b",
name: "Sensor_Temperatura_Estufa",
status: "online",
lastReading: "24.5°C",
lastUpdate: "2 min atrás",
endpoint: "https://api.dados.com/dweet/trend_1a2b",
},
{
id: "trend_3c4d",
name: "Sensor_Umidade_Solo_B4",
status: "offline",
lastReading: "78% UR",
lastUpdate: "35 min atrás",
endpoint: "https://api.dados.com/dweet/trend_3c4d",
},
{
id: "trend_5e6f",
name: "Vazão_Água_Entrada_H8",
status: "pending",
lastReading: "---",
lastUpdate: "Nunca pareado",
endpoint: "https://api.dados.com/dweet/trend_5e6f",
},
]);
// Função para copiar a URL para a área de transferência
const handleCopyUrl = (url, id) => {
navigator.clipboard.writeText(url);
setCopiedId(id);
setTimeout(() => setCopiedId(null), 2000);
};
const renderStatusDot = (status) => {
switch (status) {
case "online":
return (
<span className="w-2.5 h-2.5 rounded-full bg-emerald-500 animate-pulse"></span>
);
case "offline":
return <span className="w-2.5 h-2.5 rounded-full bg-amber-500"></span>;
default:
return <span className="w-2.5 h-2.5 rounded-full bg-slate-400"></span>;
}
};
return (
<div className="flex h-screen bg-slate-50 font-sans overflow-hidden">
<aside className="w-64 bg-slate-900 text-slate-300 flex flex-col shadow-2xl z-20 hidden md:flex">
<div className="h-16 flex items-center px-6 border-b border-slate-800">
<span className="text-white text-lg font-bold tracking-wide">
Plataforma
</span>
<div className="h-8 w-8 bg-blue-600 rounded flex items-center justify-center mr-3">
<span className="text-white font-bold text-sm">IoT</span>
</div>
</div>
<nav className="flex-1 px-4 py-6 space-y-2">
<a
href="#"
className="flex items-center px-4 py-3 bg-blue-600/10 text-blue-400 rounded-lg transition-colors">
<svg
className="w-5 h-5 mr-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
</path>
</svg>
Meus Dispositivos
</a>
<a
href="#"
className="flex items-center px-4 py-3 hover:bg-slate-800 hover:text-white rounded-lg transition-colors">
<svg
className="w-5 h-5 mr-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z">
</path>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z">
</path>
</svg>
Configurações API
</a>
</nav>
<div className="p-4 border-t border-slate-800">
<div className="flex items-center">
<div className="w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-white font-bold text-sm">
U
</div>
<div className="ml-3">
<p className="text-sm font-medium text-white">Usuário TI</p>
<p className="text-xs text-slate-500">Sair da conta</p>
</div>
</div>
</div>
</aside>
<main className="flex-1 flex flex-col h-screen overflow-hidden relative">
<header className="h-16 bg-white border-b border-slate-200 flex items-center justify-between px-8 shadow-sm">
<h1 className="text-2xl font-bold text-slate-800">
Meus Dispositivos{" "}
<span className="text-slate-400 font-normal text-xl">(Trends)</span>
</h1>
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors shadow-sm flex items-center">
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 4v16m8-8H4"
></path>
</svg>
Nova Trend
</button>
</header>
<div className="flex-1 overflow-y-auto p-8">
{/* Widgets de Resumo Global */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm flex items-center">
<div className="p-3 rounded-lg bg-blue-50 text-blue-600 mr-4">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10">
</path>
</svg>
</div>
<div>
<p className="text-sm text-slate-500 font-medium">
Total de Trends
</p>
<p className="text-2xl font-bold text-slate-800">
{trends.length}
</p>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm flex items-center">
<div className="p-3 rounded-lg bg-emerald-50 text-emerald-500 mr-4">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z">
</path>
</svg>
</div>
<div>
<p className="text-sm text-slate-500 font-medium">
Dispositivos Online
</p>
<p className="text-2xl font-bold text-slate-800">
{trends.filter((t) => t.status === "online").length}
</p>
</div>
</div>
</div>
{/* Tabela das trensds */}
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50 border-b border-slate-200 text-xs uppercase tracking-wider text-slate-500 font-semibold">
<th className="p-4 pl-6">Status</th>
<th className="p-4">Identificação</th>
<th className="p-4">Última Leitura</th>
<th className="p-4">Endpoint API</th>
<th className="p-4 text-center">Ações</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{trends.map((trend) => (
<tr
key={trend.id}
className="hover:bg-slate-50/50 transition-colors group">
{/* Status da trend*/}
<td className="p-4 pl-6 align-top">
<div className="flex items-center space-x-2 mt-1">
{renderStatusDot(trend.status)}
<span className="text-sm font-medium text-slate-700 capitalize">
{trend.status}
</span>
</div>
<span className="text-xs text-slate-400 block ml-4 mt-0.5">
{trend.lastUpdate}
</span>
</td>
{/* Identificação da trend */}
<td className="p-4 align-top">
<p className="text-sm font-semibold text-slate-800">
{trend.name}
</p>
<p className="text-xs text-slate-500 mt-0.5 font-mono">
ID: {trend.id}
</p>
</td>
{/* Leitura do último dado da trend */}
<td className="p-4 align-top">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium ${trend.status === "pending" ? "bg-slate-100 text-slate-500" : "bg-blue-50 text-blue-700"}`}>
{trend.lastReading}
</span>
</td>
{/* Endpoint da API */}
<td className="p-4 align-top">
<div className="flex items-center bg-slate-50 border border-slate-200 rounded-lg p-1 max-w-sm">
<code className="text-xs text-slate-600 truncate px-2 flex-1 font-mono">
{trend.endpoint}
</code>
<button
onClick={() =>
handleCopyUrl(trend.endpoint, trend.id)
}
className="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-white rounded-md transition-all ml-1 border border-transparent hover:border-slate-200 shadow-sm"
title="Copiar URL">
{copiedId === trend.id ? (
<svg
className="w-4 h-4 text-emerald-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 13l4 4L19 7">
</path>
</svg>
) : (
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
</path>
</svg>
)}
</button>
</div>
</td>
<td className="p-4 align-top text-center space-x-2">
<button
className="p-2 text-slate-400 hover:text-blue-600 transition-colors"
title="Visualizar Gráfico">
<svg
className="w-5 h-5 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z">
</path>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</path>
</svg>
</button>
<button
className="p-2 text-slate-400 hover:text-slate-700 transition-colors"
title="Configurações">
<svg
className="w-5 h-5 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z">
</path>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z">
</path>
</svg>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
)
}