Add ability to edit participant name and phone number

This commit is contained in:
2026-01-06 02:59:14 +01:00
parent 427da6452a
commit 97f9b60619
3 changed files with 154 additions and 0 deletions

View File

@@ -339,6 +339,10 @@ export const participantOps = {
db.prepare('DELETE FROM participants WHERE id = ?').run(id);
},
update: (id: number, name: string, phone: string | null): void => {
db.prepare('UPDATE participants SET name = ?, phone = ? WHERE id = ?').run(name, phone, id);
},
isTokenAvailable: (token: string): boolean => {
const result = db.prepare('SELECT id FROM participants WHERE token = ?').get(token);
return !result;

View File

@@ -225,6 +225,30 @@ router.post('/questionnaires/:id/participants', (req: Request, res: Response) =>
res.json({ success: true, participant });
});
// Update participant
router.put('/participants/:id', (req: Request, res: Response) => {
const { name, phone } = req.body;
const participant = participantOps.findById(parseInt(req.params.id));
if (!participant) {
res.status(404).json({ error: 'Deelnemer niet gevonden' });
return;
}
if (!name?.trim()) {
res.status(400).json({ error: 'Naam is verplicht' });
return;
}
// Clean phone number (remove spaces, keep + and digits) or set to null if empty
const cleanPhone = phone?.trim() ? phone.trim().replace(/[^\d+]/g, '') : null;
participantOps.update(participant.id, name.trim(), cleanPhone);
const updated = participantOps.findById(participant.id);
res.json({ success: true, participant: updated });
});
// Delete participant
router.delete('/participants/:id', (req: Request, res: Response) => {
participantOps.delete(parseInt(req.params.id));

View File

@@ -48,6 +48,13 @@ export function QuestionnaireDetail() {
const [editNameInput, setEditNameInput] = useState('')
const [editDescriptionInput, setEditDescriptionInput] = useState('')
const [editLoading, setEditLoading] = useState(false)
// Edit participant modal state
const [showEditParticipantModal, setShowEditParticipantModal] = useState(false)
const [editingParticipant, setEditingParticipant] = useState<Participant | null>(null)
const [editParticipantName, setEditParticipantName] = useState('')
const [editParticipantPhone, setEditParticipantPhone] = useState('')
const [editParticipantLoading, setEditParticipantLoading] = useState(false)
useEffect(() => {
fetchData()
@@ -153,6 +160,44 @@ export function QuestionnaireDetail() {
}
}
function openEditParticipantModal(participant: Participant) {
setEditingParticipant(participant)
setEditParticipantName(participant.name)
setEditParticipantPhone(participant.phone || '')
setShowEditParticipantModal(true)
}
async function handleEditParticipant(e: FormEvent) {
e.preventDefault()
if (!editingParticipant || !editParticipantName.trim()) return
setEditParticipantLoading(true)
try {
const res = await fetch(`/api/admin/participants/${editingParticipant.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
name: editParticipantName.trim(),
phone: editParticipantPhone.trim() || null
}),
})
const data = await res.json()
if (data.success && data.participant) {
setShowEditParticipantModal(false)
setEditingParticipant(null)
// Update the participant in the list
setParticipants(participants.map(p =>
p.id === editingParticipant.id ? data.participant : p
))
}
} catch (error) {
console.error('Failed to update participant:', error)
} finally {
setEditParticipantLoading(false)
}
}
async function handleDelete() {
if (!confirm('Weet je zeker dat je deze vragenlijst wilt verwijderen? Dit verwijdert ook alle activiteiten en stemmen.')) {
return
@@ -332,6 +377,13 @@ export function QuestionnaireDetail() {
)}
<div className="text-xs font-mono text-text-faint truncate mt-1">{participantUrl}</div>
</div>
<button
onClick={() => openEditParticipantModal(participant)}
className="px-2 py-1 text-xs font-medium text-text-muted hover:text-accent hover:bg-bg-card rounded transition-colors shrink-0"
title="Bewerken"
>
</button>
<button
onClick={() => handleDeleteParticipant(participant.id)}
className="px-2 py-1 text-xs font-medium text-danger hover:bg-danger-muted rounded transition-colors shrink-0"
@@ -516,6 +568,80 @@ export function QuestionnaireDetail() {
</div>
</div>
)}
{/* Edit Participant Modal */}
{showEditParticipantModal && editingParticipant && (
<div
className="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50 p-4"
onClick={() => setShowEditParticipantModal(false)}
>
<div
className="bg-bg-card border border-border rounded-2xl w-full max-w-md"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h3 className="font-semibold text-text">Deelnemer Bewerken</h3>
<button
onClick={() => setShowEditParticipantModal(false)}
className="text-2xl text-text-muted hover:text-text leading-none"
>
×
</button>
</div>
{/* Form */}
<form onSubmit={handleEditParticipant} className="p-4 space-y-4">
<div>
<label className="block text-sm font-medium text-text mb-2">
Naam <span className="text-danger">*</span>
</label>
<input
type="text"
value={editParticipantName}
onChange={(e) => setEditParticipantName(e.target.value)}
className="w-full px-4 py-2 bg-bg-input border border-border rounded-lg text-text placeholder-text-faint focus:outline-none focus:border-accent focus:ring-2 focus:ring-accent/20 transition-colors"
required
autoFocus
/>
</div>
<div>
<label className="block text-sm font-medium text-text mb-2">
Telefoonnummer
</label>
<input
type="tel"
value={editParticipantPhone}
onChange={(e) => setEditParticipantPhone(e.target.value)}
placeholder="+31612345678 (optioneel, leeg laten om te verwijderen)"
className="w-full px-4 py-2 bg-bg-input border border-border rounded-lg text-text placeholder-text-faint focus:outline-none focus:border-accent focus:ring-2 focus:ring-accent/20 transition-colors"
/>
<p className="text-xs text-text-faint mt-1">
Laat leeg om het telefoonnummer te verwijderen
</p>
</div>
<div className="flex gap-3 justify-end pt-2">
<button
type="button"
onClick={() => setShowEditParticipantModal(false)}
className="px-4 py-2 border border-border rounded-lg text-text-muted hover:text-text hover:border-text-muted transition-colors"
>
Annuleren
</button>
<button
type="submit"
disabled={editParticipantLoading}
className="px-4 py-2 bg-accent hover:bg-accent-hover text-white font-semibold rounded-lg transition-colors disabled:opacity-50"
>
{editParticipantLoading ? 'Opslaan...' : 'Opslaan'}
</button>
</div>
</form>
</div>
</div>
)}
</div>
)
}