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;
}
/* ── 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 */}