diff --git a/new-frontend/frontend/postcss.config.js b/new-frontend/frontend/postcss.config.js
new file mode 100644
index 0000000..1e8e944
--- /dev/null
+++ b/new-frontend/frontend/postcss.config.js
@@ -0,0 +1,6 @@
+// postcss.config.js
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ },
+}
\ No newline at end of file
diff --git a/new-frontend/frontend/src/components/DatasetCard.jsx b/new-frontend/frontend/src/components/DatasetCard.jsx
new file mode 100644
index 0000000..97e5104
--- /dev/null
+++ b/new-frontend/frontend/src/components/DatasetCard.jsx
@@ -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 (
+
+
+
+
+
+ Last 24h anomalies
+ 0 ? "bg-rose-50 text-rose-600" : "bg-emerald-50 text-emerald-600"}`}>
+ {anomalies24h ?? 0}
+
+
+
+
+ View dashboard →
+
+
+
+
+ );
+}
diff --git a/new-frontend/frontend/src/components/DatasetsGrid.jsx b/new-frontend/frontend/src/components/DatasetsGrid.jsx
new file mode 100644
index 0000000..60fdadf
--- /dev/null
+++ b/new-frontend/frontend/src/components/DatasetsGrid.jsx
@@ -0,0 +1,38 @@
+import DatasetCard from "./DatasetCard";
+import Section from "./Section";
+
+function Skeleton() {
+ return (
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
+ );
+}
+
+export default function DatasetsGrid({ datasets = [], loading = false, error = null }) {
+ return (
+
+ {error && (
+
+ Could not load datasets. Please try again.
+
+ )}
+ {loading ? (
+
+ ) : (
+
+ {datasets.map((ds) => )}
+
+ )}
+
+ );
+}
diff --git a/new-frontend/frontend/src/components/Footer.jsx b/new-frontend/frontend/src/components/Footer.jsx
new file mode 100644
index 0000000..9fc1498
--- /dev/null
+++ b/new-frontend/frontend/src/components/Footer.jsx
@@ -0,0 +1,9 @@
+export default function Footer({ note = "Built with React & Tailwind" }) {
+ return (
+
+ );
+}
diff --git a/new-frontend/frontend/src/components/Header.jsx b/new-frontend/frontend/src/components/Header.jsx
new file mode 100644
index 0000000..b11fb68
--- /dev/null
+++ b/new-frontend/frontend/src/components/Header.jsx
@@ -0,0 +1,19 @@
+export default function Header({ brand = "Intelligent IoT", nav = [] }) {
+ return (
+
+
+
+
+ IoT
+
+ {brand}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/new-frontend/frontend/src/components/Icon.jsx b/new-frontend/frontend/src/components/Icon.jsx
new file mode 100644
index 0000000..27f2a08
--- /dev/null
+++ b/new-frontend/frontend/src/components/Icon.jsx
@@ -0,0 +1,14 @@
+function Thermometer(props){return()}
+function Droplet(props){return()}
+function Air(props){return()}
+
+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 ;
+}
diff --git a/new-frontend/frontend/src/components/Section.jsx b/new-frontend/frontend/src/components/Section.jsx
new file mode 100644
index 0000000..bc98c05
--- /dev/null
+++ b/new-frontend/frontend/src/components/Section.jsx
@@ -0,0 +1,10 @@
+export default function Section({ id, title, center = false, className = "", children }) {
+ return (
+
+ {title && (
+ {title}
+ )}
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/new-frontend/frontend/src/components/StatusBadge.jsx b/new-frontend/frontend/src/components/StatusBadge.jsx
new file mode 100644
index 0000000..5ca544b
--- /dev/null
+++ b/new-frontend/frontend/src/components/StatusBadge.jsx
@@ -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 (
+
+ {s.text}
+
+ );
+}
diff --git a/new-frontend/frontend/src/data/datasets.js b/new-frontend/frontend/src/data/datasets.js
new file mode 100644
index 0000000..98e96c9
--- /dev/null
+++ b/new-frontend/frontend/src/data/datasets.js
@@ -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",
+ },
+];
diff --git a/new-frontend/frontend/src/hooks/useDatasets.js b/new-frontend/frontend/src/hooks/useDatasets.js
new file mode 100644
index 0000000..6b850b5
--- /dev/null
+++ b/new-frontend/frontend/src/hooks/useDatasets.js
@@ -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 };
+}
diff --git a/new-frontend/frontend/src/index.css b/new-frontend/frontend/src/index.css
index 08a3ac9..a461c50 100644
--- a/new-frontend/frontend/src/index.css
+++ b/new-frontend/frontend/src/index.css
@@ -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";
\ No newline at end of file
diff --git a/new-frontend/frontend/src/pages/HomePage.jsx b/new-frontend/frontend/src/pages/HomePage.jsx
index cc3f45e..c154acf 100644
--- a/new-frontend/frontend/src/pages/HomePage.jsx
+++ b/new-frontend/frontend/src/pages/HomePage.jsx
@@ -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 = () => (
-
-
Available Sensor Datasets
-
- {datasets.map(ds => (
- -
- {ds.name}
-
- ))}
-
-
-);
-
-export default HomePage;
+ return (
+
+
+
+
+ IoT Sensors Dashboard
+
+
+ Time-series data, anomaly awareness, and correlation insights — all in one place.
+
+
+
+
+
+ );
+}
diff --git a/new-frontend/frontend/tailwind.config.js b/new-frontend/frontend/tailwind.config.js
new file mode 100644
index 0000000..76ab992
--- /dev/null
+++ b/new-frontend/frontend/tailwind.config.js
@@ -0,0 +1,6 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
+ theme: { extend: {} },
+ plugins: [],
+}