Finish prototype
This commit is contained in:
parent
4fc6f94b0e
commit
3fb4ec8b7b
@ -24,7 +24,7 @@
|
||||
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`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Shouldnthave</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
@ -12,16 +12,6 @@
|
||||
"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)",
|
||||
@ -40,6 +30,15 @@
|
||||
"variant": "eats",
|
||||
"date": "Sat May 03 2025 12:16:13 GMT+0200 (Central European Summer Time)",
|
||||
"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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ const key = "c3dpZ2dpdHlzd29vdHkK";
|
||||
const reStoreWrite = new RegExp(`/write/${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 server = createServer((req, res) => {
|
||||
|
81
src/App.css
81
src/App.css
@ -1,38 +1,63 @@
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100vh
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: #A0C878;
|
||||
}
|
||||
.toprow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.toprow > div {
|
||||
width: 45%;
|
||||
}
|
||||
h1,h2,h3 {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
.eats form div {
|
||||
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 {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.entry-table {
|
||||
width: 80%;
|
||||
}
|
||||
.entry-table table {
|
||||
border-spacing: 12px 2px;
|
||||
}
|
||||
|
131
src/App.tsx
131
src/App.tsx
@ -1,7 +1,6 @@
|
||||
import React, { JSX, useEffect, useState } from 'react';
|
||||
import logo from './logo.svg';
|
||||
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 dayjs from 'dayjs';
|
||||
|
||||
@ -11,7 +10,8 @@ const defaultStore: Store = {
|
||||
entries: {},
|
||||
};
|
||||
|
||||
const key="c3dpZ2dpdHlzd29vdHkK"
|
||||
//const key="c3dpZ2dpdHlzd29vdHkK"
|
||||
const key = localStorage.getItem("apikey") ?? "";
|
||||
|
||||
export const App : React.FC = () => {
|
||||
const [API, setAPI] = useState(() => getAPI(key));
|
||||
@ -24,42 +24,58 @@ export const App : React.FC = () => {
|
||||
f();
|
||||
}, [API]);
|
||||
|
||||
const [showAPIField, setShowAPIField] = useState(false);
|
||||
|
||||
|
||||
const [input, setInput] = useState<string>("");
|
||||
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="eats">
|
||||
<h1>I ate ...</h1>
|
||||
<form>
|
||||
<input type="text" placeholder='Stinking Fish' onChange={(e) => setInput(e.target.value)} value={input} />
|
||||
<button
|
||||
onClick={ async () => {
|
||||
const newst = addEntry(store, {variant: "eats", date: new Date().toString(), item: input} );
|
||||
console.log("newstore is", newst);
|
||||
await API.write(JSON.stringify(store))
|
||||
const fromserver = await API.read();
|
||||
setStore({...fromserver} );
|
||||
setInput("");
|
||||
}}
|
||||
disabled={input.trim().length < 2}
|
||||
>Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="reacts">
|
||||
<h1>I have allergy</h1>
|
||||
<button
|
||||
onClick={ async () => {
|
||||
const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
|
||||
await API.write(JSON.stringify(store))
|
||||
const fromserver = await API.read();
|
||||
setStore({...fromserver} );
|
||||
}}
|
||||
>Now!</button>
|
||||
<h1>SHULNEFF</h1>
|
||||
<div className="toprow">
|
||||
<div className="eats">
|
||||
<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>
|
||||
<div>
|
||||
<input type="text" placeholder='Stinking Fish' onChange={(e) => setInput(e.target.value)} value={input} />
|
||||
<button
|
||||
onClick={ async () => {
|
||||
const newst = addEntry(store, {variant: "eats", date: new Date().toString(), item: input} );
|
||||
console.log("newstore is", newst);
|
||||
await API.write(JSON.stringify(store))
|
||||
const fromserver = await API.read();
|
||||
setStore({...fromserver} );
|
||||
setInput("");
|
||||
}}
|
||||
disabled={input.trim().length < 2}
|
||||
>Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="reacts">
|
||||
<button
|
||||
onClick={ async () => {
|
||||
const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
|
||||
await API.write(JSON.stringify(store))
|
||||
const fromserver = await API.read();
|
||||
setStore({...fromserver} );
|
||||
}}
|
||||
><h3>Allergy Alert!</h3></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
{store ?
|
||||
<RenderTable store={store} />
|
||||
<div className={"entry-table"}>
|
||||
<RenderTable API={API} store={store} setStore={setStore} />
|
||||
</div>
|
||||
:null}
|
||||
<hr/>
|
||||
<details name='foo'>
|
||||
<summary>Raw Store</summary>
|
||||
<pre>
|
||||
@ -72,21 +88,66 @@ export const App : React.FC = () => {
|
||||
|
||||
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
|
||||
.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)
|
||||
const isSelected = (selectedItem !== null) && JSON.stringify(item) === JSON.stringify(selectedItem);
|
||||
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") {
|
||||
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 <table className="entry-table">
|
||||
return <table>
|
||||
<thead>
|
||||
<tr><th>Time</th><th colSpan={2}>Event</th></tr>
|
||||
</thead>
|
||||
|
14
src/api.ts
14
src/api.ts
@ -1,11 +1,11 @@
|
||||
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>,
|
||||
read: () => Promise<Store>,
|
||||
read: () => Promise<Store>,
|
||||
};
|
||||
|
||||
const makeEndpoint = (key: string, method: string, endPointOverride?: string): string => {
|
||||
@ -65,6 +65,14 @@ type APIEndPoint = {
|
||||
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) => {
|
||||
if (!store) return;
|
||||
const today : Date = new Date();
|
||||
|
Loading…
Reference in New Issue
Block a user