Finish prototype

This commit is contained in:
Dominic Zimmer 2025-05-03 13:16:13 +02:00
parent 4fc6f94b0e
commit 3fb4ec8b7b
6 changed files with 172 additions and 78 deletions

View File

@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>Shouldnthave</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -12,16 +12,6 @@
"date": "Sat May 03 2025 12:08:00 GMT+0200 (Central European Summer Time)", "date": "Sat May 03 2025 12:08:00 GMT+0200 (Central European Summer Time)",
"item": "nothing" "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", "variant": "eats",
"date": "Sat May 03 2025 12:12:57 GMT+0200 (Central European Summer Time)", "date": "Sat May 03 2025 12:12:57 GMT+0200 (Central European Summer Time)",
@ -40,6 +30,15 @@
"variant": "eats", "variant": "eats",
"date": "Sat May 03 2025 12:16:13 GMT+0200 (Central European Summer Time)", "date": "Sat May 03 2025 12:16:13 GMT+0200 (Central European Summer Time)",
"item": "Mushrooms" "item": "Mushrooms"
},
{
"variant": "eats",
"date": "Sat May 03 2025 12:29:55 GMT+0200 (Central European Summer Time)",
"item": "something"
},
{
"variant": "allergy",
"date": "Sat May 03 2025 13:03:52 GMT+0200 (Central European Summer Time)"
} }
] ]
} }

View File

