diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index d9b00f8..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configurations": [ - { - "type": "java", - "name": "Spring Boot-WeatherApplication", - "request": "launch", - "cwd": "${workspaceFolder}", - "mainClass": "com.pogoda.weather.WeatherApplication", - "projectName": "weather", - "args": "", - "envFile": "${workspaceFolder}/.env" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c5f3f6b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file diff --git a/react/weather/package-lock.json b/react/weather/package-lock.json index 5653b9a..cf4ed72 100644 --- a/react/weather/package-lock.json +++ b/react/weather/package-lock.json @@ -18,12 +18,16 @@ "lodash": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.13", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, "devDependencies": { "@eslint/js": "^9.14.0", + "@types/react-router-dom": "^5.3.3", + "@types/styled-components": "^5.1.34", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", @@ -2266,6 +2270,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -2964,6 +2986,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3562,6 +3592,22 @@ "@types/node": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dev": true, + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3685,6 +3731,27 @@ "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -3743,6 +3810,22 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5140,6 +5223,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5631,6 +5722,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -5772,6 +5871,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -8301,6 +8410,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13062,6 +13186,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "dependencies": { + "@remix-run/router": "1.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", + "dependencies": { + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -13912,6 +14066,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14475,6 +14634,65 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -14490,6 +14708,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/react/weather/package.json b/react/weather/package.json index d440661..c8ca2af 100644 --- a/react/weather/package.json +++ b/react/weather/package.json @@ -13,7 +13,9 @@ "lodash": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.13", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, @@ -46,6 +48,8 @@ }, "devDependencies": { "@eslint/js": "^9.14.0", + "@types/react-router-dom": "^5.3.3", + "@types/styled-components": "^5.1.34", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", diff --git a/react/weather/postcss.config.js b/react/weather/postcss.config.js index 33ad091..12a703d 100644 --- a/react/weather/postcss.config.js +++ b/react/weather/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/react/weather/public/cold.jpg b/react/weather/public/cold.jpg new file mode 100644 index 0000000..576d1e7 Binary files /dev/null and b/react/weather/public/cold.jpg differ diff --git a/react/weather/public/hot.jpg b/react/weather/public/hot.jpg new file mode 100644 index 0000000..6176093 Binary files /dev/null and b/react/weather/public/hot.jpg differ diff --git a/react/weather/public/index.html b/react/weather/public/index.html index aa069f2..e65acb3 100644 --- a/react/weather/public/index.html +++ b/react/weather/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/react/weather/public/rain.jpg b/react/weather/public/rain.jpg new file mode 100644 index 0000000..9524db3 Binary files /dev/null and b/react/weather/public/rain.jpg differ diff --git a/react/weather/public/snow.jpg b/react/weather/public/snow.jpg new file mode 100644 index 0000000..1aebbab Binary files /dev/null and b/react/weather/public/snow.jpg differ diff --git a/react/weather/public/sunny.jpg b/react/weather/public/sunny.jpg new file mode 100644 index 0000000..cfa2a93 Binary files /dev/null and b/react/weather/public/sunny.jpg differ diff --git a/react/weather/src/App.test.tsx b/react/weather/src/App.test.tsx index 2a68616..cf4ef9f 100644 --- a/react/weather/src/App.test.tsx +++ b/react/weather/src/App.test.tsx @@ -1,9 +1,60 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { BrowserRouter as Router } from 'react-router-dom'; import App from './App'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); +jest.mock('./components/Home', () => () =>
Home Page
); +jest.mock('./components/History', () => () =>
History Page
); +jest.mock('./components/Settings', () => () =>
Settings Page
); + +describe('App Component', () => { + test('renders home page by default', () => { + render( + + + + ); + + expect(screen.getByText('Home Page')).toBeInTheDocument(); + }); + + test('navigates to History page when History link is clicked', async () => { + render( + + + + ); + + fireEvent.click(screen.getByText('History')); + + await waitFor(() => screen.getByText('History Page')); + + expect(screen.getByText('History Page')).toBeInTheDocument(); + }); + + test('navigates to Settings page when Settings link is clicked', async () => { + render( + + + + ); + + fireEvent.click(screen.getByText('Settings')); + + await waitFor(() => screen.getByText('Settings Page')); + + expect(screen.getByText('Settings Page')).toBeInTheDocument(); + }); + + test('has the correct navigation links', () => { + render( + + + + ); + + expect(screen.getByText('Home')).toBeInTheDocument(); + expect(screen.getByText('History')).toBeInTheDocument(); + expect(screen.getByText('Settings')).toBeInTheDocument(); + }); }); diff --git a/react/weather/src/App.tsx b/react/weather/src/App.tsx index 8931ddf..4270130 100644 --- a/react/weather/src/App.tsx +++ b/react/weather/src/App.tsx @@ -1,26 +1,77 @@ import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'; +import styled from 'styled-components'; +import Home from './components/Home'; +import History from './components/History'; +import Settings from './components/Settings'; -function App() { +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + flex-grow: 1; + z-index: 1; + position: relative; +`; + +const NavBar = styled.nav` + background: #4e2f96; + width: 100%; + padding: 10px 0; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +`; + +const NavList = styled.ul` + list-style: none; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + gap: 15px; +`; + +const NavItem = styled.li``; + +const StyledLink = styled(Link)` + text-decoration: none; + color: #ffffff; + padding: 10px 20px; + font-weight: 500; + border-radius: 5px; + transition: background 0.3s ease; + &:hover { + background: rgba(255, 255, 255, 0.2); + } +`; + +const App: React.FC = () => { return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - ELOOO - -
-
+ <> + + + + + + Home + + + History + + + Settings + + + + + } /> + } /> + } /> + + + + ); -} +}; export default App; diff --git a/react/weather/src/components/History.tsx b/react/weather/src/components/History.tsx new file mode 100644 index 0000000..0100fdf --- /dev/null +++ b/react/weather/src/components/History.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import styled from 'styled-components'; + +const HistoryContainer = styled.div` + background: #ffffff; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + max-width: 600px; + width: 100%; +`; + +const History: React.FC = () => { + return ( + +

Weather History

+

See past weather data here.

+
+ ); +}; + +export default History; diff --git a/react/weather/src/components/Home.tsx b/react/weather/src/components/Home.tsx new file mode 100644 index 0000000..dc08502 --- /dev/null +++ b/react/weather/src/components/Home.tsx @@ -0,0 +1,164 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; + +interface WeatherDTO { + pressure: number; + temperature1: number; + temperature2: number; + rainDetected: number; + humidity: number; + lightIntensity: number; + gasConcentration: number; +} + +const HomeContainer = styled.div<{ backgroundImage: string }>` + background: url(${(props) => props.backgroundImage}) no-repeat center + center/cover; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + max-width: 600px; + width: 100%; + text-align: center; + background-size: cover; +`; + +const Temperature = styled.h2<{ color: string }>` + font-size: 48px; + margin: 10px 0; + color: ${(props) => props.color}; +`; + +const WeatherDetails = styled.div` + margin-top: 20px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + text-align: left; +`; + +const DetailItem = styled.div` + flex: 1 1 45%; + padding: 10px; + background: #f0f0f0; + border-radius: 8px; + margin: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +`; + +const NoDataMessage = styled.p` + font-size: 18px; + color: red; + text-align: center; + margin-top: 20px; +`; + +const Home: React.FC = () => { + const [weatherData, setWeatherData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [noData, setNoData] = useState(false); + const [backgroundImage, setBackgroundImage] = useState(''); + const [temperatureColor, setTemperatureColor] = useState('#000000'); + + const fetchWeatherData = async () => { + try { + const response = await fetch('http://localhost:8080/weather/measurments'); + if (!response.ok) { + if (response.status === 404) { + throw new Error('Data not found (404)'); + } else if (response.status === 503) { + throw new Error('Backend is unavailable. Please try again later.'); + } else { + throw new Error(`Error: ${response.status}`); + } + } + const data: WeatherDTO = await response.json(); + setWeatherData(data); + setNoData(false); + setError(null); + } catch (err: any) { + setError(err.message); + setWeatherData(null); + setNoData(true); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + const interval = setInterval(() => { + fetchWeatherData(); + }, 5000); + + fetchWeatherData(); + + return () => clearInterval(interval); + }, []); + + useEffect(() => { + if (!weatherData) return; + + if (weatherData.temperature2 < 0 && weatherData.rainDetected) { + setBackgroundImage('/snow.jpg'); + setTemperatureColor('#000000'); + } else if (weatherData.temperature2 < 0) { + setBackgroundImage('/cold.jpg'); + setTemperatureColor('#ffffff'); + } else if (weatherData.rainDetected) { + setBackgroundImage('/rain.jpg'); + setTemperatureColor('#ffffff'); + } else if (weatherData.temperature2 <= 30) { + setBackgroundImage('/sunny.jpg'); + setTemperatureColor('#000000'); + } else { + setBackgroundImage('/hot.jpg'); + setTemperatureColor('#000000'); + } + }, [weatherData]); + + const getgasConcentrationIcon = () => { + if (!weatherData) return ''; + if (weatherData.gasConcentration <= 50) return '🟢'; + if (weatherData.gasConcentration <= 100) return '🟡'; + return '🔴'; + }; + + if (loading) return

Loading...

; + if (noData) + return ( + + Data is currently unavailable. Please try again later. + + ); + + return ( + + {error &&

Error: {error}

}{' '} + {weatherData && ( + <> + + {weatherData.temperature2}°C + + + + Humidity: {weatherData.humidity}% + + + Pressure: {weatherData.pressure} hPa + + + Light Intensity: {weatherData.lightIntensity} lx + + + Gas Concentration: {weatherData.gasConcentration}{' '} + {getgasConcentrationIcon()} + + + + )} +
+ ); +}; + +export default Home; diff --git a/react/weather/src/components/Settings.tsx b/react/weather/src/components/Settings.tsx new file mode 100644 index 0000000..a743892 --- /dev/null +++ b/react/weather/src/components/Settings.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import styled from 'styled-components'; + +const SettingsContainer = styled.div` + background: #ffffff; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + max-width: 600px; + width: 100%; +`; + +const Settings: React.FC = () => { + return ( + +

Settings

+

Here you can configure your preferences.

+
+ ); +}; + +export default Settings; diff --git a/react/weather/tailwind.config.js b/react/weather/tailwind.config.js index 117e102..b7e57f7 100644 --- a/react/weather/tailwind.config.js +++ b/react/weather/tailwind.config.js @@ -1,9 +1,8 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./src/**/*.{js,jsx,ts,tsx,html}"], + content: ['./src/**/*.{js,jsx,ts,tsx,html}'], theme: { extend: {}, }, plugins: [], -} - +}; diff --git a/react/weather/tsconfig.json b/react/weather/tsconfig.json index a273b0c..9d379a3 100644 --- a/react/weather/tsconfig.json +++ b/react/weather/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/spring/pom.xml b/spring/pom.xml index 69b7742..00744cd 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -68,6 +68,14 @@ spring-boot-starter-test test + + + + com.fasterxml.uuid + java-uuid-generator + 5.1.0 + + diff --git a/spring/src/main/java/com/pogoda/weather/configuration/WebConfig.java b/spring/src/main/java/com/pogoda/weather/configuration/WebConfig.java new file mode 100644 index 0000000..7627c6d --- /dev/null +++ b/spring/src/main/java/com/pogoda/weather/configuration/WebConfig.java @@ -0,0 +1,15 @@ +package com.pogoda.weather.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("http://localhost:3000") + .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") + .allowedHeaders("*").allowCredentials(true); + } +} diff --git a/spring/src/main/java/com/pogoda/weather/controller/Controller.java b/spring/src/main/java/com/pogoda/weather/controller/WeatherController.java similarity index 65% rename from spring/src/main/java/com/pogoda/weather/controller/Controller.java rename to spring/src/main/java/com/pogoda/weather/controller/WeatherController.java index 557a4f1..bdede26 100644 --- a/spring/src/main/java/com/pogoda/weather/controller/Controller.java +++ b/spring/src/main/java/com/pogoda/weather/controller/WeatherController.java @@ -9,28 +9,23 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.pogoda.weather.data.EspMeasurementsRepo; +import com.pogoda.weather.dto.WeatherDTO; import com.pogoda.weather.model.EspMeasurements; +import com.pogoda.weather.repository.EspMeasurementsRepo; +import com.pogoda.weather.services.WeatherService; import lombok.AllArgsConstructor; @AllArgsConstructor @RestController @RequestMapping("/weather") -public class Controller { +public class WeatherController { @Autowired - EspMeasurementsRepo espDataRepo; + private final WeatherService weatherService; - @GetMapping("/test") - public String aja() { - return "cos tam"; - } - - @PostMapping("/test") - public void ajaa() { - System.out.println("DOSTALEM"); - } + @Autowired + private EspMeasurementsRepo espDataRepo; @PostMapping("/measurments") public ResponseEntity zapis(@RequestBody EspMeasurements espMeasurements) { @@ -41,4 +36,24 @@ public ResponseEntity zapis(@RequestBody EspMeasurements espMea public ResponseEntity odczyty(@PathVariable String id) { return ResponseEntity.ok(espDataRepo.getMeasurements(id)); } + + @GetMapping("/measurments") + public ResponseEntity getWeatherData() { + WeatherDTO weatherData = weatherService.getLatestWeather(); + if (weatherData != null) { + return ResponseEntity.ok(weatherData); + } else { + return ResponseEntity.notFound().build(); + } + } + + @GetMapping("/test") + public String aja() { + return "cos tam"; + } + + @PostMapping("/test") + public void ajaaPOST() { + System.out.println("DOSTALEM"); + } } diff --git a/spring/src/main/java/com/pogoda/weather/dto/WeatherDTO.java b/spring/src/main/java/com/pogoda/weather/dto/WeatherDTO.java new file mode 100644 index 0000000..b48834c --- /dev/null +++ b/spring/src/main/java/com/pogoda/weather/dto/WeatherDTO.java @@ -0,0 +1,18 @@ +package com.pogoda.weather.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WeatherDTO { + private float pressure; + private int temperature1; + private int temperature2; + private boolean rainDetected; + private float humidity; + private float lightIntensity; + private float gasConcentration; +} diff --git a/spring/src/main/java/com/pogoda/weather/interfaces/IEspMeasurementsReposytory.java b/spring/src/main/java/com/pogoda/weather/interfaces/IEspMeasurementsReposytory.java index 27ad473..2d1b2ef 100644 --- a/spring/src/main/java/com/pogoda/weather/interfaces/IEspMeasurementsReposytory.java +++ b/spring/src/main/java/com/pogoda/weather/interfaces/IEspMeasurementsReposytory.java @@ -9,9 +9,7 @@ public interface IEspMeasurementsReposytory extends CrudRepository findByPressure1(int pressure); + Iterable findByPressure(int pressure); Iterable findByTemperature1(int temperature); @@ -22,4 +20,6 @@ public interface IEspMeasurementsReposytory extends CrudRepository findTopByOrderByDateDesc(); } diff --git a/spring/src/main/java/com/pogoda/weather/model/EspAlerts.java b/spring/src/main/java/com/pogoda/weather/model/EspAlerts.java index a3ea5bd..9bcab8d 100644 --- a/spring/src/main/java/com/pogoda/weather/model/EspAlerts.java +++ b/spring/src/main/java/com/pogoda/weather/model/EspAlerts.java @@ -1,7 +1,5 @@ package com.pogoda.weather.model; - - import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; diff --git a/spring/src/main/java/com/pogoda/weather/model/EspMeasurements.java b/spring/src/main/java/com/pogoda/weather/model/EspMeasurements.java index 71b1883..9e1654a 100644 --- a/spring/src/main/java/com/pogoda/weather/model/EspMeasurements.java +++ b/spring/src/main/java/com/pogoda/weather/model/EspMeasurements.java @@ -1,5 +1,6 @@ package com.pogoda.weather.model; +import java.time.LocalDateTime; import java.util.UUID; import jakarta.persistence.Entity; @@ -21,15 +22,26 @@ public class EspMeasurements { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; - private int pressure1; - private int pressure2; + private float pressure; private int temperature1; - private int temperature2; // i tu bedzie wiecej teraz tylko model + private int temperature2; + private boolean rainDetected; + private float humidity; + private float lightIntensity; + private float gasConcentration; - public EspMeasurements(int press1, int press2, int temp1, int temp2) { - this.pressure1 = press1; - this.pressure2 = press2; - this.temperature1 = temp1; - this.temperature2 = temp2; + private LocalDateTime date; + + public EspMeasurements(float pressure, int temperature1, int temperature2, boolean rainDetected, float humidity, + float lightIntensity, float gasConcentration) { + this.id = UUID.randomUUID().toString(); + this.pressure = pressure; + this.temperature1 = temperature1; + this.temperature2 = temperature2; + this.rainDetected = rainDetected; + this.humidity = humidity; + this.lightIntensity = lightIntensity; + this.gasConcentration = gasConcentration; + this.date = LocalDateTime.now(); } } diff --git a/spring/src/main/java/com/pogoda/weather/model/EspUserSettings.java b/spring/src/main/java/com/pogoda/weather/model/EspUserSettings.java index 1dc90a9..b5d0ca8 100644 --- a/spring/src/main/java/com/pogoda/weather/model/EspUserSettings.java +++ b/spring/src/main/java/com/pogoda/weather/model/EspUserSettings.java @@ -1,7 +1,5 @@ package com.pogoda.weather.model; - - import jakarta.persistence.Entity; import jakarta.persistence.Id; diff --git a/spring/src/main/java/com/pogoda/weather/model/EspUsers.java b/spring/src/main/java/com/pogoda/weather/model/EspUsers.java index 9c3386b..72468c7 100644 --- a/spring/src/main/java/com/pogoda/weather/model/EspUsers.java +++ b/spring/src/main/java/com/pogoda/weather/model/EspUsers.java @@ -1,7 +1,5 @@ package com.pogoda.weather.model; - - import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; diff --git a/spring/src/main/java/com/pogoda/weather/model/EspUsersAlert.java b/spring/src/main/java/com/pogoda/weather/model/EspUsersAlert.java index 0439e58..f81a159 100644 --- a/spring/src/main/java/com/pogoda/weather/model/EspUsersAlert.java +++ b/spring/src/main/java/com/pogoda/weather/model/EspUsersAlert.java @@ -2,7 +2,6 @@ import java.io.Serializable; - import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,13 +14,10 @@ @NoArgsConstructor @Getter @Setter -public class EspUsersAlert implements Serializable -{ +public class EspUsersAlert implements Serializable { @Id private String userId; @Id private String alertId; - - } diff --git a/spring/src/main/java/com/pogoda/weather/data/EspAlertsRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspAlertsRepo.java similarity index 97% rename from spring/src/main/java/com/pogoda/weather/data/EspAlertsRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspAlertsRepo.java index 0eb1f29..f42c80c 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspAlertsRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspAlertsRepo.java @@ -1,4 +1,4 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; import org.springframework.stereotype.Repository; import com.pogoda.weather.interfaces.IEspAlertsRepository; diff --git a/spring/src/main/java/com/pogoda/weather/data/EspLanguagesRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspLanguagesRepo.java similarity index 97% rename from spring/src/main/java/com/pogoda/weather/data/EspLanguagesRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspLanguagesRepo.java index 6b2a98e..4ed3281 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspLanguagesRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspLanguagesRepo.java @@ -1,4 +1,4 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; import org.springframework.stereotype.Repository; import com.pogoda.weather.interfaces.IEspLanguagesRepository; diff --git a/spring/src/main/java/com/pogoda/weather/data/EspMeasureUnitsRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspMeasureUnitsRepo.java similarity index 97% rename from spring/src/main/java/com/pogoda/weather/data/EspMeasureUnitsRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspMeasureUnitsRepo.java index b27b0dc..eb30423 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspMeasureUnitsRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspMeasureUnitsRepo.java @@ -1,4 +1,4 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; import org.springframework.stereotype.Repository; import com.pogoda.weather.interfaces.IEspMeasureUnitsRepository; diff --git a/spring/src/main/java/com/pogoda/weather/data/EspMeasurementsRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspMeasurementsRepo.java similarity index 57% rename from spring/src/main/java/com/pogoda/weather/data/EspMeasurementsRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspMeasurementsRepo.java index 4f88a28..14af7a5 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspMeasurementsRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspMeasurementsRepo.java @@ -1,10 +1,13 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; + +import java.time.LocalDateTime; import org.springframework.stereotype.Repository; + +import com.pogoda.weather.dto.WeatherDTO; import com.pogoda.weather.interfaces.IEspMeasurementsReposytory; import com.pogoda.weather.model.EspMeasurements; - @Repository public class EspMeasurementsRepo { @@ -15,6 +18,7 @@ public EspMeasurementsRepo(IEspMeasurementsReposytory espMeasurementsReposytory) } public EspMeasurements saveMeasurements(EspMeasurements espMeasurements) { + espMeasurements.setDate(LocalDateTime.now()); return espMeasurementsReposytory.save(espMeasurements); } @@ -24,13 +28,18 @@ public EspMeasurements getMeasurements(String id) { } public Iterable getMeasurementsByPressure(int pressure) { - return espMeasurementsReposytory.findByPressure1(pressure); + return espMeasurementsReposytory.findByPressure(pressure); } public Iterable getMeasurementsByTemperature(int temperature) { return espMeasurementsReposytory.findByTemperature1(temperature); } + public EspMeasurements getLatestEspMeasurements() { + return espMeasurementsReposytory.findTopByOrderByDateDesc() + .orElseThrow(() -> new RuntimeException("No measurements found")); + } + public void deleteMeasurement(String id) { espMeasurementsReposytory.deleteById(id); } @@ -43,4 +52,15 @@ public void deleteAllMeasurements() { espMeasurementsReposytory.deleteAll(); } + public WeatherDTO toDTO(EspMeasurements entity) { + return new WeatherDTO(entity.getPressure(), entity.getTemperature1(), entity.getTemperature2(), + entity.isRainDetected(), entity.getHumidity(), entity.getLightIntensity(), + entity.getGasConcentration()); + } + + public EspMeasurements fromDTO(WeatherDTO dto) { + return new EspMeasurements(dto.getPressure(), dto.getTemperature1(), dto.getTemperature2(), + dto.isRainDetected(), dto.getHumidity(), dto.getLightIntensity(), dto.getGasConcentration()); + } + } diff --git a/spring/src/main/java/com/pogoda/weather/data/EspUserSettingsRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspUserSettingsRepo.java similarity index 97% rename from spring/src/main/java/com/pogoda/weather/data/EspUserSettingsRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspUserSettingsRepo.java index fb6f4b3..1465c6b 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspUserSettingsRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspUserSettingsRepo.java @@ -1,4 +1,4 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; import org.springframework.stereotype.Repository; import com.pogoda.weather.interfaces.IEspUserSettingsRepository; diff --git a/spring/src/main/java/com/pogoda/weather/data/EspUsersAlertRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspUsersAlertRepo.java similarity index 94% rename from spring/src/main/java/com/pogoda/weather/data/EspUsersAlertRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspUsersAlertRepo.java index 4a7ec26..da49e66 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspUsersAlertRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspUsersAlertRepo.java @@ -1,11 +1,11 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; import org.springframework.stereotype.Repository; import com.pogoda.weather.interfaces.IEspUsersAlertRepository; import com.pogoda.weather.model.EspUsersAlert; @Repository -public class EspUsersAlertRepo { +public class EspUsersAlertRepo { private final IEspUsersAlertRepository espUsersAlertRepository; diff --git a/spring/src/main/java/com/pogoda/weather/data/EspUsersRepo.java b/spring/src/main/java/com/pogoda/weather/repository/EspUsersRepo.java similarity index 96% rename from spring/src/main/java/com/pogoda/weather/data/EspUsersRepo.java rename to spring/src/main/java/com/pogoda/weather/repository/EspUsersRepo.java index 7c8170d..7e3917e 100644 --- a/spring/src/main/java/com/pogoda/weather/data/EspUsersRepo.java +++ b/spring/src/main/java/com/pogoda/weather/repository/EspUsersRepo.java @@ -1,4 +1,4 @@ -package com.pogoda.weather.data; +package com.pogoda.weather.repository; import org.springframework.stereotype.Repository; import com.pogoda.weather.interfaces.IEspUsersRepository; diff --git a/spring/src/main/java/com/pogoda/weather/services/Service.java b/spring/src/main/java/com/pogoda/weather/services/Service.java index b1b52ea..5f246cb 100644 --- a/spring/src/main/java/com/pogoda/weather/services/Service.java +++ b/spring/src/main/java/com/pogoda/weather/services/Service.java @@ -1,6 +1,6 @@ package com.pogoda.weather.services; -import com.pogoda.weather.data.EspMeasurementsRepo; +import com.pogoda.weather.repository.EspMeasurementsRepo; import lombok.AllArgsConstructor; diff --git a/spring/src/main/java/com/pogoda/weather/services/WeatherService.java b/spring/src/main/java/com/pogoda/weather/services/WeatherService.java new file mode 100644 index 0000000..da998d5 --- /dev/null +++ b/spring/src/main/java/com/pogoda/weather/services/WeatherService.java @@ -0,0 +1,30 @@ +package com.pogoda.weather.services; + +import com.pogoda.weather.dto.WeatherDTO; +import com.pogoda.weather.model.EspMeasurements; +import com.pogoda.weather.repository.EspMeasurementsRepo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class WeatherService { + + private final EspMeasurementsRepo espMeasurementsRepo; + + @Autowired + public WeatherService(EspMeasurementsRepo espMeasurementsRepo) { + this.espMeasurementsRepo = espMeasurementsRepo; + } + + public WeatherDTO getLatestWeather() { + EspMeasurements latestMeasurement = espMeasurementsRepo.getLatestEspMeasurements(); + + if (latestMeasurement != null) { + return new WeatherDTO(latestMeasurement.getPressure(), latestMeasurement.getTemperature1(), + latestMeasurement.getTemperature2(), latestMeasurement.isRainDetected(), + latestMeasurement.getHumidity(), latestMeasurement.getLightIntensity(), + latestMeasurement.getGasConcentration()); + } + return null; + } +} diff --git a/spring/src/main/resources/application.properties b/spring/src/main/resources/application.properties index 99ca61b..ede1df6 100644 --- a/spring/src/main/resources/application.properties +++ b/spring/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=weather -spring.datasource.url=jdbc:mysql://localhost:3306/Weather +spring.datasource.url=jdbc:mysql://localhost:3306/weather spring.datasource.username=WeatherServer spring.datasource.password=t4jn3h4sL0 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver