Compare commits

...

2 Commits

Author SHA1 Message Date
Dominic Zimmer
1c0b107188 Migrate to new date format 2025-05-12 00:02:02 +02:00
Dominic Zimmer
1293f058b3 Add scripts 2025-05-12 00:01:52 +02:00
8 changed files with 85 additions and 73 deletions

1
.env.production Normal file
View File

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

26
data/file.json Normal file
View 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"
}
]
}
}
}

View File

@ -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": [

View File

@ -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;
} }

View File

@ -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}) => {

View File

@ -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;
};

View File

@ -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