@ -9,7 +9,8 @@ const key = "c3dpZ2dpdHlzd29vdHkK";
const reStoreWrite = new RegExp(`/write/${key}/?`) const reStoreWrite = new RegExp(`/write/${key}/?`)
const reStoreRead = new RegExp(`/read/${key}/?`) const reStoreRead = new RegExp(`/read/${key}/?`)
const hostname = '127.0.0.1'; let hostname = '127.0.0.1';
hostname = "0.0.0.0";
const port = 4444; const port = 4444;
const server = createServer((req, res) => { const server = createServer((req, res) => {

View File

@ -1,38 +1,63 @@
body {
width: 100%;
height: 100vh
}
#root {
width: 100%;
height: 100%;
}
.App { .App {
text-align: center; width: 100%;
} height: 100%;
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background-color: #A0C878;
}
.toprow {
display: flex;
flex-direction: row;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); gap: 15px;
color: white; width: 100%;
}
.toprow > div {
width: 45%;
}
h1,h2,h3 {
user-select: none;
} }
.App-link { .eats form div {
color: #61dafb; display: flex;
flex-direction: column;
gap: 10px;
}
.entry-table .reacts {
background-color: #DDEB9D;
}
.entry-table .eats:nth-child(2n+1) {
background-color: #FFFDF6;
}
.entry-table .eats:nth-child(2n) {
background-color: #FAF6E9;
}
.entry-table .delete {
background-color: #F16767;
}
.entry-table table td {
text-align: center;
}
.toprow .reacts {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
} }
@keyframes App-logo-spin { .entry-table {
from { width: 80%;
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
} }
.entry-table table {
border-spacing: 12px 2px;
} }

View File

@ -1,7 +1,6 @@
import React, { JSX, useEffect, useState } from 'react'; import React, { JSX, useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css'; import './App.css';
import { addEntry, ensureToday, getAPI, getToday } from './api'; import { addEntry, APIEndPoint, ensureToday, getAPI, getToday, removeEntry } from './api';
import { ItemType, Store, StoreEntry } from './types'; import { ItemType, Store, StoreEntry } from './types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -11,7 +10,8 @@ const defaultStore: Store = {
entries: {}, entries: {},
}; };
const key="c3dpZ2dpdHlzd29vdHkK" //const key="c3dpZ2dpdHlzd29vdHkK"
const key = localStorage.getItem("apikey") ?? "";
export const App : React.FC = () => { export const App : React.FC = () => {
const [API, setAPI] = useState(() => getAPI(key)); const [API, setAPI] = useState(() => getAPI(key));
@ -24,14 +24,25 @@ export const App : React.FC = () => {
f(); f();
}, [API]); }, [API]);
const [showAPIField, setShowAPIField] = useState(false);
const [input, setInput] = useState<string>(""); const [input, setInput] = useState<string>("");
return ( return (
<div className="App"> <div className="App">
<h1>SHULNEFF</h1>
<div className="toprow">
<div className="eats"> <div className="eats">
<h1>I ate ...</h1> <h2 onContextMenu={(e) => {e.preventDefault(); setShowAPIField(!showAPIField);}}>I ate ...</h2>
{showAPIField?
<input placeholder='API Token' onChange={(e) => {
localStorage.setItem("apikey", e.target.value);
}}/>
:null}
<form> <form>
<div>
<input type="text" placeholder='Stinking Fish' onChange={(e) => setInput(e.target.value)} value={input} /> <input type="text" placeholder='Stinking Fish' onChange={(e) => setInput(e.target.value)} value={input} />
<button <button
onClick={ async () => { onClick={ async () => {
@ -44,10 +55,10 @@ export const App : React.FC = () => {
}} }}
disabled={input.trim().length < 2} disabled={input.trim().length < 2}
>Submit</button> >Submit</button>
</div>
</form> </form>
</div> </div>
<div className="reacts"> <div className="reacts">
<h1>I have allergy</h1>
<button <button
onClick={ async () => { onClick={ async () => {
const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} ); const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
@ -55,11 +66,16 @@ export const App : React.FC = () => {
const fromserver = await API.read(); const fromserver = await API.read();
setStore({...fromserver} ); setStore({...fromserver} );
}} }}
>Now!</button> ><h3>Allergy Alert!</h3></button>
</div> </div>
</div>
<hr/>
{store ? {store ?
<RenderTable store={store} /> <div className={"entry-table"}>
<RenderTable API={API} store={store} setStore={setStore} />
</div>
:null} :null}
<hr/>
<details name='foo'> <details name='foo'>
<summary>Raw Store</summary> <summary>Raw Store</summary>
<pre> <pre>
@ -72,21 +88,66 @@ export const App : React.FC = () => {
const DATEFORMAT = "YYYY MM DD HH:mm"; const DATEFORMAT = "YYYY MM DD HH:mm";
export const RenderTable : React.FC<{store: Store}> = ({store}) => { export const RenderTable : React.FC<{API: APIEndPoint, store: Store, setStore: (store: Store) => void}> = ({store, API, setStore}) => {
const [selectedItem, setSelected] = useState<ItemType|null>(null);
const listEntries = Object const listEntries = Object
.values(store.entries) .values(store.entries)
.flatMap<ItemType>((entry) => entry.items) .flatMap<ItemType>((entry) => entry.items)
.sort((a,b) => b.date.localeCompare(a.date)); .sort((a,b) => b.date.localeCompare(a.date));
const itemToRow = (item: ItemType, index: number): JSX.Element => { const itemToRow = (item: ItemType, index: number): JSX.Element => {
const datestring = dayjs(new Date(item.date)).format(DATEFORMAT) const datestring = dayjs(new Date(item.date)).format(DATEFORMAT)
const isSelected = (selectedItem !== null) && JSON.stringify(item) === JSON.stringify(selectedItem);
if (item.variant === "eats") { if (item.variant === "eats") {
return <tr key={index} className="eats"><td>{datestring}</td><td><small>ate</small></td><td> {item.item}</td></tr>; return <tr
onContextMenu={(e) => {
e.preventDefault();
if (isSelected) setSelected(null);
else setSelected(item);
}}
onClick={async (e) => {
if (isSelected) {
const newst = removeEntry(store, item );
console.log("newstore is", newst);
await API.write(JSON.stringify(store))
const fromserver = await API.read();
setStore({...fromserver} );
}
}}
key={index} className="eats"><td>{datestring}</td><td><small>ate</small></td>
{isSelected ?
<td className="delete">X</td>
: <td> {item.item}</td>
}
</tr>;
} else if (item.variant === "allergy") { } else if (item.variant === "allergy") {
return <tr key={index} className="eats"><td>{datestring}</td><td colSpan={2}><small>Reaction!</small></td></tr>; return <tr
onContextMenu={(e) => {
e.preventDefault();
if (isSelected) setSelected(null);
else setSelected(item);
}}
onClick={async (e) => {
if (isSelected) {
const newst = removeEntry(store, item );
console.log("newstore is", newst);
await API.write(JSON.stringify(store))
const fromserver = await API.read();
setStore({...fromserver} );
}
}}
key={index} className="reacts"><td>{datestring}</td>
{isSelected ?
<td colSpan={2} className="delete">X</td>
: <td colSpan={2}><small>Reaction!</small></td>
}
</tr>;
} }
return <></>; return <></>;
} }
return <table className="entry-table"> return <table>
<thead> <thead>
<tr><th>Time</th><th colSpan={2}>Event</th></tr> <tr><th>Time</th><th colSpan={2}>Event</th></tr>
</thead> </thead>

View File

@ -1,9 +1,9 @@
import { ItemType, Store, StoreEntry } from "./types" import { ItemType, Store, StoreEntry } from "./types"
const defaultEndpoint = "http://localhost:4444/" const defaultEndpoint = "http://slateport:4444"
type APIEndPoint = { export type APIEndPoint = {
write: (json: string) => Promise<void>, write: (json: string) => Promise<void>,
read: () => Promise<Store>, read: () => Promise<Store>,
}; };
@ -65,6 +65,14 @@ type APIEndPoint = {
return store; return store;
} }
export const removeEntry = (store: Store, entry: ItemType): Store => {
const [storeIndex, dateString] = getToday(ensureToday(store))!;
const today = store.entries[storeIndex];
today.items = today.items.filter((item) => JSON.stringify(item) !== JSON.stringify(entry));
store.entries[storeIndex] = today;
return store;
}
export const getToday = (store: Store) => { export const getToday = (store: Store) => {
if (!store) return; if (!store) return;
const today : Date = new Date(); const today : Date = new Date();