fix: modal styling - solid bg, full-width dropdown, labels

This commit is contained in:
root 2026-05-27 11:59:43 +00:00
parent e04485c446
commit cee5acab1d
1 changed files with 92 additions and 54 deletions

View File

@ -49,7 +49,7 @@ async function adminApi(method, body) {
return json; return json;
} }
/* ── Custom dropdown (matches app design system) ── */ /* ── Custom dropdown (matches app design system, full-width) ── */
function RoleDropdown({ value, onChange }) { function RoleDropdown({ value, onChange }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const ref = useRef(null); const ref = useRef(null);
@ -66,17 +66,17 @@ function RoleDropdown({ value, onChange }) {
const pick = (role) => { onChange(role); setOpen(false); }; const pick = (role) => { onChange(role); setOpen(false); };
return ( return (
<div ref={ref} className="relative"> <div ref={ref} className="relative w-full">
<button <button
type="button" type="button"
onClick={() => setOpen((v) => !v)} 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> <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> </button>
{open && ( {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) => { {ROLES.map((r) => {
const sel = r === value; const sel = r === value;
return ( return (
@ -85,8 +85,8 @@ function RoleDropdown({ value, onChange }) {
type="button" type="button"
onClick={() => pick(r)} onClick={() => pick(r)}
className={[ className={[
'flex w-full items-center justify-between px-4 py-2.5 text-left text-sm transition', '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-text)]' : 'text-[var(--color-text)] hover:bg-[var(--color-surface-strong)]', sel ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]' : 'text-[var(--color-text)] hover:bg-[var(--color-surface)]',
].join(' ')} ].join(' ')}
> >
<span>{ROLE_LABELS[r]}</span> <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() { export default function UserManagementPanel() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [roles, setRoles] = useState([]); const [roles, setRoles] = useState([]);
@ -110,7 +184,6 @@ export default function UserManagementPanel() {
const [editForm, setEditForm] = useState({ name: '', email: '', role: '' }); const [editForm, setEditForm] = useState({ name: '', email: '', role: '' });
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [deleteConfirmId, setDeleteConfirmId] = useState(null); const [deleteConfirmId, setDeleteConfirmId] = useState(null);
const [addForm, setAddForm] = useState({ name: '', email: '', role: '' });
const [addSubmitting, setAddSubmitting] = useState(false); const [addSubmitting, setAddSubmitting] = useState(false);
const [addError, setAddError] = useState(null); const [addError, setAddError] = useState(null);
@ -146,16 +219,14 @@ export default function UserManagementPanel() {
}; };
/* ── Add via edge function ── */ /* ── Add via edge function ── */
const handleAddUser = async (e) => { const handleAddUser = async (form) => {
e.preventDefault();
setAddError(null); setAddError(null);
if (!addForm.name || !addForm.email || !addForm.role) { setAddError('Все поля обязательны.'); return; } if (!form.name || !form.email || !form.role) { setAddError('Все поля обязательны.'); return; }
setAddSubmitting(true); setAddSubmitting(true);
try { 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(); await fetchUsers();
setShowAddForm(false); setShowAddForm(false);
setAddForm({ name: '', email: '', role: '' });
} catch (err) { } catch (err) {
setAddError(err.message || 'Не удалось добавить пользователя.'); setAddError(err.message || 'Не удалось добавить пользователя.');
} finally { setAddSubmitting(false); } } finally { setAddSubmitting(false); }
@ -199,54 +270,21 @@ export default function UserManagementPanel() {
<div className="flex items-center justify-between gap-2 flex-wrap mb-4"> <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> <span className="text-sm text-[var(--color-text-muted)]">{users.length} пользователей</span>
<button <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" className="rounded-full bg-[var(--color-accent)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90 transition"
> >
{showAddForm ? 'Отмена' : '+ Добавить'} + Добавить
</button> </button>
</div> </div>
{/* Add user modal */} {/* Add user modal */}
{showAddForm && ( {showAddForm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm" onClick={() => { setShowAddForm(false); setAddError(null); }}> <AddUserModal
<div className="w-[340px] rounded-[28px] border border-[var(--color-border)] bg-[var(--color-surface)] p-6 shadow-soft" onClick={(e) => e.stopPropagation()}> onSubmit={handleAddUser}
<h3 className="mb-4 text-base font-semibold">Новый пользователь</h3> onClose={() => { setShowAddForm(false); setAddError(null); }}
<form onSubmit={handleAddUser} className="space-y-3"> submitting={addSubmitting}
<Input error={addError}
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>
)} )}
{/* Error */} {/* Error */}