MyKindlebox/mykindlebox/client/src/components/AddToEventModal.jsx

171 lines
6.9 KiB
JavaScript

import { useState } from 'react'
import { Link } from 'react-router-dom'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { getShowEvents, createShowEvent, addShowToEvent } from '../api'
import Button from './Button'
// Modal launched from a row's "Add to Event" button in the Shows browser.
// Lets the user drop the ARBA show into an existing event or spin up a new one
// inline (design doc §6 "Adding Shows to an Event Flow").
function AddToEventModal({ show, onClose }) {
const queryClient = useQueryClient()
const { data: events, isLoading } = useQuery({ queryKey: ['show-events'], queryFn: getShowEvents })
const [mode, setMode] = useState('existing') // 'existing' | 'new'
const [selectedId, setSelectedId] = useState('')
const [newName, setNewName] = useState('')
const [newNotes, setNewNotes] = useState('')
const [result, setResult] = useState(null) // { id, name } once added
// No events yet → force the create path.
const hasEvents = (events?.length ?? 0) > 0
const effectiveMode = hasEvents ? mode : 'new'
const mutation = useMutation({
mutationFn: async () => {
let eventId
let eventName
if (effectiveMode === 'new') {
const created = await createShowEvent({ name: newName.trim(), notes: newNotes.trim() || null })
eventId = created.id
eventName = created.name
} else {
eventId = Number(selectedId)
eventName = events.find((e) => e.id === eventId)?.name ?? 'event'
}
await addShowToEvent(eventId, show)
return { id: eventId, name: eventName }
},
onSuccess: (added) => {
setResult(added)
queryClient.invalidateQueries({ queryKey: ['show-events'] })
queryClient.invalidateQueries({ queryKey: ['show-event', added.id] })
},
})
const canSubmit =
effectiveMode === 'new' ? newName.trim().length > 0 : selectedId !== ''
return (
<div
className="fixed inset-0 z-30 flex items-center justify-center bg-black/40 p-4"
onMouseDown={(e) => {
if (e.target === e.currentTarget) onClose()
}}
>
<div className="w-full max-w-md rounded-lg border border-border bg-surface-raised p-6 shadow-xl">
{result ? (
<div className="flex flex-col gap-4">
<h2 className="font-display text-lg text-text-primary">Added to event</h2>
<p className="font-body text-sm text-text-secondary">
<span className="font-medium text-text-primary">{show.club_name}</span> was added to{' '}
<span className="font-medium text-text-primary">{result.name}</span>.
</p>
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={onClose}>
Close
</Button>
<Link to={`/shows/events/${result.id}`}>
<Button variant="primary">View Event</Button>
</Link>
</div>
</div>
) : (
<div className="flex flex-col gap-4">
<div>
<h2 className="font-display text-lg text-text-primary">Add to Event</h2>
<p className="mt-1 font-body text-sm text-text-secondary">
{show.club_name} — {show.raw_date || show.date}
</p>
</div>
{hasEvents && (
<div className="flex gap-1 rounded-md border border-border bg-surface p-1">
<button
type="button"
onClick={() => setMode('existing')}
className={`flex-1 rounded px-3 py-1.5 font-body text-sm ${
effectiveMode === 'existing' ? 'bg-accent text-surface' : 'text-text-primary hover:bg-surface-sunken'
}`}
>
Existing event
</button>
<button
type="button"
onClick={() => setMode('new')}
className={`flex-1 rounded px-3 py-1.5 font-body text-sm ${
effectiveMode === 'new' ? 'bg-accent text-surface' : 'text-text-primary hover:bg-surface-sunken'
}`}
>
New event
</button>
</div>
)}
{effectiveMode === 'existing' ? (
<div>
<label className="block font-body text-sm font-medium text-text-primary">Event</label>
<select
value={selectedId}
onChange={(e) => setSelectedId(e.target.value)}
className="mt-1 w-full rounded-md border border-border bg-surface-raised px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
>
<option value="">{isLoading ? 'Loading…' : 'Select an event'}</option>
{events?.map((event) => (
<option key={event.id} value={event.id}>
{event.name} ({event.show_count} show{event.show_count === 1 ? '' : 's'})
</option>
))}
</select>
</div>
) : (
<div className="flex flex-col gap-3">
<div>
<label className="block font-body text-sm font-medium text-text-primary">
Event name <span className="text-danger">*</span>
</label>
<input
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder="e.g. Spring Circuit Weekend"
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
/>
</div>
<div>
<label className="block font-body text-sm font-medium text-text-primary">Notes</label>
<textarea
value={newNotes}
onChange={(e) => setNewNotes(e.target.value)}
rows={2}
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
/>
</div>
</div>
)}
{mutation.isError && (
<p className="font-body text-sm text-danger">{mutation.error.message}</p>
)}
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={onClose} disabled={mutation.isPending}>
Cancel
</Button>
<Button
variant="primary"
onClick={() => mutation.mutate()}
disabled={!canSubmit || mutation.isPending}
>
{mutation.isPending ? 'Adding…' : 'Add'}
</Button>
</div>
</div>
)}
</div>
</div>
)
}
export default AddToEventModal