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 ( + +
+
+
+ + + +
+
Dataset
+

{name}

+
+
+ +
+
+
+
+
+

{description}

+
+
+ 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 ( +
+
+ © {new Date().getFullYear()} Intelligent IoT · {note} +
+
+ ); +} 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: [], +}