import { useEffect, useState } from 'react' import { api } from '../api' import type { CospendProject, Group, Item, Member } from '../types' interface Props { receiptId: string items: Item[] onAllDone: (groups: Group[]) => void } export function GroupStep({ receiptId, items, onAllDone }: Props) { const [projects, setProjects] = useState([]) const [projectId, setProjectId] = useState(null) const [members, setMembers] = useState([]) const [loadError, setLoadError] = useState(null) const [remainingIds, setRemainingIds] = useState>(new Set(items.map((i) => i.id))) const [groups, setGroups] = useState([]) // current draft const [name, setName] = useState('WG') const [payerId, setPayerId] = useState(null) const [selectedMemberIds, setSelectedMemberIds] = useState>(new Set()) const [selectedItemIds, setSelectedItemIds] = useState>(new Set()) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) // load Cospend projects once, pick the configured default (or the only one) useEffect(() => { api .getProjects() .then(({ projects: p, default_project_id }) => { setProjects(p) const initial = default_project_id ?? (p.length === 1 ? p[0].id : null) if (initial) setProjectId(initial) }) .catch((e) => setLoadError(e instanceof Error ? e.message : String(e))) }, []) // load members whenever the selected project changes useEffect(() => { if (!projectId) return api .getMembers(projectId) .then((m) => { setMembers(m) if (m.length > 0) { setPayerId(m[0].id) setSelectedMemberIds(new Set(m.map((mm) => mm.id))) } }) .catch((e) => setLoadError(e instanceof Error ? e.message : String(e))) }, [projectId]) function toggleItem(id: string) { setSelectedItemIds((prev) => { const next = new Set(prev) next.has(id) ? next.delete(id) : next.add(id) return next }) } function toggleMember(id: string) { setSelectedMemberIds((prev) => { const next = new Set(prev) next.has(id) ? next.delete(id) : next.add(id) return next }) } const remainingItems = items.filter((i) => remainingIds.has(i.id)) const groupTotal = items .filter((i) => selectedItemIds.has(i.id)) .reduce((sum, i) => sum + i.price, 0) async function submitCurrentGroup() { if (!projectId) { setError('pick a Cospend project') return } if (!payerId || selectedMemberIds.size === 0 || selectedItemIds.size === 0) { setError('pick a payer, at least one member, and at least one item') return } setSubmitting(true) setError(null) try { const { id: groupId } = await api.createGroup(receiptId, { name, cospend_project_id: projectId, payer_member_id: payerId, member_ids: [...selectedMemberIds], item_ids: [...selectedItemIds], }) const result = await api.submitGroup(receiptId, groupId) const allGroups = await api.listGroups(receiptId) setGroups(allGroups) setRemainingIds((prev) => { const next = new Set(prev) selectedItemIds.forEach((id) => next.delete(id)) return next }) const wasLastGroup = remainingItems.length === selectedItemIds.size setSelectedItemIds(new Set()) setName('WG') if (wasLastGroup) { onAllDone(allGroups) } void result } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setSubmitting(false) } } if (loadError) { return (

Couldn't load Cospend data: {loadError}

) } return (

Who's splitting this?

Select the items for one splitting arrangement, pick who's in on it, submit. Repeat for any items that need a different group (e.g. only your snack).

{groups.length > 0 && (
{groups.map((g) => (

✓ {g.name} bill created —{' '} receipt

))}
)} setName(e.target.value)} placeholder="group name (e.g. WG, just me + Alex)" />

Items ({remainingItems.length} left)

    {remainingItems.map((item) => (
  • toggleItem(item.id)} > {item.label || '(unnamed)'} {item.price.toFixed(2)}
  • ))}

Split with

    {members.map((m) => (
  • ))}

Group total: {groupTotal.toFixed(2)}

{error &&

{error}

} {remainingItems.length === 0 && groups.length > 0 && ( )}
) }