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",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"server": "node server/server.js",
"frontend": "react-scripts start"
},
"eslintConfig": {
"extends": [

View File

@ -33,6 +33,9 @@ h1,h2,h3 {
flex-direction: column;
gap: 10px;
}
.entry-table td {
min-width: 40px;
}
.entry-table .reacts {
background-color: #DDEB9D;
}

View File

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

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 = {
write: (json: string) => Promise<void>,
@ -9,7 +9,7 @@ export type APIEndPoint = {
};
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 => {
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);
});
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;
export const addEntry = (store: Store, entry: WithoutDate<ItemType> ): Store => {
const dID = getDateID()
const storeEntry = store.entries[dID] ?? { items: [] };
storeEntry.items = [...storeEntry.items, {...entry, date: currentTimestamp() }];
store.entries[dID] = storeEntry;
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;
console.log("removing", entry, "from", store)
Object.keys(store.entries).forEach((k) => {
const storeEntry = store.entries[parseInt(k)];
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;
}
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 VersionedStore = {
1: {
index: Array<[number, DateString]>,
entries: {[ key: number ]: StoreEntry }
version: 0,
},
export type DateID = number;
export type Timestamp = string; /* Strings formatted YYYY-MM-DD HH:mm:ss */
export type Store = {
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 = ({
date: DateString,
variant: "eats" | "allergy",
date: Timestamp,
} & ({
variant: "eats",
item: string