Implement working prototype
This commit is contained in:
parent
9979d96022
commit
4fc6f94b0e
7
package-lock.json
generated
7
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
}
|
}
|
112
src/App.tsx
112
src/App.tsx
@ -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
|
|
||||||
className="App-link"
|
|
||||||
onClick={ async () => {
|
onClick={ async () => {
|
||||||
const rawResponse = await fetch(`http://localhost:4444/write/${key}/`, {
|
const newst = addEntry(store, {variant: "eats", date: new Date().toString(), item: input} );
|
||||||
method: 'POST',
|
console.log("newstore is", newst);
|
||||||
headers: {
|
await API.write(JSON.stringify(store))
|
||||||
},
|
const fromserver = await API.read();
|
||||||
body: JSON.stringify({a: 1, b: 'Textual content'})
|
setStore({...fromserver} );
|
||||||
});
|
setInput("");
|
||||||
await rawResponse.text();
|
|
||||||
}}
|
}}
|
||||||
>
|
disabled={input.trim().length < 2}
|
||||||
Write
|
>Submit</button>
|
||||||
</a>
|
</form>
|
||||||
<a
|
</div>
|
||||||
className="App-link"
|
<div className="reacts">
|
||||||
|
<h1>I have allergy</h1>
|
||||||
|
<button
|
||||||
onClick={ async () => {
|
onClick={ async () => {
|
||||||
const rawResponse = await fetch(`http://localhost:4444/read/${key}/`, {
|
const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
|
||||||
method: 'POST'
|
await API.write(JSON.stringify(store))
|
||||||
});
|
const fromserver = await API.read();
|
||||||
await rawResponse.text();
|
setStore({...fromserver} );
|
||||||
}}
|
}}
|
||||||
>
|
>Now!</button>
|
||||||
Read
|
</div>
|
||||||
</a>
|
{store ?
|
||||||
</header>
|
<RenderTable store={store} />
|
||||||
|
:null}
|
||||||
|
<details name='foo'>
|
||||||
|
<summary>Raw Store</summary>
|
||||||
|
<pre>
|
||||||
|
{JSON.stringify(store, null, 4)}
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
</div>
|
</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
77
src/api.ts
Normal 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
26
src/types.ts
Normal 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 {};
|
Loading…
Reference in New Issue
Block a user