function ListEditPage({ listId }) { const { AppBootstrap } = window.__CRM__; const { Container, Stack, Typography, TextField, Button, Breadcrumbs, Link, Paper, useMediaQuery, Table, TableHead, TableRow, TableCell, TableBody, TableContainer, Pagination, IconButton, Chip, Checkbox, Dialog, DialogTitle, DialogContent, DialogActions, Grid, MenuItem, Card, CardContent, Divider } = MaterialUI; const isXs = useMediaQuery('(max-width:600px)'); const isSm = useMediaQuery('(max-width:900px)'); const [loading, setLoading] = React.useState(true); const [err, setErr] = React.useState(''); const [list, setList] = React.useState(null); // table state const [q, setQ] = React.useState(''); const [rows, setRows] = React.useState([]); const [total, setTotal] = React.useState(0); const [page, setPage] = React.useState(1); const limit = 25; const [selected, setSelected] = React.useState(new Set()); // Add/Edit modal const emptyForm = { id:0, name:'', phone:'', email:'', status:'Active', address_line:'', apt_suite:'', city:'', state:'', zip_code:'', country:'', notes:'' }; const [openEdit, setOpenEdit] = React.useState(false); const [form, setForm] = React.useState(emptyForm); // Add from Contacts modal const [openFrom, setOpenFrom] = React.useState(false); const [searchC, setSearchC] = React.useState(''); const [candidates, setCandidates] = React.useState([]); const [pick, setPick] = React.useState(new Set()); React.useEffect(() => { let alive = true; (async () => { try { const r = await fetch(`/api/lists_get.php?id=${encodeURIComponent(listId)}`, { credentials:'include' }); if (!alive) return; if (!r.ok) { setErr('List not found'); setLoading(false); return; } const j = await r.json(); setList(j.list); setLoading(false); } catch { if (!alive) return; setErr('Failed to load list'); setLoading(false); } })(); return () => { alive = false; }; }, [listId]); async function load(p=1){ const offset = (p-1)*limit; const res = await fetch(`/api/list_contacts_list.php?list_id=${listId}&q=${encodeURIComponent(q)}&limit=${limit}&offset=${offset}`, { credentials:'include' }); const j = await res.json(); setRows(j.items||[]); setTotal(j.total||0); setPage(p); setSelected(new Set()); } React.useEffect(()=>{ if(list) load(1); }, [list]); // CRUD async function upsert(payload){ await fetch('/api/list_contacts_upsert.php', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ ...payload, list_id: Number(listId) }), credentials:'include' }); } async function delOne(id){ await fetch('/api/list_contacts_delete.php', { method:'POST', body:new URLSearchParams({ list_id:String(listId), id:String(id) }), credentials:'include' }); } async function delBulk(){ if(!selected.size) return; if(!confirm(`Delete ${selected.size} selected contact(s) from this list?`)) return; await fetch('/api/list_contacts_delete_bulk.php', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ list_id:Number(listId), ids:Array.from(selected) }), credentials:'include' }); load(page); } function exportAll(){ location.href = `/api/lists_export.php?id=${encodeURIComponent(listId)}`; } function exportSelected(){ if(!selected.size) return; location.href = `/api/lists_export.php?id=${encodeURIComponent(listId)}&ids=${encodeURIComponent(Array.from(selected).join(','))}`; } // Edit modal handlers function openAdd(){ setForm(emptyForm); setOpenEdit(true); } function openEditRow(r){ setForm({ ...r }); setOpenEdit(true); } function closeEdit(){ setOpenEdit(false); } async function saveEdit(){ if(!form.name.trim()) { alert('Name is required'); return; } await upsert(form); setOpenEdit(false); load(form.id? page : 1); } // From Contacts modal async function searchContacts(){ const r = await fetch(`/api/contacts_search.php?q=${encodeURIComponent(searchC)}&limit=50`, { credentials:'include' }); const j = await r.json(); setCandidates(j.items||[]); } function togglePick(id){ setPick(prev=>{ const n=new Set(prev); if(n.has(id)) n.delete(id); else n.add(id); return n; }); } async function addFromContacts(){ if(!pick.size) { setOpenFrom(false); return; } await fetch('/api/list_contacts_add_from_contacts.php', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ list_id:Number(listId), contact_ids:Array.from(pick) }), credentials:'include' }); setOpenFrom(false); setPick(new Set()); load(1); } // selection helpers const allIdsOnPage = React.useMemo(()=>rows.map(r=>r.id),[rows]); const allSelected = allIdsOnPage.length>0 && allIdsOnPage.every(id=>selected.has(id)); const someSelected = allIdsOnPage.some(id=>selected.has(id)); function toggleOne(id){ setSelected(prev=>{ const n=new Set(prev); if(n.has(id)) n.delete(id); else n.add(id); return n; }); } function toggleAllPage(){ setSelected(prev=>{ const n=new Set(prev); if(allSelected){ allIdsOnPage.forEach(id=>n.delete(id)); } else { allIdsOnPage.forEach(id=>n.add(id)); } return n; }); } const pages = Math.max(1, Math.ceil(total/limit)); const CardRow = ({ r }) => ( toggleOne(r.id)} /> {r.name} {r.phone && Phone: {r.phone}} {r.email && Email: {r.email}} {(r.city||r.state) && Location: {[r.city,r.state].filter(Boolean).join(' / ')}} {r.address_line && Address: {r.address_line}} {r.notes && Notes: {r.notes}} openEditRow(r)} title="Edit">edit { if(confirm('Delete this contact from list?')){ delOne(r.id).then(()=>load(page)); }}} title="Delete">delete ); return ( Lists Edit List {loading ? 'Loading…' : (list ? list.name : 'List')} setQ(e.target.value)} /> load(1)}>Search Import CSV{ const f=e.target.files?.[0]; if(!f) return; const fd=new FormData(); fd.append('list_id', String(listId)); fd.append('file', f); fetch('/api/list_contacts_import.php',{method:'POST',body:fd,credentials:'include'}).then(()=>{ e.target.value=''; load(1); }); }} /> Export CSV Add Contact { setOpenFrom(true); setSearchC(''); setCandidates([]); setPick(new Set()); }}>Add from Contacts Delete Selected{selected.size?` (${selected.size})`:''} Export Selected{selected.size?` (${selected.size})`:''} {err && {err}} {!err && (isXs ? ( {rows.length===0 ? No records. : rows.map(r => )} ) : ( Name Phone Email Status Address City State Zip Country Notes Actions {rows.map(r=>{ const checked = selected.has(r.id); return ( toggleOne(r.id)} /> {r.name} {r.phone||'-'} {r.email||'-'} {r.address_line||'-'} {r.city||'-'} {r.state||'-'} {r.zip_code||'-'} {r.country||'-'} {r.notes||'-'} openEditRow(r)} title="Edit">edit { if(confirm('Delete this contact from list?')){ delOne(r.id).then(()=>load(page)); }}} title="Delete">delete ); })} {rows.length===0 && ( No records. )} ))} load(p)} /> {/* Add/Edit contact modal */} {form.id ? 'Edit Contact' : 'Add Contact'} setForm({...form,name:e.target.value})} fullWidth required /> setForm({...form,status:e.target.value})} fullWidth> Active Blocked setForm({...form,phone:e.target.value})} fullWidth /> setForm({...form,email:e.target.value})} fullWidth /> setForm({...form,address_line:e.target.value})} fullWidth /> setForm({...form,apt_suite:e.target.value})} fullWidth /> setForm({...form,city:e.target.value})} fullWidth /> setForm({...form,state:e.target.value})} fullWidth /> setForm({...form,zip_code:e.target.value})} fullWidth /> setForm({...form,country:e.target.value})} fullWidth /> setForm({...form,notes:e.target.value})} fullWidth multiline minRows={3} /> Cancel {form.id?'Save':'Add'} {/* Add from Contacts modal */} setOpenFrom(false)} maxWidth="md" fullWidth> Add from Contacts setSearchC(e.target.value)} fullWidth /> Search Name Phone Email {candidates.map(c => { const checked = pick.has(c.id); return ( togglePick(c.id)} sx={{ cursor:'pointer' }}> {c.name} {c.phone||'-'} {c.email||'-'} ); })} {candidates.length===0 && ( No results. )} setOpenFrom(false)}>Cancel Add Selected{pick.size?` (${pick.size})`:''} ); } window.ListEditPage = ListEditPage;