Files
rosterchirp/frontend/src/components/AddChildAliasModal.jsx

196 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { useToast } from '../contexts/ToastContext.jsx';
import { api } from '../utils/api.js';
export default function AddChildAliasModal({ onClose }) {
const toast = useToast();
const [aliases, setAliases] = useState([]);
const [editingAlias, setEditingAlias] = useState(null); // null = new entry
const [form, setForm] = useState({ firstName: '', lastName: '', dob: '', phone: '', email: '' });
const [avatarFile, setAvatarFile] = useState(null);
const [saving, setSaving] = useState(false);
useEffect(() => {
api.getAliases().then(({ aliases }) => setAliases(aliases || [])).catch(() => {});
}, []);
const set = k => e => setForm(p => ({ ...p, [k]: e.target.value }));
const resetForm = () => {
setEditingAlias(null);
setForm({ firstName: '', lastName: '', dob: '', phone: '', email: '' });
setAvatarFile(null);
};
const handleSelectAlias = (a) => {
if (editingAlias?.id === a.id) { resetForm(); return; }
setEditingAlias(a);
setForm({
firstName: a.first_name || '',
lastName: a.last_name || '',
dob: a.date_of_birth ? a.date_of_birth.slice(0, 10) : '',
phone: a.phone || '',
email: a.email || '',
});
setAvatarFile(null);
};
const handleSave = async () => {
if (!form.firstName.trim() || !form.lastName.trim())
return toast('First and last name required', 'error');
setSaving(true);
try {
if (editingAlias) {
await api.updateAlias(editingAlias.id, {
firstName: form.firstName.trim(),
lastName: form.lastName.trim(),
dateOfBirth: form.dob || null,
phone: form.phone || null,
email: form.email || null,
});
if (avatarFile) await api.uploadAliasAvatar(editingAlias.id, avatarFile);
toast('Child alias updated', 'success');
} else {
const { alias } = await api.createAlias({
firstName: form.firstName.trim(),
lastName: form.lastName.trim(),
dateOfBirth: form.dob || null,
phone: form.phone || null,
email: form.email || null,
});
if (avatarFile) await api.uploadAliasAvatar(alias.id, avatarFile);
toast('Child alias added', 'success');
}
const { aliases: fresh } = await api.getAliases();
setAliases(fresh || []);
resetForm();
} catch (e) {
toast(e.message, 'error');
} finally {
setSaving(false);
}
};
const handleDelete = async (e, aliasId) => {
e.stopPropagation();
try {
await api.deleteAlias(aliasId);
setAliases(prev => prev.filter(a => a.id !== aliasId));
if (editingAlias?.id === aliasId) resetForm();
toast('Child alias removed', 'success');
} catch (err) { toast(err.message, 'error'); }
};
const lbl = (text, required) => (
<label className="text-sm" style={{ color: 'var(--text-secondary)', display: 'block', marginBottom: 4 }}>
{text}{required && <span style={{ color: 'var(--error)', marginLeft: 2 }}>*</span>}
</label>
);
return (
<div className="modal-overlay" onClick={e => e.target === e.currentTarget && onClose()}>
<div className="modal">
{/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
<h2 className="modal-title" style={{ margin: 0 }}>Add Child Alias</h2>
<button className="btn-icon" onClick={onClose} aria-label="Close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
{/* Existing aliases list */}
{aliases.length > 0 && (
<div style={{ marginBottom: 16 }}>
<div className="text-sm font-medium" style={{ color: 'var(--text-secondary)', marginBottom: 8 }}>
Your Children click to edit
</div>
<div style={{ border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' }}>
{aliases.map((a, i) => (
<div
key={a.id}
onClick={() => handleSelectAlias(a)}
style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '9px 12px', cursor: 'pointer',
borderBottom: i < aliases.length - 1 ? '1px solid var(--border)' : 'none',
background: editingAlias?.id === a.id ? 'var(--primary-light)' : 'transparent',
}}
>
<span style={{ flex: 1, fontSize: 14, fontWeight: editingAlias?.id === a.id ? 600 : 400 }}>
{a.first_name} {a.last_name}
</span>
{a.date_of_birth && (
<span style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>
{a.date_of_birth.slice(0, 10)}
</span>
)}
<button
onClick={e => handleDelete(e, a.id)}
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--error)', fontSize: 18, lineHeight: 1, padding: '0 2px' }}
aria-label="Remove"
>×</button>
</div>
))}
</div>
</div>
)}
{/* Form section label */}
<div className="text-sm font-medium" style={{ color: 'var(--text-secondary)', marginBottom: 10 }}>
{editingAlias
? `Editing: ${editingAlias.first_name} ${editingAlias.last_name}`
: 'New Child Alias'}
</div>
{/* Form */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
<div>
{lbl('First Name', true)}
<input className="input" value={form.firstName} onChange={set('firstName')}
autoComplete="off" autoCapitalize="words" />
</div>
<div>
{lbl('Last Name', true)}
<input className="input" value={form.lastName} onChange={set('lastName')}
autoComplete="off" autoCapitalize="words" />
</div>
<div>
{lbl('Date of Birth')}
<input className="input" placeholder="YYYY-MM-DD" value={form.dob} onChange={set('dob')}
autoComplete="off" />
</div>
<div>
{lbl('Phone')}
<input className="input" type="tel" value={form.phone} onChange={set('phone')}
autoComplete="off" />
</div>
</div>
<div>
{lbl('Email (optional)')}
<input className="input" type="email" value={form.email} onChange={set('email')}
autoComplete="off" />
</div>
<div>
{lbl('Avatar (optional)')}
<input type="file" accept="image/*"
onChange={e => setAvatarFile(e.target.files?.[0] || null)} />
</div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}>
{editingAlias && (
<button className="btn btn-secondary" onClick={resetForm}>Cancel Edit</button>
)}
<button className="btn btn-primary" onClick={handleSave} disabled={saving}>
{saving ? 'Saving…' : editingAlias ? 'Update Alias' : 'Add Alias'}
</button>
</div>
</div>
</div>
</div>
);
}