From 3bcd2e16a2ce844dc2b8c6bd59223eef6445ff4d Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Fri, 25 Jul 2025 01:03:11 +0200 Subject: [PATCH] replace redux with zustand --- biome.json | 34 +++++++ bun.lock | 22 +++++ eslint.config.js | 34 +++---- package.json | 82 ++++++++--------- src/App.css | 42 ++++----- src/App.tsx | 29 ++---- src/api/api.ts | 13 --- src/api/flatastic.ts | 92 +++++++++---------- src/api/kvv.ts | 13 +++ src/components/Flatastic/Flatastic.tsx | 49 +++++++---- src/components/Timetable/Timetable.tsx | 93 +++++++++----------- src/components/TimetableRow/TimetableRow.tsx | 31 ++++--- src/components/TimetableRow/style.module.css | 2 +- src/index.css | 84 +++++++++--------- src/main.tsx | 16 +--- src/store/flatastic.ts | 26 ++++++ src/store/index.ts | 43 --------- src/store/kvv.ts | 25 ++++++ src/store/persist/persistConfig.ts | 8 -- src/store/slices/flatastiucChoresSlice.ts | 20 ----- src/store/slices/timetableSlice.ts | 24 ----- src/store/thunks/fetchFlatasticChores.ts | 22 ----- src/store/thunks/fetchTimetable.ts | 18 ---- src/types/departureType.ts | 15 ++++ src/types/flatasticChore.ts | 13 +++ src/types/types.ts | 14 --- tsconfig.app.json | 58 ++++++------ tsconfig.json | 56 ++++++------ tsconfig.node.json | 42 ++++----- vite.config.ts | 24 ++--- 30 files changed, 510 insertions(+), 534 deletions(-) create mode 100644 biome.json delete mode 100644 src/api/api.ts create mode 100644 src/api/kvv.ts create mode 100644 src/store/flatastic.ts delete mode 100644 src/store/index.ts create mode 100644 src/store/kvv.ts delete mode 100644 src/store/persist/persistConfig.ts delete mode 100644 src/store/slices/flatastiucChoresSlice.ts delete mode 100644 src/store/slices/timetableSlice.ts delete mode 100644 src/store/thunks/fetchFlatasticChores.ts delete mode 100644 src/store/thunks/fetchTimetable.ts create mode 100644 src/types/departureType.ts create mode 100644 src/types/flatasticChore.ts delete mode 100644 src/types/types.ts diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..7f47e82 --- /dev/null +++ b/biome.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/bun.lock b/bun.lock index e5adb16..f3f2ee9 100644 --- a/bun.lock +++ b/bun.lock @@ -12,8 +12,10 @@ "react-dom": "^19.1.0", "react-redux": "^9.2.0", "redux-persist": "^6.0.0", + "zustand": "^5.0.6", }, "devDependencies": { + "@biomejs/biome": "2.1.2", "@eslint/js": "^9.31.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -36,6 +38,24 @@ }, }, "packages": { + "@biomejs/biome": ["@biomejs/biome@2.1.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.2", "@biomejs/cli-darwin-x64": "2.1.2", "@biomejs/cli-linux-arm64": "2.1.2", "@biomejs/cli-linux-arm64-musl": "2.1.2", "@biomejs/cli-linux-x64": "2.1.2", "@biomejs/cli-linux-x64-musl": "2.1.2", "@biomejs/cli-win32-arm64": "2.1.2", "@biomejs/cli-win32-x64": "2.1.2" }, "bin": { "biome": "bin/biome" } }, "sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-leFAks64PEIjc7MY/cLjE8u5OcfBKkcDB0szxsWUB4aDfemBep1WVKt0qrEyqZBOW8LPHzrFMyDl3FhuuA0E7g=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-Nmmv7wRX5Nj7lGmz0FjnWdflJg4zii8Ivruas6PBKzw5SJX/q+Zh2RfnO+bBnuKLXpj8kiI2x2X12otpH6a32A=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-NWNy2Diocav61HZiv2enTQykbPP/KrA/baS7JsLSojC7Xxh2nl9IczuvE5UID7+ksRy2e7yH7klm/WkA72G1dw=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-qgHvafhjH7Oca114FdOScmIKf1DlXT1LqbOrrbR30kQDLFPEOpBG0uzx6MhmsrmhGiCFCr2obDamu+czk+X0HQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Km/UYeVowygTjpX6sGBzlizjakLoMQkxWbruVZSNE6osuSI63i4uCeIL+6q2AJlD3dxoiBJX70dn1enjQnQqwA=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-xlB3mU14ZUa3wzLtXfmk2IMOGL+S0aHFhSix/nssWS/2XlD27q+S6f0dlQ8WOCbYoXcuz8BCM7rCn2lxdTrlQA=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-G8KWZli5ASOXA3yUQgx+M4pZRv3ND16h77UsdunUL17uYpcL/UC7RkWTdkfvMQvogVsAuz5JUcBDjgZHXxlKoA=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.2", "", { "os": "win32", "cpu": "x64" }, "sha512-9zajnk59PMpjBkty3bK2IrjUsUHvqe9HWwyAWQBjGLE7MIBjbX2vwv1XPEhmO2RRuGoTkVx3WCanHrjAytICLA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="], @@ -740,6 +760,8 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zustand": ["zustand@5.0.6", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], diff --git a/eslint.config.js b/eslint.config.js index 47fbe76..f3a82a0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,21 +6,21 @@ import tseslint from "typescript-eslint"; import { globalIgnores } from "eslint/config"; export default tseslint.config([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - "airbnb", - "plugin:prettier/recommended", - js.configs.recommended, - tseslint.configs.recommended, - reactHooks.configs["recommended-latest"], - reactRefresh.configs.vite, - "prettier", - ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - }, + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + "airbnb", + "plugin:prettier/recommended", + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs["recommended-latest"], + reactRefresh.configs.vite, + "prettier", + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, ]); diff --git a/package.json b/package.json index 7b5f256..adefec9 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,44 @@ { - "name": "monitor-im-flur", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@reduxjs/toolkit": "^2.8.2", - "@types/lodash": "^4.17.20", - "@types/node": "^24.1.0", - "lodash": "^4.17.21", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-redux": "^9.2.0", - "redux-persist": "^6.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.31.0", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react-swc": "^3.10.2", - "eslint": "^9.31.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-prettier": "^5.5.3", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^16.3.0", - "prettier": "3.6.2", - "typescript": "~5.8.3", - "typescript-eslint": "^8.35.1", - "vite": "^7.0.4" - } + "name": "monitor-im-flur", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.8.2", + "@types/lodash": "^4.17.20", + "@types/node": "^24.1.0", + "lodash": "^4.17.21", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-redux": "^9.2.0", + "redux-persist": "^6.0.0", + "zustand": "^5.0.6" + }, + "devDependencies": { + "@biomejs/biome": "2.1.2", + "@eslint/js": "^9.31.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react-swc": "^3.10.2", + "eslint": "^9.31.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "prettier": "3.6.2", + "typescript": "~5.8.3", + "typescript-eslint": "^8.35.1", + "vite": "^7.0.4" + } } diff --git a/src/App.css b/src/App.css index f44fb79..df674c0 100644 --- a/src/App.css +++ b/src/App.css @@ -1,42 +1,42 @@ #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; } .logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; } .logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); + filter: drop-shadow(0 0 2em #646cffaa); } .logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); + filter: drop-shadow(0 0 2em #61dafbaa); } @keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } @media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } } .card { - padding: 2em; + padding: 2em; } .read-the-docs { - color: #888; + color: #888; } diff --git a/src/App.tsx b/src/App.tsx index 5f62f17..3353c34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,14 @@ -import "./App.css"; -import Timetable from "./components/Timetable/Timetable"; -import { useDispatch } from "react-redux"; -import { fetchTimetable } from "./store/index"; -import { useEffect } from "react"; -import { type AppDispatch } from "./store/index"; -import Flatastic from "./components/Flatastic/Flatastic"; -import fetchFlatasticChores from "./store/thunks/fetchFlatasticChores"; +import "@/App.css"; +import Timetable from "@/components/Timetable/Timetable"; +import Flatastic from "@/components/Flatastic/Flatastic"; function App() { - const dispatch = useDispatch(); - // Fetch the timetable data when the app loads - useEffect(() => { - dispatch(fetchTimetable()); - dispatch(fetchFlatasticChores()); - }, [dispatch]); - return ( - <> - - - - ); + return ( + <> + + + + ); } export default App; diff --git a/src/api/api.ts b/src/api/api.ts deleted file mode 100644 index 4a4aa98..0000000 --- a/src/api/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -async function callApi(stopId: number) { - const API_URL = `https://projekte.kvv-efa.de/sl3-alone/XSLT_DM_REQUEST?outputFormat=JSON&coordOutputFormat=WGS84[dd.ddddd]&depType=stopEvents&locationServerActive=1&mode=direct&name_dm=${stopId}&type_dm=stop&useOnlyStops=1&useRealtime=1&limit=6&line=kvv:22301:E:H:s25&line=kvv:21012:E:H:s25&line=kvv:21012:E:R:s25&line=kvv:22305:E:H:s25`; - - const data = await fetch(API_URL, { - method: "GET", - }); - if (!data.ok) { - throw new Error(`HTTP error! status: ${data.status}`); - } - return data; -} - -export { callApi }; diff --git a/src/api/flatastic.ts b/src/api/flatastic.ts index 3a22691..0cc59ff 100644 --- a/src/api/flatastic.ts +++ b/src/api/flatastic.ts @@ -1,55 +1,55 @@ - class Flatastic { - private apikey: string; + private apikey: string; - constructor(apikey: string) { - this.apikey = apikey; - } + constructor(apikey: string) { + this.apikey = apikey; + } - async request(url: string, option: any, cb: (info: any) => void) { - const headers = { - "accept": "application/json, text/plain, */*", - "accept-language": "de-CH,de;q=0.9,en-US;q=0.8,en-CH;q=0.7,en;q=0.6,ar-JO;q=0.5,ar;q=0.4,de-DE;q=0.3", - // "cache-control": "no-cache", - // "pragma": "no-cache", - // "sec-fetch-dest": "empty", - // "sec-fetch-mode": "cors", - // "sec-fetch-site": "same-site", - "x-api-key": this.apikey, - "x-api-version": "2.0.0", - "x-client-version": "2.3.20" - }; - try { - const response = await fetch(url, { - method: 'GET', - headers: headers - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const info = await response.json(); - cb(info); - } catch (error) { - let message = 'Unknown error'; - if (error instanceof Error) { - message = error.message; - } - cb({ error: message }); - } - } + async request(url: string, option: any) { + const headers = { + accept: "application/json, text/plain, */*", + "accept-language": + "de-CH,de;q=0.9,en-US;q=0.8,en-CH;q=0.7,en;q=0.6,ar-JO;q=0.5,ar;q=0.4,de-DE;q=0.3", + // "cache-control": "no-cache", + // "pragma": "no-cache", + // "sec-fetch-dest": "empty", + // "sec-fetch-mode": "cors", + // "sec-fetch-site": "same-site", + "x-api-key": this.apikey, + "x-api-version": "2.0.0", + "x-client-version": "2.3.20", + }; + const response = await fetch(url, { + ...option, + headers: { + ...headers, + ...option.headers, + }, + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } - getShoppingList(callback: (info: any) => void) { - this.request('https://api.flatastic-app.com/index.php/api/shoppinglist', {}, callback); - } + getShoppingList() { + return this.request( + "https://api.flatastic-app.com/index.php/api/shoppinglist", + {}, + ); + } - getTaskList(callback: (info: any) => void) { - this.request('https://api.flatastic-app.com/index.php/api/chores', {}, callback); - } + getTaskList() { + return this.request( + "https://api.flatastic-app.com/index.php/api/chores", + {}, + ); + } - getInformation(callback: (info: any) => void) { - this.request('https://api.flatastic-app.com/index.php/api/wg', {}, callback); - } + getInformation() { + this.request("https://api.flatastic-app.com/index.php/api/wg", {}); + } } export { Flatastic }; -export default Flatastic; \ No newline at end of file +export default Flatastic; diff --git a/src/api/kvv.ts b/src/api/kvv.ts new file mode 100644 index 0000000..794d475 --- /dev/null +++ b/src/api/kvv.ts @@ -0,0 +1,13 @@ +async function fetchKvvDepartures(stopId: number) { + const API_URL = `https://projekte.kvv-efa.de/sl3-alone/XSLT_DM_REQUEST?outputFormat=JSON&coordOutputFormat=WGS84[dd.ddddd]&depType=stopEvents&locationServerActive=1&mode=direct&name_dm=${stopId}&type_dm=stop&useOnlyStops=1&useRealtime=1&limit=6&line=kvv:22301:E:H:s25&line=kvv:21012:E:H:s25&line=kvv:21012:E:R:s25&line=kvv:22305:E:H:s25`; + + const data = await fetch(API_URL, { + method: "GET", + }); + if (!data.ok) { + throw new Error(`HTTP error! status: ${data.status}`); + } + return data; +} + +export { fetchKvvDepartures }; diff --git a/src/components/Flatastic/Flatastic.tsx b/src/components/Flatastic/Flatastic.tsx index 8346352..fe712b5 100644 --- a/src/components/Flatastic/Flatastic.tsx +++ b/src/components/Flatastic/Flatastic.tsx @@ -1,21 +1,38 @@ -import fetchFlatasticChores from "@/store/thunks/fetchFlatasticChores"; +import { useFlatasticStore } from "@/store/flatastic"; import { useEffect } from "react"; -import { useDispatch } from "react-redux"; -import { type AppDispatch } from "@/store/index"; + +import type { FlatasticChore } from "@/types/flatasticChore"; + +const idToNameMap: Record = { + 1836104: "Gruber", + 1836101: "Darius", + 1593610: "Arif", + 1860060: "Rishab", +}; export default function Flatastic() { - const dispatch = useDispatch(); - useEffect(() => { - const intervalID = setInterval(() => { - dispatch(fetchFlatasticChores()); - }, 60000); // Fetch every 60 seconds + const fetchChores = useFlatasticStore((state) => state.fetch); + const chores = useFlatasticStore((state) => state.chores); - return () => clearInterval(intervalID); - }, []); + useEffect(() => { + fetchChores(); + const interval = setInterval(() => { + fetchChores(); + }, 60000); + return () => clearInterval(interval); + }, [fetchChores]); - return ( -

- Flatastic API Key: {import.meta.env.VITE_FLATTASTIC_API_KEY || "Not set"} -

- ) -} \ No newline at end of file + return ( +
+

Flatastic Chores

+
    + {chores.map((chore: FlatasticChore) => ( +
  • + {idToNameMap[chore.currentUser]}: {chore.title} - Points:{" "} + {chore.points} +
  • + ))} +
+
+ ); +} diff --git a/src/components/Timetable/Timetable.tsx b/src/components/Timetable/Timetable.tsx index 17473b4..b467c4e 100644 --- a/src/components/Timetable/Timetable.tsx +++ b/src/components/Timetable/Timetable.tsx @@ -1,63 +1,56 @@ import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { - fetchTimetable, - type AppDispatch, - type AppState, -} from "../../store/index"; -import { type DepartureList } from "../../types/types"; + import TimetableRow from "../TimetableRow/TimetableRow"; import _ from "lodash"; +import { useKVVStore } from "@/store/kvv"; +import type { DepartureType } from "@/types/departureType"; -function parseTimetableData(data: DepartureList[]) { - const result = data.map((item) => { - return { - ...item, - }; - }); +function parseTimetableData(data: DepartureType[]) { + const result = data.map((item) => { + return { + ...item, + }; + }); - return result; + return result; } export default function Timetable() { - const dispatch = useDispatch(); - useEffect(() => { - const intervalID = setInterval(() => { - dispatch(fetchTimetable()); - }, 60000); // Fetch every 60 seconds + const fetchTimetable = useKVVStore((state) => state.fetch); + const pStreet = useKVVStore((state) => state.pStreet); + const hStreet = useKVVStore((state) => state.hStreet); - return () => clearInterval(intervalID); - }, []); - const pStreet = useSelector((state: AppState) => state.timetable.pStreet); - const hStreet = useSelector((state: AppState) => state.timetable.hStreet); + useEffect(() => { + fetchTimetable(); + const interval = setInterval(() => { + fetchTimetable(); + }, 60000); + return () => clearInterval(interval); + }, [fetchTimetable]); - const hStreetData = hStreet - ? parseTimetableData(hStreet.departureList) - : []; - const pStreetData = pStreet - ? parseTimetableData(pStreet.departureList) - : []; + const hStreetData = parseTimetableData(hStreet.departureList || []); + const pStreetData = parseTimetableData(pStreet.departureList || []); - return ( -
-

Timetable

-

H-Street Departures

- - - {hStreetData.map((departure, index) => ( - - ))} - -
-

P-Street Departures

- - - {pStreetData.map((departure, index) => ( - - ))} - -
-
- ); + return ( +
+

Timetable

+

H-Street Departures

+ + + {hStreetData.map((departure, index) => ( + + ))} + +
+

P-Street Departures

+ + + {pStreetData.map((departure, index) => ( + + ))} + +
+
+ ); } diff --git a/src/components/TimetableRow/TimetableRow.tsx b/src/components/TimetableRow/TimetableRow.tsx index 445bb6f..94342b1 100644 --- a/src/components/TimetableRow/TimetableRow.tsx +++ b/src/components/TimetableRow/TimetableRow.tsx @@ -1,23 +1,22 @@ -import { type DepartureList } from "../../types/types"; +import type { DepartureType } from "@/types/departureType"; import styles from "./style.module.css"; export default function TimetableRow({ - departure, + departure, }: { - departure: DepartureList; + departure: DepartureType; }) { - const hour = String(departure.dateTime.hour).padStart(2, "0"); - const minute = String(departure.dateTime.minute).padStart(2, "0"); - const dateTimeString = `${hour}:${minute}`; - return ( - <> - - {dateTimeString} - {departure.servingLine.name} - {departure.servingLine.number} - ({departure.servingLine.direction}) - - - ); + const hour = String(departure.dateTime.hour).padStart(2, "0"); + const minute = String(departure.dateTime.minute).padStart(2, "0"); + const dateTimeString = `${hour}:${minute}`; + + return ( + + {dateTimeString} + {departure.servingLine.name} + {departure.servingLine.number} + ({departure.servingLine.direction}) + + ); } diff --git a/src/components/TimetableRow/style.module.css b/src/components/TimetableRow/style.module.css index a6f418f..bf73d63 100644 --- a/src/components/TimetableRow/style.module.css +++ b/src/components/TimetableRow/style.module.css @@ -1,3 +1,3 @@ .timetableRow { - text-align: left; + text-align: left; } diff --git a/src/index.css b/src/index.css index 6e2bee0..e543249 100644 --- a/src/index.css +++ b/src/index.css @@ -1,68 +1,68 @@ :root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + 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; + 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; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } a:hover { - color: #535bf2; + color: #535bf2; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } h1 { - font-size: 3.2em; - line-height: 1.1; + 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; + 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; + border-color: #646cff; } button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + 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; - } + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } } diff --git a/src/main.tsx b/src/main.tsx index f2d2ee4..6505447 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,21 +1,11 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { Provider } from "react-redux"; -import { PersistGate } from 'redux-persist/integration/react' -import { persistStore } from 'redux-persist' import "./index.css"; import App from "./App.tsx"; -import store from "./store/index.ts"; - -let persistor = persistStore(store) createRoot(document.getElementById("root")!).render( - - - - - - - , + + + , ); diff --git a/src/store/flatastic.ts b/src/store/flatastic.ts new file mode 100644 index 0000000..2af7ea8 --- /dev/null +++ b/src/store/flatastic.ts @@ -0,0 +1,26 @@ +import { create } from "zustand"; +import Flatastic from "@/api/flatastic"; +import type { FlatasticChore } from "@/types/flatasticChore"; + +interface FlatasticStore { + chores: FlatasticChore[]; + fetch: () => Promise; +} + +const useFlatasticStore = create( + (set: (state: Partial) => void) => ({ + chores: [], + fetch: async () => { + if (!import.meta.env.VITE_FLATTASTIC_API_KEY) { + throw new Error("Flatastic API Key is not set"); + } + const flatastic = new Flatastic(import.meta.env.VITE_FLATTASTIC_API_KEY); + const data = await flatastic.getTaskList(); + + console.log("Flatastic chores fetched:", data); + set({ chores: data as FlatasticChore[] }); + }, + }), +); + +export { useFlatasticStore }; diff --git a/src/store/index.ts b/src/store/index.ts deleted file mode 100644 index 57d7b30..0000000 --- a/src/store/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { configureStore } from "@reduxjs/toolkit"; -import { persistReducer } from "redux-persist"; -import timetableSlice from "./slices/timetableSlice"; -import flatasticChoresSlice from "./slices/flatastiucChoresSlice"; -import fetchTimetable from "./thunks/fetchTimetable"; -import persistConfig from "./persist/persistConfig"; - -const persistedTimetableReducer = persistReducer( - persistConfig, - timetableSlice.reducer -); - -const persistedFlatasticChoresReducer = persistReducer( - persistConfig, - flatasticChoresSlice.reducer -); - -const store = configureStore({ - reducer: { - timetable: persistedTimetableReducer, - flatasticChores: persistedFlatasticChoresReducer - }, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - serializableCheck: { - ignoredActions: [ - "persist/PERSIST", - "persist/REHYDRATE", - "persist/PAUSE", - "persist/FLUSH", - "persist/REGISTER", - "persist/PURGE", - ], - }, - }), -}); - -export { store, fetchTimetable }; -export default store; -export type RootState = ReturnType; -export type AppState = ReturnType; -export type AppDispatch = typeof store.dispatch; -export type ThunkDispatch = typeof store.dispatch; diff --git a/src/store/kvv.ts b/src/store/kvv.ts new file mode 100644 index 0000000..d8d5a01 --- /dev/null +++ b/src/store/kvv.ts @@ -0,0 +1,25 @@ +import { create } from "zustand"; +import { fetchKvvDepartures } from "@/api/kvv"; +import type { DepartureType } from "@/types/departureType"; +import { devtools } from "zustand/middleware"; + +const useKVVStore = create( + devtools((set) => ({ + pStreet: [] as DepartureType[], + hStreet: [] as DepartureType[], + fetch: async () => { + const hStreetStopId = 7000044; + const pStreetStopId = 7000045; + const hStreetData = await fetchKvvDepartures(hStreetStopId); + const pStreetData = await fetchKvvDepartures(pStreetStopId); + const hStreetJson = await hStreetData.json(); + const pStreetJson = await pStreetData.json(); + set({ + hStreet: hStreetJson as DepartureType[], + pStreet: pStreetJson as DepartureType[], + }); + }, + })), +); + +export { useKVVStore }; diff --git a/src/store/persist/persistConfig.ts b/src/store/persist/persistConfig.ts deleted file mode 100644 index 7e166a5..0000000 --- a/src/store/persist/persistConfig.ts +++ /dev/null @@ -1,8 +0,0 @@ -import storage from 'redux-persist/lib/storage'; - -const persistConfig = { - key: 'root', - storage, -}; - -export default persistConfig; diff --git a/src/store/slices/flatastiucChoresSlice.ts b/src/store/slices/flatastiucChoresSlice.ts deleted file mode 100644 index 2933778..0000000 --- a/src/store/slices/flatastiucChoresSlice.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import fetchFlatasticChores from "../thunks/fetchFlatasticChores"; - -const timetableSlice = createSlice({ - name: "chores", - initialState: { - chores: [] as any[], - }, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(fetchFlatasticChores.fulfilled, (state, action) => { - // Filter out timetable-related entries - state.chores = Array.isArray(action.payload) - ? action.payload.filter((item) => item.name !== "hstreeet" && item.name !== "pstreet") - : []; - }); - }, -}); - -export default timetableSlice; diff --git a/src/store/slices/timetableSlice.ts b/src/store/slices/timetableSlice.ts deleted file mode 100644 index 00abce3..0000000 --- a/src/store/slices/timetableSlice.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import fetchTimetable from "@thunks/fetchTimetable"; -import { type DepartureList } from "@/types/types"; - -const timetableSlice = createSlice({ - name: "timetable", - initialState: { - hStreet: { - departureList: [] as DepartureList[], - }, - pStreet: { - departureList: [] as DepartureList[], - }, - }, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(fetchTimetable.fulfilled, (state, action) => { - state.hStreet = action.payload.hStreet; - state.pStreet = action.payload.pStreet; - }); - }, -}); - -export default timetableSlice; diff --git a/src/store/thunks/fetchFlatasticChores.ts b/src/store/thunks/fetchFlatasticChores.ts deleted file mode 100644 index 75a6698..0000000 --- a/src/store/thunks/fetchFlatasticChores.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { Flatastic } from "@/api/flatastic"; - -const fetchFlatasticChores = createAsyncThunk("flatastic/chores", async () => { - if (!import.meta.env.VITE_FLATTASTIC_API_KEY) { - throw new Error("Flatastic API Key is not set"); - } - const flatastic = new Flatastic(import.meta.env.VITE_FLATTASTIC_API_KEY); - const data = await new Promise((resolve, reject) => { - flatastic.getTaskList((info) => { - if (info.error) { - reject(new Error(info.error)); - } else { - resolve(info); - } - }); - }); - - return data; -}); - -export default fetchFlatasticChores; diff --git a/src/store/thunks/fetchTimetable.ts b/src/store/thunks/fetchTimetable.ts deleted file mode 100644 index 0a5985d..0000000 --- a/src/store/thunks/fetchTimetable.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { callApi } from "@/api/api"; - -const fetchTimetable = createAsyncThunk("timetable/fetchTimeTable", async () => { - const hStreetStopId = 7000044; - const pStreetStopId = 7000045; - const hStreetData = await callApi(hStreetStopId); - const pStreetData = await callApi(pStreetStopId); - const hStreetJson = await hStreetData.json(); - const pStreetJson = await pStreetData.json(); - - return { - hStreet: hStreetJson, - pStreet: pStreetJson, - }; -}); - -export default fetchTimetable; diff --git a/src/types/departureType.ts b/src/types/departureType.ts new file mode 100644 index 0000000..6b14cfd --- /dev/null +++ b/src/types/departureType.ts @@ -0,0 +1,15 @@ +export type DepartureType = { + dateTime: { + year: number; + month: number; + day: number; + hour: number; + minute: number; + }; + servingLine: { + number: string; + name: string; + direction: string; + }; + stopID: number; +}; diff --git a/src/types/flatasticChore.ts b/src/types/flatasticChore.ts new file mode 100644 index 0000000..2a9966e --- /dev/null +++ b/src/types/flatasticChore.ts @@ -0,0 +1,13 @@ +interface FlatasticChore { + id: number; + title: string; + details: string | null; + users: Array; + points: number; + rotationTime: number; + currentUser: number; + lastDoneDate: string; + timeLeftNext: number; +} + +export type { FlatasticChore }; diff --git a/src/types/types.ts b/src/types/types.ts deleted file mode 100644 index a8bc59f..0000000 --- a/src/types/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type DepartureList = { - dateTime: { - year: number; - month: number; - day: number; - hour: number; - minute: number; - }; - servingLine: { - number: string; - name: string; - direction: string; - }; -}; diff --git a/tsconfig.app.json b/tsconfig.app.json index e1f695d..613f1c8 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,33 +1,33 @@ { - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - "baseUrl": ".", - "paths": { - "@/*": ["src/*"], - "@components/*": ["src/components/*"], - "@store/*": ["src/store/*"], - "@api/*": ["src/api/*"], - "@types/*": ["src/types/*"], - "@thunks/*": ["src/store/thunks/*"], - "@slices/*": ["src/store/slices/*"], - }, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@store/*": ["src/store/*"], + "@api/*": ["src/api/*"], + "@types/*": ["src/types/*"], + "@thunks/*": ["src/store/thunks/*"], + "@slices/*": ["src/store/slices/*"] + }, - /* Linting */ - "strict": true, - "noUnusedLocals": true - }, - "include": ["src"] + /* Linting */ + "strict": true, + "noUnusedLocals": true + }, + "include": ["src"] } diff --git a/tsconfig.json b/tsconfig.json index d13f7ec..e545e51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,30 +1,30 @@ { - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["src/*"], - "@components/*": ["src/components/*"], - "@store/*": ["src/store/*"], - "@api/*": ["src/api/*"], - "@types/*": ["src/types/*"], - "@thunks/*": ["src/store/thunks/*"], - "@slices/*": ["src/store/slices/*"] - }, - "module": "esnext", - "moduleResolution": "node", - "target": "esnext", - "lib": ["dom", "dom.iterable", "esnext"], - "jsx": "react-jsx", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "resolveJsonModule": true - } + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@store/*": ["src/store/*"], + "@api/*": ["src/api/*"], + "@types/*": ["src/types/*"], + "@thunks/*": ["src/store/thunks/*"], + "@slices/*": ["src/store/slices/*"] + }, + "module": "esnext", + "moduleResolution": "node", + "target": "esnext", + "lib": ["dom", "dom.iterable", "esnext"], + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "resolveJsonModule": true + } } diff --git a/tsconfig.node.json b/tsconfig.node.json index 22b15d3..1a5ed45 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,25 +1,25 @@ { - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 9568ffa..87e846b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,16 +4,16 @@ import path from "path"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "src"), - "@components": path.resolve(__dirname, "src/components"), - "@store": path.resolve(__dirname, "src/store"), - "@api": path.resolve(__dirname, "src/api"), - "@types": path.resolve(__dirname, "src/types"), - "@thunks": path.resolve(__dirname, "src/store/thunks"), - "@slices": path.resolve(__dirname, "src/store/slices"), - }, - }, + plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + "@components": path.resolve(__dirname, "src/components"), + "@store": path.resolve(__dirname, "src/store"), + "@api": path.resolve(__dirname, "src/api"), + "@types": path.resolve(__dirname, "src/types"), + "@thunks": path.resolve(__dirname, "src/store/thunks"), + "@slices": path.resolve(__dirname, "src/store/slices"), + }, + }, });