Compare commits

..

No commits in common. "1c0b107188602835cf33c06957df59083d09f00e" and "998d9a2590c60edffff0104f54a1197e4c707d26" have entirely different histories.

8 changed files with 73 additions and 85 deletions

View File

@ -1 +0,0 @@
REACT_APP_API_URL=https://shulneffapi.leafbla.de

View File

@ -1,26 +0,0 @@
{
"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"
}
]
}
}
}

View File

@ -24,8 +24,7 @@
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"server": "node server/server.js", "eject": "react-scripts eject"
"frontend": "react-scripts start"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

@ -33,9 +33,6 @@ 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;
} }

View File

@ -1,11 +1,12 @@
import React, { JSX, useEffect, useState } from 'react'; import React, { JSX, useEffect, useState } from 'react';
import './App.css'; import './App.css';
import { addEntry, APIEndPoint, getAPI, removeEntry } from './api'; import { addEntry, APIEndPoint, ensureToday, getAPI, getToday, removeEntry } from './api';
import { ItemType, Store, WithoutDate } from './types'; import { ItemType, Store, StoreEntry } from './types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const defaultStore: Store = { const defaultStore: Store = {
version: 1, index: [],
version: 0,
entries: {}, entries: {},
}; };
@ -17,7 +18,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(st); setStore(ensureToday(st));
}; };
f(); f();
}, [API]); }, [API]);
@ -26,9 +27,8 @@ export const App : React.FC = () => {
const formSubmit = async () => { const formSubmit = async () => {
const entry : WithoutDate<ItemType> = {variant: "eats", item: input}; const newst = addEntry(store, {variant: "eats", date: new Date().toString(), item: input} );
const newStore = addEntry(store, entry); console.log("newstore is", newst);
console.log("newstore is", newStore);
API.write(JSON.stringify(store)).then(async () => { API.write(JSON.stringify(store)).then(async () => {
setTimeout(async () => { setTimeout(async () => {
const fromserver = await API.read(); const fromserver = await API.read();
@ -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"} ); const newst = addEntry(store, {variant: "allergy", date: new Date().toString()} );
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 = "MMM DD, HH:mm"; const DATEFORMAT = "YYYY MM 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}) => {

View File

@ -1,7 +1,7 @@
import { currentTimestamp, getDateID, ItemType, Store, StoreEntry, WithoutDate } from "./types" import { ItemType, Store, StoreEntry } from "./types"
const endpoint = process.env.REACT_APP_API_URL ?? "API endpoint missing!"; const defaultEndpoint = "https://shulneffapi.leafbla.de"
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 ?? endpoint}\/${method}\/${key}\/`; return `${endPointOverride ?? defaultEndpoint}\/${method}\/${key}\/`;
} }
@ -33,23 +33,53 @@ export type APIEndPoint = {
} }
}); });
export const addEntry = (store: Store, entry: WithoutDate<ItemType> ): Store => { export const ensureToday = (store: Store): Store => {
const dID = getDateID() const today : Date = new Date();
const storeEntry = store.entries[dID] ?? { items: [] }; const storeEntry = store.index.find(([key, dateString]) => {
storeEntry.items = [...storeEntry.items, {...entry, date: currentTimestamp() }]; const date = new Date(dateString);
store.entries[dID] = storeEntry; 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; return store;
} }
export const removeEntry = (store: Store, entry: ItemType): Store => { export const removeEntry = (store: Store, entry: ItemType): Store => {
console.log("removing", entry, "from", store) const [storeIndex, dateString] = getToday(ensureToday(store))!;
Object.keys(store.entries).forEach((k) => { const today = store.entries[storeIndex];
const storeEntry = store.entries[parseInt(k)]; today.items = today.items.filter((item) => JSON.stringify(item) !== JSON.stringify(entry));
storeEntry.items = [...storeEntry.items.filter((item) => store.entries[storeIndex] = today;
!(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;
};

View File

@ -1,28 +1,17 @@
import dayjs from "dayjs";
export type DateString = string; export type DateString = string;
export type DateID = number; export type VersionedStore = {
export type Timestamp = string; /* Strings formatted YYYY-MM-DD HH:mm:ss */ 1: {
index: Array<[number, DateString]>,
export type Store = { entries: {[ key: number ]: StoreEntry }
entries: {[ key: DateID ]: StoreEntry } version: 0,
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 WithoutDate<T> = T extends any ? Omit<T, "date"> : never; export type Store = VersionedStore[1];
export type ItemType = ({ export type ItemType = ({
date: Timestamp, date: DateString,
variant: "eats" | "allergy",
} & ({ } & ({
variant: "eats", variant: "eats",
item: string item: string