Kansho/frontend/src/components/DayScratch.jsx
2026-08-25 13:57:23 +02:00

116 lines
3.4 KiB
JavaScript

import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { api } from '../api.js'
function newItem() {
const id = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `s-${Date.now()}-${Math.random()}`
return { id, text: '', done: false }
}
function rowsFrom(items) {
if (items?.length) {
return items.map((item) => ({
id: item.id || newItem().id,
text: item.text || '',
done: !!item.done
}))
}
return [newItem()]
}
export default function DayScratch({ dayId, token, items, onError }) {
const [local, setLocal] = useState(() => rowsFrom(items))
const timer = useRef(null)
const seq = useRef(0)
const pendingFocus = useRef(null)
const inputRefs = useRef({})
useEffect(() => {
window.clearTimeout(timer.current)
seq.current += 1
setLocal(rowsFrom(items))
}, [dayId])
useLayoutEffect(() => {
const id = pendingFocus.current
if (!id) return
const el = inputRefs.current[id]
if (!el) return
el.focus()
pendingFocus.current = null
}, [local])
const persist = (next, focusId) => {
if (focusId) pendingFocus.current = focusId
const mine = ++seq.current
setLocal(next)
window.clearTimeout(timer.current)
timer.current = window.setTimeout(async () => {
try {
await api(`/api/journal/days/${dayId}/scratch`, {
token,
method: 'PATCH',
body: { items: next.filter((item) => item.text.trim() || item.done) }
})
} catch (err) {
if (mine === seq.current) onError?.(err.message)
}
}, 400)
}
useEffect(() => () => window.clearTimeout(timer.current), [])
const patch = (id, fields) => persist(local.map((row) => (row.id === id ? { ...row, ...fields } : row)))
return (
<aside className="day-scratch">
<h2>Stichpunkte</h2>
<p className="muted">
Nur für dich, parallel zum Gespräch. Kanshō sieht diese Liste nicht noch unklar, ob das später sinnvoll wäre.
</p>
<ul className="day-scratch-list">
{local.map((item, index) => (
<li key={item.id}>
<label className="check">
<input
type="checkbox"
checked={!!item.done}
onChange={(e) => patch(item.id, { done: e.target.checked })}
/>
<input
ref={(el) => {
if (el) inputRefs.current[item.id] = el
else delete inputRefs.current[item.id]
}}
type="text"
autoComplete="off"
value={item.text}
placeholder="Nicht vergessen …"
onChange={(e) => patch(item.id, { text: e.target.value })}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
const created = newItem()
const next = [...local]
next.splice(index + 1, 0, created)
persist(next, created.id)
}
}}
/>
</label>
</li>
))}
</ul>
<button
type="button"
className="ghost"
onClick={() => {
const created = newItem()
persist([...local, created], created.id)
}}
>
Punkt hinzufügen
</button>
</aside>
)
}