From 4fc6f94b0e8d3706034d1df10baf28ba8b681499 Mon Sep 17 00:00:00 2001 From: Dominic Zimmer Date: Sat, 3 May 2025 12:16:36 +0200 Subject: [PATCH] Implement working prototype --- package-lock.json | 7 +++ package.json | 1 + server/file.json | 54 ++++++++++++++++++++- src/App.tsx | 120 +++++++++++++++++++++++++++++++++------------- src/api.ts | 77 +++++++++++++++++++++++++++++ src/types.ts | 26 ++++++++++ 6 files changed, 250 insertions(+), 35 deletions(-) create mode 100644 src/api.ts create mode 100644 src/types.ts diff --git a/package-lock.json b/package-lock.json index 195f025..486bbff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.3", + "dayjs": "^1.11.13", "lowdb": "^7.0.1", "react": "^19.1.0", "react-dom": "^19.1.0", @@ -6469,6 +6470,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", diff --git a/package.json b/package.json index 9b06c21..45c5712 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.3", + "dayjs": "^1.11.13", "lowdb": "^7.0.1", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/server/file.json b/server/file.json index ce75971..7c9ad6b 100644 --- a/server/file.json +++ b/server/file.json @@ -1,4 +1,54 @@ { - "foo": "bar", - "baz": 42 + "entries": { + "20250406": { + "items": [ + { + "variant": "eats", + "date": "Sat May 03 2025 12:07:11 GMT+0200 (Central European Summer Time)", + "item": "strawberries" + }, + { + "variant": "eats", + "date": "Sat May 03 2025 12:08:00 GMT+0200 (Central European Summer Time)", + "item": "nothing" + }, + { + "variant": "eats", + "date": "Sat May 03 2025 12:09:03 GMT+0200 (Central European Summer Time)", + "item": "nuttin" + }, + { + "variant": "eats", + "date": "Sat May 03 2025 12:09:56 GMT+0200 (Central European Summer Time)", + "item": "foo" + }, + { + "variant": "eats", + "date": "Sat May 03 2025 12:12:57 GMT+0200 (Central European Summer Time)", + "item": "swine" + }, + { + "variant": "allergy", + "date": "Sat May 03 2025 12:16:05 GMT+0200 (Central European Summer Time)" + }, + { + "variant": "eats", + "date": "Sat May 03 2025 12:16:10 GMT+0200 (Central European Summer Time)", + "item": "Nothing much" + }, + { + "variant": "eats", + "date": "Sat May 03 2025 12:16:13 GMT+0200 (Central European Summer Time)", + "item": "Mushrooms" + } + ] + } + }, + "index": [ + [ + 20250406, + "2025-05-03T09:28:49.374Z" + ] + ], + "version": 0 } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index f2d92ca..7d5d446 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,45 +1,99 @@ -import React from 'react'; +import React, { JSX, useEffect, useState } from 'react'; import logo from './logo.svg'; import './App.css'; +import { addEntry, ensureToday, getAPI, getToday } from './api'; +import { ItemType, Store, StoreEntry } from './types'; +import dayjs from 'dayjs'; + +const defaultStore: Store = { + index: [], + version: 0, + entries: {}, +}; const key="c3dpZ2dpdHlzd29vdHkK" -function App() { +export const App : React.FC = () => { + const [API, setAPI] = useState(() => getAPI(key)); + const [store, setStore] = useState(defaultStore); + useEffect(() => { + const f = async () => { + const st = await API.read(); + setStore(ensureToday(st)); + }; + f(); + }, [API]); + + const [input, setInput] = useState(""); + + return (
-
- logo -

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

- { - const rawResponse = await fetch(`http://localhost:4444/write/${key}/`, { - method: 'POST', - headers: { - }, - body: JSON.stringify({a: 1, b: 'Textual content'}) - }); - await rawResponse.text(); + +
-
+ >Now! + + {store ? + + :null} +
+ Raw Store +
+        {JSON.stringify(store, null, 4)}
+        
+
+ ); -} +}; + +const DATEFORMAT = "YYYY MM DD HH:mm"; + +export const RenderTable : React.FC<{store: Store}> = ({store}) => { + const listEntries = Object + .values(store.entries) + .flatMap((entry) => entry.items) + .sort((a,b) => b.date.localeCompare(a.date)); + const itemToRow = (item: ItemType, index: number): JSX.Element => { + const datestring = dayjs(new Date(item.date)).format(DATEFORMAT) + if (item.variant === "eats") { + return {datestring}ate {item.item}; + } else if (item.variant === "allergy") { + return {datestring}Reaction!; + } + return <>; + } + return + + + + + {listEntries.map(itemToRow)} + +
TimeEvent
+}; export default App; diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..34c8f79 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,77 @@ +import { ItemType, Store, StoreEntry } from "./types" + + +const defaultEndpoint = "http://localhost:4444/" + +type APIEndPoint = { + write: (json: string) => Promise, + read: () => Promise, +}; + + const makeEndpoint = (key: string, method: string, endPointOverride?: string): string => { + return `${endPointOverride ?? defaultEndpoint}\/${method}\/${key}\/`; + } + + + export const getAPI = (key: string, endPointOverride?: string): APIEndPoint => ({ + write: async (json: string) => { + const rawResponse = await fetch(makeEndpoint(key, "write", endPointOverride), { + method: 'POST', + headers: { + }, + body: json, + }); + const resp = await rawResponse.text(); + return; + }, + read: async () => { + const rawResponse = await fetch(makeEndpoint(key, "read", endPointOverride), { + method: 'POST' + }); + const respText = await rawResponse.text(); + return JSON.parse(respText, ) as Store; + } + }); + + export const ensureToday = (store: Store): Store => { + const today : Date = new Date(); + const storeEntry = store.index.find(([key, dateString]) => { + const date = new Date(dateString); + const [y, m, d] = [today.getFullYear(), today.getMonth(), today.getDay()]; + return (date.getFullYear() === y && date.getMonth() === m && date.getDay() == d); + }); + + if (storeEntry !== undefined) return store; + + const indexKey = today.getDay() + today.getMonth() * 100 + today.getFullYear() * 10000; + const entries = store.entries; + const newEntry : StoreEntry = { + items: [], + } + entries[indexKey] = newEntry; + + return { + entries: entries, + index: [[indexKey, today.toString()], ...store.index ], + version: store.version, + }; + } + + export const addEntry = (store: Store, entry: ItemType): Store => { + const [storeIndex, dateString] = getToday(ensureToday(store))!; + const today = store.entries[storeIndex]; + today.items = [...today.items, entry]; + store.entries[storeIndex] = today; + return store; + } + + export const getToday = (store: Store) => { + if (!store) return; + const today : Date = new Date(); + const storeEntry = store.index.find(([key, dateString]) => { + const date = new Date(dateString); + const [y, m, d] = [today.getFullYear(), today.getMonth(), today.getDay()]; + return (date.getFullYear() === y && date.getMonth() === m && date.getDay() == d); + }); + return storeEntry; + }; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..03acac4 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,26 @@ +export type DateString = string; +export type VersionedStore = { + 1: { + index: Array<[number, DateString]>, + entries: {[ key: number ]: StoreEntry } + version: 0, + }, +} + +export type Store = VersionedStore[1]; + +export type ItemType = ({ + date: DateString, + variant: "eats" | "allergy", + } & ({ + variant: "eats", + item: string + } | { + variant: "allergy" + })); + +export type StoreEntry = { + items: Array +} + +export default {};