Spaces:
Paused
Paused
Download web/src/components/MembersPanel.jsx from secutorpro/dispatch-console: direct link, hf CLI and curl.
- Browser
- Download file 3.24 kB
-
https://huggingface.co/spaces/secutorpro/dispatch-console/resolve/main/web/src/components/MembersPanel.jsx
- Command line
-
hf download hf://spaces/secutorpro/dispatch-console/web/src/components/MembersPanel.jsx
-
curl -L -o MembersPanel.jsx https://huggingface.co/spaces/secutorpro/dispatch-console/resolve/main/web/src/components/MembersPanel.jsx
3.24 kB
| import { useState } from 'react'; | |
| const EMPTY = { name: '', callsign: '', device_id: '', group_id: '' }; | |
| export default function MembersPanel({ members, groups, sosIds = new Set(), selected, onSelect, onSave, onDelete }) { | |
| const [editing, setEditing] = useState(null); // null | {} (new) | member | |
| const [form, setForm] = useState(EMPTY); | |
| const [error, setError] = useState(''); | |
| const start = (m) => { | |
| setEditing(m || {}); | |
| setForm({ | |
| name: m?.name || '', | |
| callsign: m?.callsign || '', | |
| device_id: m?.device_id || '', | |
| group_id: m?.group_id ?? '', | |
| }); | |
| }; | |
| const submit = async (e) => { | |
| e.preventDefault(); | |
| setError(''); | |
| try { | |
| await onSave(editing?.id ?? null, { | |
| ...form, | |
| group_id: form.group_id === '' ? null : Number(form.group_id), | |
| }); | |
| setEditing(null); | |
| setForm(EMPTY); | |
| } catch (err) { | |
| setError(err.message); | |
| } | |
| }; | |
| const remove = async (m) => { | |
| if (!confirm(`Remove ${m.name}?`)) return; | |
| setError(''); | |
| try { | |
| await onDelete(m.id); | |
| } catch (err) { | |
| setError(err.message); | |
| } | |
| }; | |
| return ( | |
| <section className="panel"> | |
| <h2> | |
| Members ({members.length}) | |
| <button className="icon-btn" style={{ float: 'right' }} onClick={() => (editing ? (setEditing(null), setForm(EMPTY)) : start(null))}> | |
| {editing ? '×' : '+'} | |
| </button> | |
| </h2> | |
| {error && <p className="error">{error}</p>} | |
| {editing && ( | |
| <form className="member-form" onSubmit={submit}> | |
| <input placeholder="Name" required value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} /> | |
| <div className="form-row"> | |
| <input placeholder="Callsign" value={form.callsign} onChange={(e) => setForm({ ...form, callsign: e.target.value })} /> | |
| <input placeholder="Device ID (unique)" value={form.device_id} onChange={(e) => setForm({ ...form, device_id: e.target.value })} disabled={!!editing.id} /> | |
| </div> | |
| <select value={form.group_id ?? ''} onChange={(e) => setForm({ ...form, group_id: e.target.value })}> | |
| <option value="">No group</option> | |
| {groups.map((g) => <option key={g.id} value={g.id}>{g.name}</option>)} | |
| </select> | |
| <div className="form-row"> | |
| <button className="primary">{editing.id ? 'Save' : 'Add member'}</button> | |
| {editing.id && <button type="button" className="danger" onClick={() => remove(editing)}>Delete</button>} | |
| </div> | |
| </form> | |
| )} | |
| {members.map((m) => ( | |
| <div key={m.id} className={`mrow ${selected === m.id ? 'selected' : ''}`} onClick={() => onSelect(m.id)}> | |
| <span className={`st ${m.status === 'online' ? 'online' : ''} ${sosIds.has(m.id) ? 'sos' : ''}`} /> | |
| <span className="nm"> | |
| {m.name} | |
| <span className="sub"> · {m.group_name || '—'}{m.callsign ? ` · ${m.callsign}` : ''}</span> | |
| </span> | |
| <span className="bat">{m.battery != null ? `${m.battery}%` : ''}</span> | |
| <button className="icon-btn" onClick={(e) => { e.stopPropagation(); start(m); }}>✎</button> | |
| </div> | |
| ))} | |
| </section> | |
| ); | |
| } |