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.
|
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>
|
||||||
|
@ -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)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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) => {
|
||||||
|
81
src/App.css
81
src/App.css
@ -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);
|
}
|
||||||
}
|
.entry-table table {
|
||||||
to {
|
border-spacing: 12px 2px;
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
83
src/App.tsx
83
src/App.tsx
@ -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>
|
||||||
|
12
src/api.ts
12
src/api.ts
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user