Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions new-frontend/frontend/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// postcss.config.js
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
63 changes: 63 additions & 0 deletions new-frontend/frontend/src/components/DatasetCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Link } from "react-router-dom";
import StatusBadge from "./StatusBadge";
import Icon from "./Icon";

const GRADIENT = {
temperature: "from-teal-600 to-teal-500",
humidity: "from-orange-600 to-amber-500",
air: "from-violet-600 to-fuchsia-500",
default: "from-slate-600 to-slate-500",
};

export default function DatasetCard({ dataset }) {
const {
id, // string (e.g., "temp-sensor-01")
name, // display name
description, // short text
type, // "temperature" | "humidity" | "air" | ...
status, // "live" | "offline" | "degraded"
anomalies24h, // number
gradient, // optional override
} = dataset;

const g = gradient || GRADIENT[type] || GRADIENT.default;

return (
<Link
to={`/dashboard/${encodeURIComponent(id)}`}
className="group rounded-2xl overflow-hidden bg-white shadow-md transition-all hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-4 focus:ring-indigo-300"
>
<div className={`bg-gradient-to-r ${g} text-white`}>
<div className="flex items-center justify-between p-6">
<div className="flex items-center gap-3">
<span className="grid h-10 w-10 place-items-center rounded-xl bg-white/15">
<Icon name={type} />
</span>
<div>
<div className="text-[11px] uppercase tracking-wide opacity-90">Dataset</div>
<h3 className="text-2xl font-semibold">{name}</h3>
</div>
</div>
<StatusBadge status={status} />
</div>
<div className="h-2 bg-white/25" />
</div>
<div className="p-6 divide-y divide-gray-100">
<div className="pb-4">
<p className="text-slate-600">{description}</p>
</div>
<div className="py-4 flex items-center justify-between">
<span className="text-sm text-slate-500">Last 24h anomalies</span>
<span className={`rounded-full px-2.5 py-1 text-sm font-semibold ${anomalies24h > 0 ? "bg-rose-50 text-rose-600" : "bg-emerald-50 text-emerald-600"}`}>
{anomalies24h ?? 0}
</span>
</div>
<div className="pt-4">
<span className="inline-flex items-center gap-1 text-indigo-600 font-medium group-hover:translate-x-0.5 transition-transform">
View dashboard →
</span>
</div>
</div>
</Link>
);
}
38 changes: 38 additions & 0 deletions new-frontend/frontend/src/components/DatasetsGrid.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import DatasetCard from "./DatasetCard";
import Section from "./Section";

function Skeleton() {
return (
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
{[...Array(3)].map((_, i) => (
<div key={i} className="rounded-2xl overflow-hidden bg-white shadow-md">
<div className="h-20 bg-slate-200 animate-pulse" />
<div className="p-6 space-y-3">
<div className="h-4 w-1/3 bg-slate-200 animate-pulse" />
<div className="h-4 w-2/3 bg-slate-200 animate-pulse" />
<div className="h-8 w-24 bg-slate-200 animate-pulse" />
</div>
</div>
))}
</div>
);
}

export default function DatasetsGrid({ datasets = [], loading = false, error = null }) {
return (
<Section id="datasets" title="Available Sensor Datasets" center className="pb-16">
{error && (
<div className="mb-6 rounded-lg bg-rose-50 p-4 text-rose-700">
Could not load datasets. Please try again.
</div>
)}
{loading ? (
<Skeleton />
) : (
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
{datasets.map((ds) => <DatasetCard key={ds.id} dataset={ds} />)}
</div>
)}
</Section>
);
}
9 changes: 9 additions & 0 deletions new-frontend/frontend/src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function Footer({ note = "Built with React & Tailwind" }) {
return (
<footer className="border-t border-gray-200/70 bg-white">
<div className="mx-auto max-w-7xl px-6 py-6 text-sm text-slate-500">
© {new Date().getFullYear()} Intelligent IoT · {note}
</div>
</footer>
);
}
19 changes: 19 additions & 0 deletions new-frontend/frontend/src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function Header({ brand = "Intelligent IoT", nav = [] }) {
return (
<header className="sticky top-0 z-20 border-b border-gray-200/70 bg-white/80 backdrop-blur">
<div className="mx-auto flex max-w-7xl items-center justify-between px-6 py-3">
<div className="flex items-center gap-2">
<span className="inline-grid h-7 w-7 place-items-center rounded bg-gradient-to-tr from-indigo-600 to-blue-500 text-white text-xs font-bold">
IoT
</span>
<span className="text-base md:text-lg font-semibold tracking-wide">{brand}</span>
</div>
<nav className="hidden gap-6 text-sm md:flex">
{nav.map((n) => (
<a key={n.href} href={n.href} className="hover:text-indigo-600">{n.label}</a>
))}
</nav>
</div>
</header>
);
}
14 changes: 14 additions & 0 deletions new-frontend/frontend/src/components/Icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function Thermometer(props){return(<svg viewBox="0 0 24 24" fill="none" {...props}><path d="M10 14.5V5a2 2 0 1 1 4 0v9.5a4 4 0 1 1-4 0Z" stroke="currentColor" strokeWidth="1.8"/><path d="M12 12v4.5" stroke="currentColor" strokeWidth="1.8"/></svg>)}
function Droplet(props){return(<svg viewBox="0 0 24 24" fill="none" {...props}><path d="M12 3s6 6.5 6 10a6 6 0 1 1-12 0c0-3.5 6-10 6-10Z" stroke="currentColor" strokeWidth="1.8"/></svg>)}
function Air(props){return(<svg viewBox="0 0 24 24" fill="none" {...props}><path d="M3 8h11a3 3 0 1 0-3-3" stroke="currentColor" strokeWidth="1.8"/><path d="M3 12h15a3 3 0 1 1-3 3" stroke="currentColor" strokeWidth="1.8"/><path d="M3 16h6" stroke="currentColor" strokeWidth="1.8"/></svg>)}

