Implement working prototype

This commit is contained in:
Dominic Zimmer 2025-05-03 12:16:36 +02:00
parent 9979d96022
commit 4fc6f94b0e
6 changed files with 250 additions and 35 deletions

7
package-lock.json generated
View File

@ -16,6 +16,7 @@
"@types/node": "^16.18.126", "@types/node": "^16.18.126",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3", "@types/react-dom": "^19.1.3",
"dayjs": "^1.11.13",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
@ -6469,6 +6470,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/debug": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",

View File

@ -11,6 +11,7 @@
"@types/node": "^16.18.126", "@types/node": "^16.18.126",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3", "@types/react-dom": "^19.1.3",
"dayjs": "^1.11.13",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",

View File

@ -1,4 +1,54 @@
{ {
"foo": "bar", "entries": {
"baz": 42 "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
} }

View File

@ -1,45 +1,99 @@
import React from 'react'; import React, { JSX, useEffect, useState } from 'react';
import logo from './logo.svg'; import logo from './logo.svg';
import './App.css'; 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" const key="c3dpZ2dpdHlzd29vdHkK"
function App() { export const App : React.FC = () => {
const [API, setAPI] = useState(() => getAPI(key));
const [store, setStore] = useState<Store>(defaultStore);
useEffect(() => {
const f = async () => {
const st = await API.read();
setStore(ensureToday(st));
};
f();
}, [API]);
const [input, setInput] = useState<string>("");
return ( return (
<div className="App"> <div className="App">
<header className="App-header"> <div className="eats">
<img src={logo} className="App-logo" alt="logo" /> <h1>I ate ...</h1>
<p> <form>
Edit <code>src/App.tsx</code> and save to reload. <input type="text" placeholder='Stinking Fish' onChange={(e) => setInput(e.target.value)} value={input} />
</p> <button
<a onClick={ async () => {
className="App-link" const newst = addEntry(store, {variant: "eats", date: new Date().toString(), item: input} );
onClick={async () => { console.log("newstore is", newst);
const rawResponse = await fetch(`http://localhost:4444/write/${key}/`, { await API.write(JSON.stringify(store))
method: 'POST', const fromserver = await API.read();
headers: { setStore({...fromserver} );
}, setInput("");
body: JSON.stringify({a: 1, b: 'Textual content'})
});
await rawResponse.text();
}} }}
> disabled={input.trim().length < 2}
Write >Submit</button>
</a> </form>
<a </div>
className="App-link" <div className="reacts">
onClick={async () => { <h1>I have allergy</h1>
const rawResponse = await fetch(`http://localhost:4444/read/${key}/`, { <button
method: 'POST' onClick={ async () => {
}); const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
await rawResponse.text(); await API.write(JSON.stringify(store))
const fromserver = await API.read();
setStore({...fromserver} );
}} }}
> >Now!</button>
Read </div>
</a> {store ?
</header> <RenderTable store={store} />
</div> :null}
<details name='foo'>
<summary>Raw Store</summary>
<pre>
{JSON.stringify(store, null, 4)}
</pre>
</details>
</div>
); );
} };
const DATEFORMAT = "YYYY MM DD HH:mm";
export const RenderTable : React.FC<{store: Store}> = ({store}) => {
const listEntries = Object
.values(store.entries)
.flatMap<ItemType>((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 <tr key={index} className="eats"><td>{datestring}</td><td><small>ate</small></td><td> {item.item}</td></tr>;
} else if (item.variant === "allergy") {
return <tr key={index} className="eats"><td>{datestring}</td><td colSpan={2}><small>Reaction!</small></td></tr>;
}
return <></>;
}
return <table className="entry-table">
<thead>
<tr><th>Time</th><th colSpan={2}>Event</th></tr>
</thead>
<tbody>
{listEntries.map(itemToRow)}
</tbody>
</table>
};
export default App; export default App;

77
src/api.ts Normal file
View File

@ -0,0 +1,77 @@
import { ItemType, Store, StoreEntry } from "./types"
const defaultEndpoint = "http://localhost:4444/"
type APIEndPoint = {
write: (json: string) => Promise<void>,
read: () => Promise<Store>,
};
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;
};

26
src/types.ts Normal file
View File

@ -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<ItemType>
}
export default {};