// SPDX-License-Identifier: AGPL-3.0-or-later import { Button, Card, ErrorNotice, LoadMore, LogList, SectionTitle, Stack, Text, __, sprintf, useAdminSettings, useGraph, useGraphMutation, useToaster, } from '@alphone/frontend-sdk' import type { AdminSettingsRead, GraphFailure } from '@alphone/frontend-sdk' import { useEffect, useId, useMemo, useRef, useState } from 'react' import { EntryCells } from './cells' import { draftCells, entriesOf, holdsInput, localDay, typedEntry } from './entries' import type { RepeaterRow } from './entries' import { entryMessage, outcomeOf, refetchAfter } from './entryOutcome' import { EntryRow } from './EntryRow' import { focusFirstControl } from './focus' import { addContactFieldEntryMutation } from './operations' import { useEntryActions } from './useEntryActions' import type { GoneHandler } from './useEntryActions' /** * Returns how many entries a repeater draws: none until the page size arrives, every one when it cannot be read. * @param sizing - The read of the admin settings. * @param pages - The pages the reader opened. * @param total - The entries the repeater holds. * @returns The count to draw. */ function entriesToDraw({ settings, failed }: AdminSettingsRead, pages: number, total: number) { if (settings === undefined) { return failed ? total : 0 } return pages * settings.listPageSize } /** * Renders one repeater of a contact as a card: its heading in the header, one add form and a page of its entries. * @param props - The contact, the repeater, its stored value and the report of a repeater gone. * @returns The repeater card. */ export function RepeaterEntries({ contactId, field, stored, onGone, }: { contactId: string field: RepeaterRow stored: unknown onGone: GoneHandler }) { const heading = useId() const entries = useMemo(() => entriesOf(stored), [stored]) const sizing = useAdminSettings() const [pages, setPages] = useState(1) const shown = entriesToDraw(sizing, pages, entries.length) const actions = useEntryActions({ contactId, field, entries, shown, onGone }) const listed = actions.listed const more = { hasNextPage: listed.length > 0 && listed.length < entries.length, isFetchingNextPage: false, fetchNextPage: async () => setPages((held) => held + 1), } return ( {field.label} {actions.failure !== '' && {actions.failure}} {entries.length === 0 && {__('No entries yet.', 'alphone-fields')}} {listed.length > 0 && ( {listed.map((entry) => ( ))} )} {__('Load more entries', 'alphone-fields')} ) } /** * Renders the form adding one entry, its dates starting on today. * @param props - The contact, the repeater, the count of focus requests, and what runs on an add and a repeater gone. * @returns The add form. */ function AddEntryForm({ contactId, field, focusCount, onAdded, onGone, }: { contactId: string field: RepeaterRow focusCount: number onAdded: (from: Element | null) => void onGone: GoneHandler }) { const [picked, setPicked] = useState>(() => new Map()) const [add, runAdd] = useGraphMutation(addContactFieldEntryMutation) const graph = useGraph() const toaster = useToaster() const formRef = useRef(null) const shown = draftCells(field.subFields, picked, localDay(new Date())) const label = sprintf(__('Add an entry to %(label)s', 'alphone-fields'), { label: field.label }) const fallback = __('The entry could not be added.', 'alphone-fields') useEffect(() => { if (focusCount > 0) { focusFirstControl(formRef.current as HTMLFormElement) } }, [focusCount]) const submit = async () => { const from = document.activeElement const result = await runAdd({ contactId, field: field.name, entry: typedEntry(field.subFields, shown) }) refetchAfter(graph, result.error) const outcome = outcomeOf(result.error) if (outcome === 'done') { setPicked(new Map()) toaster.show(__('Entry added.', 'alphone-fields')) onAdded(from) } else if (outcome === 'field-gone') { onGone(entryMessage(result.error as GraphFailure, fallback), from) } } return (
{ event.preventDefault() void submit() }} > setPicked((held) => new Map(held).set(name, text))} /> {add.error && outcomeOf(add.error) !== 'field-gone' ? ( {entryMessage(add.error, fallback)} ) : null} ) }