const ICONS = {
temperature: Thermometer,
humidity: Droplet,
air: Air,
};

export default function Icon({ name, className = "h-6 w-6" }) {
const Comp = ICONS[name] || Air; // fallback generic
return <Comp className={className} />;
}
10 changes: 10 additions & 0 deletions new-frontend/frontend/src/components/Section.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function Section({ id, title, center = false, className = "", children }) {
return (
<section id={id} className={`mx-auto max-w-7xl px-6 ${className}`}>
{title && (
<h2 className={`${center ? "text-center" : ""} mb-8 text-2xl font-semibold`}>{title}</h2>
)}
{children}
</section>
);
}
13 changes: 13 additions & 0 deletions new-frontend/frontend/src/components/StatusBadge.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default function StatusBadge({ status = "offline" }) {
const palette = {
live: { pill: "bg-emerald-100 text-emerald-700", dot: "bg-emerald-600", text: "Live" },
offline:{ pill: "bg-rose-100 text-rose-700", dot: "bg-rose-600", text: "Offline" },
degraded:{ pill:"bg-amber-100 text-amber-800", dot: "bg-amber-600", text: "Degraded" },
};
const s = palette[status] || palette.offline;
return (
<span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${s.pill}`}>
<span className={`h-1.5 w-1.5 rounded-full ${s.dot}`} /> {s.text}
</span>
);
}
29 changes: 29 additions & 0 deletions new-frontend/frontend/src/data/datasets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const datasets = [
{
id: "temp-01",
name: "Sensor 1",
description: "Temperature readings",
type: "temperature",
status: "live",
anomalies24h: 2,
gradient: "from-teal-600 to-teal-500",
},
{
id: "hum-02",
name: "Sensor 2",
description: "Humidity monitoring",
type: "humidity",
status: "live",
anomalies24h: 0,
gradient: "from-orange-600 to-amber-500",
},
{
id: "aqi-03",
name: "Sensor 3",
description: "Air quality index",
type: "air",
status: "offline",
anomalies24h: 5,
gradient: "from-violet-600 to-fuchsia-500",
},
];
31 changes: 31 additions & 0 deletions new-frontend/frontend/src/hooks/useDatasets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } from "react";
import { datasets as seed } from "../data/datasets";

export default function useDatasets() {
const [datasets, setDatasets] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
let cancelled = false;

(async () => {
try {
// Try real API here later; for now just use seed
await new Promise(r => setTimeout(r, 120)); // optional tiny delay for skeleton
if (!cancelled) {
setDatasets(seed);
setError(null);
}
} catch (e) {
if (!cancelled) setError(e);
} finally {
if (!cancelled) setLoading(false);
}
})();

return () => { cancelled = true; };
}, []);

return { datasets, loading, error };
}
69 changes: 1 addition & 68 deletions new-frontend/frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -1,68 +1 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@import "tailwindcss";
43 changes: 23 additions & 20 deletions new-frontend/frontend/src/pages/HomePage.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { Link } from 'react-router-dom';
import Header from "../components/Header";
import Section from "../components/Section";
import DatasetsGrid from "../components/DatasetsGrid";
import Footer from "../components/Footer";
import useDatasets from "../hooks/useDatasets";

const datasets = [
{ id: 'sensor1', name: 'Sensor 1' },
{ id: 'sensor2', name: 'Sensor 2' },
{ id: 'sensor3', name: 'Sensor 3' },
];
export default function HomePage() {
const { datasets, loading, error } = useDatasets();

const HomePage = () => (
<div>
<h2>Available Sensor Datasets</h2>
<ul>
{datasets.map(ds => (
<li key={ds.id}>
<Link to={`/dashboard/${ds.id}`}>{ds.name}</Link>
</li>
))}
</ul>
</div>
);

export default HomePage;
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100">
<Header brand="Intelligent IoT" nav={[{href:"#datasets",label:"Datasets"}]} />
<Section id="hero" className="pt-10 pb-6 text-center">
<h1 className="text-4xl font-extrabold tracking-tight text-slate-900 sm:text-5xl">
IoT Sensors Dashboard
</h1>
<p className="mx-auto mt-3 max-w-2xl text-slate-600">
Time-series data, anomaly awareness, and correlation insights — all in one place.
</p>
</Section>
<DatasetsGrid datasets={datasets} loading={loading} error={error} />
<Footer />
</div>
);
}
6 changes: 6 additions & 0 deletions new-frontend/frontend/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: { extend: {} },
plugins: [],
}