Compare commits
2 Commits
998d9a2590
...
1c0b107188
Author | SHA1 | Date | |
---|---|---|---|
|
1c0b107188 | ||
|
1293f058b3 |
1
.env.production
Normal file
1
.env.production
Normal file
@ -0,0 +1 @@
|
|||||||
|
REACT_APP_API_URL=https://shulneffapi.leafbla.de
|
26
data/file.json
Normal file
26
data/file.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"entries": {
|
||||||
|
"20250511": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"variant": "allergy",
|
||||||
|
"date": "2025-05-11 23:57:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variant": "eats",
|
||||||
|
"item": "Nix gesse",
|
||||||
|
"date": "2025-05-11 23:57:13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variant": "eats",
|
||||||
|
"item": "Manchmal was gesse",
|
||||||
|
"date": "2025-05-11 23:57:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variant": "allergy",
|
||||||
|
"date": "2025-05-11 23:57:26"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,8 @@
|
|||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"server": "node server/server.js",
|
||||||
|
"frontend": "react-scripts start"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
@ -33,6 +33,9 @@ h1,h2,h3 {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
.entry-table td {
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
.entry-table .reacts {
|
.entry-table .reacts {
|
||||||
background-color: #DDEB9D;
|
background-color: #DDEB9D;
|
||||||
}
|
}
|
||||||
|
32
src/App.tsx
32
src/App.tsx
@ -1,12 +1,11 @@
|
|||||||
import React, { JSX, useEffect, useState } from 'react';
|
import React, { JSX, useEffect, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { addEntry, APIEndPoint, ensureToday, getAPI, getToday, removeEntry } from './api';
|
import { addEntry, APIEndPoint, getAPI, removeEntry } from './api';
|
||||||
import { ItemType, Store, StoreEntry } from './types';
|
import { ItemType, Store, WithoutDate } from './types';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const defaultStore: Store = {
|
const defaultStore: Store = {
|
||||||
index: [],
|
version: 1,
|
||||||
version: 0,
|
|
||||||
entries: {},
|
entries: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ export const App : React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const f = async () => {
|
const f = async () => {
|
||||||
const st = await API.read();
|
const st = await API.read();
|
||||||
setStore(ensureToday(st));
|
setStore(st);
|
||||||
};
|
};
|
||||||
f();
|
f();
|
||||||
}, [API]);
|
}, [API]);
|
||||||
@ -27,15 +26,16 @@ export const App : React.FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
const formSubmit = async () => {
|
const formSubmit = async () => {
|
||||||
const newst = addEntry(store, {variant: "eats", date: new Date().toString(), item: input} );
|
const entry : WithoutDate<ItemType> = {variant: "eats", item: input};
|
||||||
console.log("newstore is", newst);
|
const newStore = addEntry(store, entry);
|
||||||
API.write(JSON.stringify(store)).then(async () => {
|
console.log("newstore is", newStore);
|
||||||
setTimeout(async () => {
|
API.write(JSON.stringify(store)).then(async () => {
|
||||||
const fromserver = await API.read();
|
setTimeout(async () => {
|
||||||
setStore({...fromserver} );
|
const fromserver = await API.read();
|
||||||
setInput("");
|
setStore({...fromserver} );
|
||||||
}, 150);
|
setInput("");
|
||||||
})
|
}, 150);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
const [input, setInput] = useState<string>("");
|
const [input, setInput] = useState<string>("");
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ export const App : React.FC = () => {
|
|||||||
<div className="reacts">
|
<div className="reacts">
|
||||||
<button
|
<button
|
||||||
onClick={ async () => {
|
onClick={ async () => {
|
||||||
const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
|
const newst = addEntry(store, {variant: "allergy"} );
|
||||||
await API.write(JSON.stringify(store))
|
await API.write(JSON.stringify(store))
|
||||||
const fromserver = await API.read();
|
const fromserver = await API.read();
|
||||||
setStore({...fromserver} );
|
setStore({...fromserver} );
|
||||||
@ -96,7 +96,7 @@ export const App : React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DATEFORMAT = "YYYY MM DD HH:mm";
|
const DATEFORMAT = "MMM DD, HH:mm";
|
||||||
|
|
||||||
export const RenderTable : React.FC<{API: APIEndPoint, store: Store, setStore: (store: Store) => void}> = ({store, API, setStore}) => {
|
export const RenderTable : React.FC<{API: APIEndPoint, store: Store, setStore: (store: Store) => void}> = ({store, API, setStore}) => {
|
||||||
|
|
||||||
|
62
src/api.ts
62
src/api.ts
@ -1,7 +1,7 @@
|
|||||||
import { ItemType, Store, StoreEntry } from "./types"
|
import { currentTimestamp, getDateID, ItemType, Store, StoreEntry, WithoutDate } from "./types"
|
||||||
|
|
||||||
|
|
||||||
const defaultEndpoint = "https://shulneffapi.leafbla.de"
|
const endpoint = process.env.REACT_APP_API_URL ?? "API endpoint missing!";
|
||||||
|
|
||||||
export type APIEndPoint = {
|
export type APIEndPoint = {
|
||||||
write: (json: string) => Promise<void>,
|
write: (json: string) => Promise<void>,
|
||||||
@ -9,7 +9,7 @@ export type APIEndPoint = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const makeEndpoint = (key: string, method: string, endPointOverride?: string): string => {
|
const makeEndpoint = (key: string, method: string, endPointOverride?: string): string => {
|
||||||
return `${endPointOverride ?? defaultEndpoint}\/${method}\/${key}\/`;
|
return `${endPointOverride ?? endpoint}\/${method}\/${key}\/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -33,53 +33,23 @@ export type APIEndPoint = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ensureToday = (store: Store): Store => {
|
export const addEntry = (store: Store, entry: WithoutDate<ItemType> ): Store => {
|
||||||
const today : Date = new Date();
|
const dID = getDateID()
|
||||||
const storeEntry = store.index.find(([key, dateString]) => {
|
const storeEntry = store.entries[dID] ?? { items: [] };
|
||||||
const date = new Date(dateString);
|
storeEntry.items = [...storeEntry.items, {...entry, date: currentTimestamp() }];
|
||||||
const [y, m, d] = [today.getFullYear(), today.getMonth(), today.getDay()];
|
store.entries[dID] = storeEntry;
|
||||||
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;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeEntry = (store: Store, entry: ItemType): Store => {
|
export const removeEntry = (store: Store, entry: ItemType): Store => {
|
||||||
const [storeIndex, dateString] = getToday(ensureToday(store))!;
|
console.log("removing", entry, "from", store)
|
||||||
const today = store.entries[storeIndex];
|
Object.keys(store.entries).forEach((k) => {
|
||||||
today.items = today.items.filter((item) => JSON.stringify(item) !== JSON.stringify(entry));
|
const storeEntry = store.entries[parseInt(k)];
|
||||||
store.entries[storeIndex] = today;
|
storeEntry.items = [...storeEntry.items.filter((item) =>
|
||||||
|
!(item.date === entry.date && item.variant === entry.variant && ("item" in item && "item" in entry ? item.item === entry.item : true))
|
||||||
|
)];
|
||||||
|
store.entries[parseInt(k)] = storeEntry;
|
||||||
|
});
|
||||||
return store;
|
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;
|
|
||||||
};
|
|
||||||
|
31
src/types.ts
31
src/types.ts
@ -1,17 +1,28 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export type DateString = string;
|
export type DateString = string;
|
||||||
export type VersionedStore = {
|
export type DateID = number;
|
||||||
1: {
|
export type Timestamp = string; /* Strings formatted YYYY-MM-DD HH:mm:ss */
|
||||||
index: Array<[number, DateString]>,
|
|
||||||
entries: {[ key: number ]: StoreEntry }
|
export type Store = {
|
||||||
version: 0,
|
entries: {[ key: DateID ]: StoreEntry }
|
||||||
},
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDateID = (): DateID => {
|
||||||
|
const d = dayjs(new Date());
|
||||||
|
return d.year() * 10000 + (d.month()+1) * 100 + d.date();
|
||||||
|
}
|
||||||
|
export const dateFromDateId = (dateID: DateID): Date => {
|
||||||
|
return new Date(Math.floor(dateID / 10000), Math.floor(dateID % 10000 / 100)-1, dateID % 100);
|
||||||
|
}
|
||||||
|
export const currentTimestamp = () => {
|
||||||
|
return dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss");
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Store = VersionedStore[1];
|
export type WithoutDate<T> = T extends any ? Omit<T, "date"> : never;
|
||||||
|
|
||||||
export type ItemType = ({
|
export type ItemType = ({
|
||||||
date: DateString,
|
date: Timestamp,
|
||||||
variant: "eats" | "allergy",
|
|
||||||
} & ({
|
} & ({
|
||||||
variant: "eats",
|
variant: "eats",
|
||||||
item: string
|
item: string
|
||||||
|
Loading…
Reference in New Issue
Block a user