V 2.0.0 ferige Version
This commit is contained in:
235
app/components/SpritzClient.tsx
Normal file
235
app/components/SpritzClient.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
interface DataItem {
|
||||
day: string;
|
||||
status: boolean;
|
||||
einheit: number;
|
||||
}
|
||||
|
||||
interface Schema {
|
||||
curdate: string;
|
||||
months: string[];
|
||||
years: string[];
|
||||
data: DataItem[];
|
||||
einheit: number;
|
||||
}
|
||||
|
||||
export interface SysParams {
|
||||
testing: boolean;
|
||||
doinit: boolean;
|
||||
einheit: number;
|
||||
version: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
sysParams: SysParams;
|
||||
}
|
||||
|
||||
function CellContent({ day, einheit }: { day: DateTime; einheit: number }) {
|
||||
return (
|
||||
<div className="inner">
|
||||
{day.toFormat('d')}
|
||||
<div className="lowline small">
|
||||
<div className="wtg">{day.setLocale('de').toFormat('ccc')}</div>
|
||||
<div className="eh">{einheit !== 0 ? einheit : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function buildMonthsLabel(s: Schema): string {
|
||||
let months = s.months.join(' - ');
|
||||
months += ' ';
|
||||
months += s.years.join('/');
|
||||
return months;
|
||||
}
|
||||
|
||||
export default function SpritzClient({ sysParams }: Props) {
|
||||
const [schema, setSchema] = useState<Schema | null>(null);
|
||||
const [curEinheit, setCurEinheit] = useState<number>(sysParams.einheit);
|
||||
const [monthsLabel, setMonthsLabel] = useState<string>('');
|
||||
const [einheitInput, setEinheitInput] = useState<number>(sysParams.einheit);
|
||||
|
||||
const schemaRef = useRef<Schema | null>(null);
|
||||
const curEinheitRef = useRef<number>(sysParams.einheit);
|
||||
|
||||
schemaRef.current = schema;
|
||||
curEinheitRef.current = curEinheit;
|
||||
|
||||
const apiUrl = sysParams.testing ? '/api/data?test=true' : '/api/data';
|
||||
|
||||
async function fetchData(): Promise<{ data: Schema | null; err: string | null }> {
|
||||
const res = await fetch(apiUrl);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function storeData(data: Schema): Promise<unknown> {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function initSchema(startdate: string): Promise<Schema> {
|
||||
const setArray: DataItem[] = [];
|
||||
const monthArray: string[] = [];
|
||||
const yearsArray: string[] = [];
|
||||
const ld0 = DateTime.fromISO(startdate);
|
||||
let k = 0;
|
||||
for (let i = 0; i < 35; i++) {
|
||||
const elem: DataItem = { status: false, einheit: 0, day: '' };
|
||||
if (i === 17) {
|
||||
elem.day = '';
|
||||
} else {
|
||||
const ld = ld0.plus({ day: k });
|
||||
elem.day = ld.toFormat('y-LL-dd');
|
||||
const month = ld.setLocale('de').toFormat('LLLL');
|
||||
const year = ld.toFormat('y');
|
||||
if (!monthArray.includes(month)) monthArray.push(month);
|
||||
if (!yearsArray.includes(year)) yearsArray.push(year);
|
||||
k++;
|
||||
}
|
||||
setArray.push(elem);
|
||||
}
|
||||
const newSchema: Schema = {
|
||||
curdate: startdate,
|
||||
months: monthArray,
|
||||
years: yearsArray,
|
||||
data: setArray,
|
||||
einheit: curEinheitRef.current,
|
||||
};
|
||||
await storeData(newSchema);
|
||||
return newSchema;
|
||||
}
|
||||
|
||||
function applySchema(s: Schema) {
|
||||
// Update einheit input to last non-zero einheit found in data
|
||||
let lastEinheit = s.einheit;
|
||||
for (const item of s.data) {
|
||||
if (item.einheit !== 0) lastEinheit = item.einheit;
|
||||
}
|
||||
if (lastEinheit !== 0 && curEinheitRef.current === 0) {
|
||||
setCurEinheit(lastEinheit);
|
||||
curEinheitRef.current = lastEinheit;
|
||||
setEinheitInput(lastEinheit);
|
||||
}
|
||||
setMonthsLabel(buildMonthsLabel(s));
|
||||
setSchema(s);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
let s: Schema | null = null;
|
||||
if (sysParams.doinit) {
|
||||
s = await initSchema('2023-05-01');
|
||||
}
|
||||
if (!s) {
|
||||
const ret = await fetchData();
|
||||
s = ret.data;
|
||||
}
|
||||
if (!s) {
|
||||
// DB leer → neues Schema mit aktuellem Datum anlegen
|
||||
const today = DateTime.now().toFormat('y-LL-dd');
|
||||
s = await initSchema(today);
|
||||
}
|
||||
if (s) {
|
||||
applySchema(s);
|
||||
}
|
||||
}
|
||||
init();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleClick = useCallback(async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const button = (e.target as HTMLElement).closest('button') as HTMLButtonElement | null;
|
||||
if (!button || button.disabled) return;
|
||||
const currentSchema = schemaRef.current;
|
||||
if (!currentSchema) return;
|
||||
|
||||
const idNum = parseInt(button.id.slice(2)) - 1; // 0-basierter Index
|
||||
const lastDay = currentSchema.data[34].day;
|
||||
|
||||
const newSchema: Schema = {
|
||||
...currentSchema,
|
||||
data: currentSchema.data.map((item, idx) => {
|
||||
if (idx === idNum) {
|
||||
return { ...item, status: !item.status, einheit: curEinheitRef.current };
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
};
|
||||
|
||||
await storeData(newSchema);
|
||||
applySchema(newSchema);
|
||||
|
||||
// Letzten Button (bt35) geklickt → nächste Periode anlegen
|
||||
if (button.id === 'bt35') {
|
||||
const nextDate = DateTime.fromISO(lastDay).plus({ day: 1 }).toFormat('y-LL-dd');
|
||||
const nextSchema = await initSchema(nextDate);
|
||||
applySchema(nextSchema);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleEinheitChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const val = parseInt(e.target.value) || 0;
|
||||
setCurEinheit(val);
|
||||
curEinheitRef.current = val;
|
||||
setEinheitInput(val);
|
||||
if (schemaRef.current) {
|
||||
schemaRef.current = { ...schemaRef.current, einheit: val };
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!schema) {
|
||||
return <div style={{ padding: '30px' }}>Lade…</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="spritztab">
|
||||
<h1>Spritz-Tabelle</h1>
|
||||
<h2 id="curmon">{monthsLabel}</h2>
|
||||
<div id="sptab" onClick={handleClick}>
|
||||
{schema.data.map((item, idx) => {
|
||||
const btId = `bt${idx + 1}`;
|
||||
const day = item.day ? DateTime.fromISO(item.day) : null;
|
||||
const isDisabled = item.day === '';
|
||||
const ariaLabel = isDisabled ? 'o' : item.status ? 'x' : '';
|
||||
const displayEinheit = item.einheit !== 0 ? item.einheit : (item.status ? schema.einheit : 0);
|
||||
return (
|
||||
<button
|
||||
key={btId}
|
||||
id={btId}
|
||||
disabled={isDisabled}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{day && <CellContent day={day} einheit={displayEinheit} />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div id="infeld">
|
||||
<label htmlFor="einheiten">Einheiten:</label>
|
||||
<input
|
||||
id="einheiten"
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
value={einheitInput}
|
||||
onChange={handleEinheitChange}
|
||||
/>
|
||||
</div>
|
||||
<footer>
|
||||
<div id="v">
|
||||
Version {sysParams.version} vom {sysParams.date}
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user