secutorpro commited on
Commit
741bf09
·
verified ·
1 Parent(s): 6c972ac

members panel

Browse files
Files changed (1) hide show
  1. web/src/components/MembersPanel.jsx +53 -76
web/src/components/MembersPanel.jsx CHANGED
@@ -1,36 +1,42 @@
1
  import { useState } from 'react';
2
- import { api } from '../api.js';
3
 
4
  const EMPTY = { name: '', callsign: '', device_id: '', group_id: '' };
5
 
6
- export default function MembersPanel({ members, groups }) {
 
7
  const [form, setForm] = useState(EMPTY);
8
- const [editingId, setEditingId] = useState(null);
9
  const [error, setError] = useState('');
10
 
11
- const save = async (e) => {
 
 
 
 
 
 
 
 
 
 
12
  e.preventDefault();
13
  setError('');
14
- const body = { ...form, group_id: form.group_id ? Number(form.group_id) : null };
15
  try {
16
- if (editingId) await api.put(`/members/${editingId}`, body);
17
- else await api.post('/members', body);
 
 
 
18
  setForm(EMPTY);
19
- setEditingId(null);
20
  } catch (err) {
21
  setError(err.message);
22
  }
23
  };
24
 
25
- const edit = (m) => {
26
- setEditingId(m.id);
27
- setForm({ name: m.name, callsign: m.callsign || '', device_id: m.device_id || '', group_id: m.group_id || '' });
28
- };
29
-
30
  const remove = async (m) => {
31
  if (!confirm(`Remove ${m.name}?`)) return;
 
32
  try {
33
- await api.del(`/members/${m.id}`);
34
  } catch (err) {
35
  setError(err.message);
36
  }
@@ -38,70 +44,41 @@ export default function MembersPanel({ members, groups }) {
38
 
39
  return (
40
  <section className="panel">
41
- <h2>Members ({members.length})</h2>
 
 
 
 
 
42
  {error && <p className="error">{error}</p>}
43
- <table>
44
- <thead>
45
- <tr><th></th><th>Name</th><th>Group</th><th>Device</th><th>Batt</th><th></th></tr>
46
- </thead>
47
- <tbody>
48
- {members.map((m) => (
49
- <tr key={m.id}>
50
- <td><span className={`dot ${m.status}`} /></td>
51
- <td>
52
- {m.name}
53
- {m.callsign && <><br /><span className="muted small">{m.callsign}</span></>}
54
- </td>
55
- <td><span className="chip-label" style={{ '--c': m.group_color || '#64748b' }}>{m.group_name || '—'}</span></td>
56
- <td className="small muted">{m.device_id}</td>
57
- <td className="small">{m.battery != null ? `${m.battery}%` : '—'}</td>
58
- <td>
59
- <button className="secondary small-btn" onClick={() => edit(m)}>edit</button>{' '}
60
- <button className="secondary small-btn" onClick={() => remove(m)}>✕</button>
61
- </td>
62
- </tr>
63
- ))}
64
- </tbody>
65
- </table>
66
-
67
- <h3>{editingId ? `Edit member #${editingId}` : 'Add member'}</h3>
68
- <form onSubmit={save}>
69
- <input
70
- required
71
- placeholder="Name"
72
- value={form.name}
73
- onChange={(e) => setForm({ ...form, name: e.target.value })}
74
- />
75
- <input
76
- placeholder="Callsign"
77
- value={form.callsign}
78
- onChange={(e) => setForm({ ...form, callsign: e.target.value })}
79
- />
80
- <input
81
- required
82
- placeholder="Device ID (unique)"
83
- value={form.device_id}
84
- onChange={(e) => setForm({ ...form, device_id: e.target.value })}
85
- />
86
- <select value={form.group_id} onChange={(e) => setForm({ ...form, group_id: e.target.value })}>
87
- <option value="">No group</option>
88
- {groups.map((g) => (
89
- <option key={g.id} value={g.id}>{g.name}</option>
90
- ))}
91
- </select>
92
- <div className="row">
93
- <button type="submit">{editingId ? 'Save' : 'Add'}</button>
94
- {editingId && (
95
- <button
96
- type="button"
97
- className="secondary"
98
- onClick={() => { setEditingId(null); setForm(EMPTY); }}
99
- >
100
- Cancel
101
- </button>
102
- )}
103
  </div>
104
- </form>
105
  </section>
106
  );
107
  }
 
1
  import { useState } from 'react';
 
2
 
3
  const EMPTY = { name: '', callsign: '', device_id: '', group_id: '' };
4
 
5
+ export default function MembersPanel({ members, groups, sosIds = new Set(), selected, onSelect, onSave, onDelete }) {
6
+ const [editing, setEditing] = useState(null); // null | {} (new) | member
7
  const [form, setForm] = useState(EMPTY);
 
8
  const [error, setError] = useState('');
9
 
10
+ const start = (m) => {
11
+ setEditing(m || {});
12
+ setForm({
13
+ name: m?.name || '',
14
+ callsign: m?.callsign || '',
15
+ device_id: m?.device_id || '',
16
+ group_id: m?.group_id ?? '',
17
+ });
18
+ };
19
+
20
+ const submit = async (e) => {
21
  e.preventDefault();
22
  setError('');
 
23
  try {
24
+ await onSave(editing?.id ?? null, {
25
+ ...form,
26
+ group_id: form.group_id === '' ? null : Number(form.group_id),
27
+ });
28
+ setEditing(null);
29
  setForm(EMPTY);
 
30
  } catch (err) {
31
  setError(err.message);
32
  }
33
  };
34
 
 
 
 
 
 
35
  const remove = async (m) => {
36
  if (!confirm(`Remove ${m.name}?`)) return;
37
+ setError('');
38
  try {
39
+ await onDelete(m.id);
40
  } catch (err) {
41
  setError(err.message);
42
  }
 
44
 
45
  return (
46
  <section className="panel">
47
+ <h2>
48
+ Members ({members.length})
49
+ <button className="icon-btn" style={{ float: 'right' }} onClick={() => (editing ? (setEditing(null), setForm(EMPTY)) : start(null))}>
50
+ {editing ? '×' : '+'}
51
+ </button>
52
+ </h2>
53
  {error && <p className="error">{error}</p>}
54
+ {editing && (
55
+ <form className="member-form" onSubmit={submit}>
56
+ <input placeholder="Name" required value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
57
+ <div className="form-row">
58
+ <input placeholder="Callsign" value={form.callsign} onChange={(e) => setForm({ ...form, callsign: e.target.value })} />
59
+ <input placeholder="Device ID (unique)" value={form.device_id} onChange={(e) => setForm({ ...form, device_id: e.target.value })} disabled={!!editing.id} />
60
+ </div>
61
+ <select value={form.group_id ?? ''} onChange={(e) => setForm({ ...form, group_id: e.target.value })}>
62
+ <option value="">No group</option>
63
+ {groups.map((g) => <option key={g.id} value={g.id}>{g.name}</option>)}
64
+ </select>
65
+ <div className="form-row">
66
+ <button className="primary">{editing.id ? 'Save' : 'Add member'}</button>
67
+ {editing.id && <button type="button" className="danger" onClick={() => remove(editing)}>Delete</button>}
68
+ </div>
69
+ </form>
70
+ )}
71
+ {members.map((m) => (
72
+ <div key={m.id} className={`mrow ${selected === m.id ? 'selected' : ''}`} onClick={() => onSelect(m.id)}>
73
+ <span className={`st ${m.status === 'online' ? 'online' : ''} ${sosIds.has(m.id) ? 'sos' : ''}`} />
74
+ <span className="nm">
75
+ {m.name}
76
+ <span className="sub"> · {m.group_name || '—'}{m.callsign ? ` · ${m.callsign}` : ''}</span>
77
+ </span>
78
+ <span className="bat">{m.battery != null ? `${m.battery}%` : ''}</span>
79
+ <button className="icon-btn" onClick={(e) => { e.stopPropagation(); start(m); }}>✎</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  </div>
81
+ ))}
82
  </section>
83
  );
84
  }