fix: modal styling - solid bg, full-width dropdown, labels
This commit is contained in:
parent
e04485c446
commit
cee5acab1d
|
|
@ -49,7 +49,7 @@ async function adminApi(method, body) {
|
|||
return json;
|
||||
}
|
||||
|
||||
/* ── Custom dropdown (matches app design system) ── */
|
||||
/* ── Custom dropdown (matches app design system, full-width) ── */
|
||||
function RoleDropdown({ value, onChange }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
|
@ -66,17 +66,17 @@ function RoleDropdown({ value, onChange }) {
|
|||
const pick = (role) => { onChange(role); setOpen(false); };
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
<div ref={ref} className="relative w-full">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="flex h-[38px] items-center gap-1 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-3 text-sm transition hover:border-[var(--color-accent)]"
|
||||
className="flex w-full h-[46px] items-center justify-between gap-2 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-strong)] px-4 text-sm transition hover:border-[var(--color-accent)]"
|
||||
>
|
||||
<Badge tone={ROLE_TONES[value] || 'neutral'}>{ROLE_LABELS[value] || value}</Badge>
|
||||
<span className="ml-1 text-[var(--color-text-muted)] text-xs">▾</span>
|
||||
<span className="text-[var(--color-text-muted)] text-xs">▾</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute left-0 top-full z-30 mt-2 overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-dropdown-surface)] shadow-soft min-w-[180px]">
|
||||
<div className="absolute left-0 right-0 top-full z-50 mt-1.5 overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-dropdown-surface)] shadow-soft">
|
||||
{ROLES.map((r) => {
|
||||
const sel = r === value;
|
||||
return (
|
||||
|
|
@ -85,8 +85,8 @@ function RoleDropdown({ value, onChange }) {
|
|||
type="button"
|
||||
onClick={() => pick(r)}
|
||||
className={[
|
||||
'flex w-full items-center justify-between px-4 py-2.5 text-left text-sm transition',
|
||||
sel ? 'bg-[var(--color-accent-soft)] text-[var(--color-text)]' : 'text-[var(--color-text)] hover:bg-[var(--color-surface-strong)]',
|
||||
'flex w-full items-center justify-between px-4 py-3 text-left text-sm transition',
|
||||
sel ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]' : 'text-[var(--color-text)] hover:bg-[var(--color-surface)]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span>{ROLE_LABELS[r]}</span>
|
||||
|
|
@ -100,6 +100,80 @@ function RoleDropdown({ value, onChange }) {
|
|||
);
|
||||
}
|
||||
|
||||
/* ── Add-user modal ── */
|
||||
function AddUserModal({ onSubmit, onClose, submitting, error }) {
|
||||
const [form, setForm] = useState({ name: '', email: '', role: 'manager' });
|
||||
const nameRef = useRef(null);
|
||||
|
||||
useEffect(() => { nameRef.current?.focus(); }, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="w-[400px] max-w-[92vw] rounded-[28px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-6 shadow-panel"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className="mb-5 text-lg font-semibold text-[var(--color-text)]">Новый пользователь</h3>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
onSubmit(form);
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-xs font-medium text-[var(--color-text-muted)]">Имя</label>
|
||||
<Input
|
||||
ref={nameRef}
|
||||
placeholder="Иван Петров"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-xs font-medium text-[var(--color-text-muted)]">Email</label>
|
||||
<Input
|
||||
placeholder="ivan@company.ru"
|
||||
type="email"
|
||||
value={form.email}
|
||||
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-xs font-medium text-[var(--color-text-muted)]">Роль</label>
|
||||
<RoleDropdown
|
||||
value={form.role}
|
||||
onChange={(r) => setForm((f) => ({ ...f, role: r }))}
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="rounded-xl bg-[rgba(201,61,61,0.12)] px-4 py-2.5 text-sm text-[var(--color-danger)]">{error}</div>
|
||||
)}
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex-1 rounded-full border border-[var(--color-border)] px-4 py-2.5 text-sm font-semibold text-[var(--color-text-muted)] hover:bg-[var(--color-surface)] transition"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="flex-1 rounded-full bg-[var(--color-accent)] px-4 py-2.5 text-sm font-semibold text-white hover:opacity-90 transition disabled:opacity-50"
|
||||
>
|
||||
{submitting ? 'Добавление…' : 'Добавить'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UserManagementPanel() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [roles, setRoles] = useState([]);
|
||||
|
|
@ -110,7 +184,6 @@ export default function UserManagementPanel() {
|
|||
const [editForm, setEditForm] = useState({ name: '', email: '', role: '' });
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [deleteConfirmId, setDeleteConfirmId] = useState(null);
|
||||
const [addForm, setAddForm] = useState({ name: '', email: '', role: '' });
|
||||
const [addSubmitting, setAddSubmitting] = useState(false);
|
||||
const [addError, setAddError] = useState(null);
|
||||
|
||||
|
|
@ -146,16 +219,14 @@ export default function UserManagementPanel() {
|
|||
};
|
||||
|
||||
/* ── Add via edge function ── */
|
||||
const handleAddUser = async (e) => {
|
||||
e.preventDefault();
|
||||
const handleAddUser = async (form) => {
|
||||
setAddError(null);
|
||||
if (!addForm.name || !addForm.email || !addForm.role) { setAddError('Все поля обязательны.'); return; }
|
||||
if (!form.name || !form.email || !form.role) { setAddError('Все поля обязательны.'); return; }
|
||||
setAddSubmitting(true);
|
||||
try {
|
||||
await adminApi('POST', { email: addForm.email, name: addForm.name, role: addForm.role });
|
||||
await adminApi('POST', { email: form.email, name: form.name, role: form.role });
|
||||
await fetchUsers();
|
||||
setShowAddForm(false);
|
||||
setAddForm({ name: '', email: '', role: '' });
|
||||
} catch (err) {
|
||||
setAddError(err.message || 'Не удалось добавить пользователя.');
|
||||
} finally { setAddSubmitting(false); }
|
||||
|
|
@ -199,54 +270,21 @@ export default function UserManagementPanel() {
|
|||
<div className="flex items-center justify-between gap-2 flex-wrap mb-4">
|
||||
<span className="text-sm text-[var(--color-text-muted)]">{users.length} пользователей</span>
|
||||
<button
|
||||
onClick={() => { setShowAddForm(!showAddForm); setAddError(null); }}
|
||||
onClick={() => { setShowAddForm(true); setAddError(null); }}
|
||||
className="rounded-full bg-[var(--color-accent)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90 transition"
|
||||
>
|
||||
{showAddForm ? 'Отмена' : '+ Добавить'}
|
||||
+ Добавить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Add user modal */}
|
||||
{showAddForm && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm" onClick={() => { setShowAddForm(false); setAddError(null); }}>
|
||||
<div className="w-[340px] rounded-[28px] border border-[var(--color-border)] bg-[var(--color-surface)] p-6 shadow-soft" onClick={(e) => e.stopPropagation()}>
|
||||
<h3 className="mb-4 text-base font-semibold">Новый пользователь</h3>
|
||||
<form onSubmit={handleAddUser} className="space-y-3">
|
||||
<Input
|
||||
placeholder="Имя"
|
||||
value={addForm.name}
|
||||
onChange={(e) => setAddForm((f) => ({ ...f, name: e.target.value }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Email"
|
||||
type="email"
|
||||
value={addForm.email}
|
||||
onChange={(e) => setAddForm((f) => ({ ...f, email: e.target.value }))}
|
||||
/>
|
||||
<RoleDropdown
|
||||
value={addForm.role || 'manager'}
|
||||
onChange={(r) => setAddForm((f) => ({ ...f, role: r }))}
|
||||
/>
|
||||
{addError && <div className="text-sm text-[var(--color-danger)]">{addError}</div>}
|
||||
<div className="flex justify-end gap-2 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setShowAddForm(false); setAddError(null); }}
|
||||
className="rounded-full border border-[var(--color-border)] px-4 py-2 text-sm font-semibold text-[var(--color-text-muted)] hover:bg-[var(--color-surface-strong)] transition"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={addSubmitting}
|
||||
className="rounded-full bg-[var(--color-accent)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90 transition disabled:opacity-50"
|
||||
>
|
||||
{addSubmitting ? 'Добавление…' : 'Добавить'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<AddUserModal
|
||||
onSubmit={handleAddUser}
|
||||
onClose={() => { setShowAddForm(false); setAddError(null); }}
|
||||
submitting={addSubmitting}
|
||||
error={addError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue