feat: UserManagementPanel — themed dropdown, inline edit name/email/role
This commit is contained in:
parent
bb6708e94b
commit
b9b227e524
|
|
@ -1,9 +1,7 @@
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { Panel } from '../UI/Panel';
|
import { Panel } from '../UI/Panel';
|
||||||
import { Badge } from '../UI/Badge';
|
import { Badge } from '../UI/Badge';
|
||||||
import { Input } from '../UI/Input';
|
import { Input } from '../UI/Input';
|
||||||
import { Select } from '../UI/Select';
|
|
||||||
|
|
||||||
|
|
||||||
import { supabase } from '../../supabaseClient';
|
import { supabase } from '../../supabaseClient';
|
||||||
|
|
||||||
|
|
@ -25,17 +23,56 @@ const ROLE_TONES = {
|
||||||
driver: 'accent',
|
driver: 'accent',
|
||||||
};
|
};
|
||||||
|
|
||||||
const useIsMobile = () => {
|
/* ── Custom dropdown (matches app design system) ── */
|
||||||
const [mobile, setMobile] = useState(false);
|
function RoleDropdown({ value, onChange, onClose }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mq = window.matchMedia('(max-width: 640px)');
|
if (!open) return;
|
||||||
setMobile(mq.matches);
|
const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) { setOpen(false); onClose?.(); } };
|
||||||
const handler = (e) => setMobile(e.matches);
|
const onKey = (e) => { if (e.key === 'Escape') { setOpen(false); onClose?.(); } };
|
||||||
mq.addEventListener('change', handler);
|
document.addEventListener('pointerdown', onDown);
|
||||||
return () => mq.removeEventListener('change', handler);
|
document.addEventListener('keydown', onKey);
|
||||||
}, []);
|
return () => { document.removeEventListener('pointerdown', onDown); document.removeEventListener('keydown', onKey); };
|
||||||
return mobile;
|
}, [open, onClose]);
|
||||||
};
|
|
||||||
|
const pick = (role) => { onChange(role); setOpen(false); };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="relative">
|
||||||
|
<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)]"
|
||||||
|
>
|
||||||
|
<Badge tone={ROLE_TONES[value] || 'neutral'}>{ROLE_LABELS[value] || value}</Badge>
|
||||||
|
<span className="ml-1 text-[var(--color-text-muted)] text-xs">▾</span>
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div className="absolute left-0 right-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]">
|
||||||
|
{ROLES.map((r) => {
|
||||||
|
const sel = r === value;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={r}
|
||||||
|
type="button"
|
||||||
|
onClick={() => pick(r)}
|
||||||
|
className={[
|
||||||
|
'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)]',
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
<span>{ROLE_LABELS[r]}</span>
|
||||||
|
{sel && <span className="text-[var(--color-accent)]">✓</span>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function UserManagementPanel() {
|
export default function UserManagementPanel() {
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
|
|
@ -43,15 +80,13 @@ export default function UserManagementPanel() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [showAddForm, setShowAddForm] = useState(false);
|
const [showAddForm, setShowAddForm] = useState(false);
|
||||||
const [editingRoleId, setEditingRoleId] = useState(null);
|
const [editingId, setEditingId] = useState(null);
|
||||||
|
const [editForm, setEditForm] = useState({ name: '', email: '', role: '' });
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
const [deleteConfirmId, setDeleteConfirmId] = useState(null);
|
const [deleteConfirmId, setDeleteConfirmId] = useState(null);
|
||||||
const [addForm, setAddForm] = useState({ name: '', email: '', role: '' });
|
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);
|
||||||
const mobile = useIsMobile();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const fetchRoles = useCallback(async () => {
|
const fetchRoles = useCallback(async () => {
|
||||||
const { data, error: err } = await supabase.from('roles').select('id, name').order('name');
|
const { data, error: err } = await supabase.from('roles').select('id, name').order('name');
|
||||||
|
|
@ -79,25 +114,18 @@ export default function UserManagementPanel() {
|
||||||
const match = roles.find((r) => r.id === user.role_id);
|
const match = roles.find((r) => r.id === user.role_id);
|
||||||
return match ? match.name : 'unknown';
|
return match ? match.name : 'unknown';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRoleId = (roleName) => {
|
const getRoleId = (roleName) => {
|
||||||
const match = roles.find((r) => r.name === roleName);
|
const match = roles.find((r) => r.name === roleName);
|
||||||
return match ? match.id : null;
|
return match ? match.id : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ── Add ── */
|
||||||
const handleAddUser = async (e) => {
|
const handleAddUser = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setAddError(null);
|
setAddError(null);
|
||||||
if (!addForm.name || !addForm.email || !addForm.role) {
|
if (!addForm.name || !addForm.email || !addForm.role) { setAddError('Все поля обязательны.'); return; }
|
||||||
setAddError('Все поля обязательны.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAddSubmitting(true);
|
setAddSubmitting(true);
|
||||||
try {
|
try {
|
||||||
if (!supabase) {
|
|
||||||
setAddError('Требуется VITE_SUPABASE_SERVICE_ROLE_KEY для управления пользователями.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const roleId = getRoleId(addForm.role);
|
const roleId = getRoleId(addForm.role);
|
||||||
const { data: authData, error: authErr } = await supabase.auth.admin.createUser({
|
const { data: authData, error: authErr } = await supabase.auth.admin.createUser({
|
||||||
email: addForm.email,
|
email: addForm.email,
|
||||||
|
|
@ -114,67 +142,56 @@ export default function UserManagementPanel() {
|
||||||
setAddForm({ name: '', email: '', role: '' });
|
setAddForm({ name: '', email: '', role: '' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setAddError(err.message || 'Не удалось добавить пользователя.');
|
setAddError(err.message || 'Не удалось добавить пользователя.');
|
||||||
} finally {
|
} finally { setAddSubmitting(false); }
|
||||||
setAddSubmitting(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRoleChange = async (userId, newRoleName) => {
|
/* ── Edit ── */
|
||||||
const roleId = getRoleId(newRoleName);
|
const startEdit = (user) => {
|
||||||
if (!roleId) return;
|
setEditingId(user.id);
|
||||||
|
setEditForm({ name: user.name || '', email: user.email || '', role: getRoleName(user) });
|
||||||
|
setDeleteConfirmId(null);
|
||||||
|
};
|
||||||
|
const cancelEdit = () => { setEditingId(null); setEditForm({ name: '', email: '', role: '' }); };
|
||||||
|
const saveEdit = async () => {
|
||||||
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
|
const roleId = getRoleId(editForm.role);
|
||||||
const { error: err } = await supabase
|
const { error: err } = await supabase
|
||||||
.from('users')
|
.from('users')
|
||||||
.update({ role_id: roleId })
|
.update({ name: editForm.name, email: editForm.email, role_id: roleId })
|
||||||
.eq('id', userId);
|
.eq('id', editingId);
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
setEditingRoleId(null);
|
setEditingId(null);
|
||||||
await fetchUsers();
|
await fetchUsers();
|
||||||
} catch (err) {
|
} catch (err) { setError(err.message || 'Не удалось сохранить.'); }
|
||||||
setError(err.message || 'Не удалось обновить роль.');
|
finally { setSaving(false); }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ── Delete ── */
|
||||||
const handleDeleteUser = async (userId) => {
|
const handleDeleteUser = async (userId) => {
|
||||||
try {
|
try {
|
||||||
const { error: err } = await supabase.from('users').delete().eq('id', userId);
|
const { error: err } = await supabase.from('users').delete().eq('id', userId);
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
if (supabase) await supabase.auth.admin.deleteUser(userId);
|
try { await supabase.auth.admin.deleteUser(userId); } catch {}
|
||||||
setDeleteConfirmId(null);
|
setDeleteConfirmId(null);
|
||||||
await fetchUsers();
|
await fetchUsers();
|
||||||
} catch (err) {
|
} catch (err) { setError(err.message || 'Не удалось удалить.'); }
|
||||||
setError(err.message || 'Не удалось удалить пользователя.');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fmtDate = (iso) => iso ? new Date(iso).toLocaleString('ru-RU') : '—';
|
const fmtDate = (iso) => iso ? new Date(iso).toLocaleString('ru-RU') : '—';
|
||||||
|
|
||||||
// Mobile: horizontal scroll for table
|
if (loading) {
|
||||||
const mobileTableStyle = mobile ? {
|
return <Panel className="p-5"><div className="text-center py-8 text-[var(--color-text-muted)]">Загрузка…</div></Panel>;
|
||||||
overflowX: 'auto',
|
}
|
||||||
WebkitOverflowScrolling: 'touch',
|
|
||||||
msOverflowStyle: '-ms-autohiding-scrollbar',
|
|
||||||
} : {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel>
|
<Panel>
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem', flexWrap: 'wrap', gap: '0.5rem' }}>
|
<div className="flex items-center justify-between gap-2 flex-wrap mb-4">
|
||||||
<span style={{ color: 'var(--color-text-muted, #94a3b8)', fontSize: '0.9rem' }}>
|
<span className="text-sm text-[var(--color-text-muted)]">{users.length} пользователей</span>
|
||||||
{users.length} пользователей
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { setShowAddForm(!showAddForm); setAddError(null); }}
|
onClick={() => { setShowAddForm(!showAddForm); setAddError(null); }}
|
||||||
style={{
|
className="rounded-full bg-[var(--color-accent)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90 transition"
|
||||||
background: 'var(--color-accent, #22c55e)',
|
|
||||||
color: '#fff',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
padding: '0.5rem 1rem',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '0.85rem',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{showAddForm ? 'Отмена' : '+ Добавить'}
|
{showAddForm ? 'Отмена' : '+ Добавить'}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -182,16 +199,8 @@ export default function UserManagementPanel() {
|
||||||
|
|
||||||
{/* Add form */}
|
{/* Add form */}
|
||||||
{showAddForm && (
|
{showAddForm && (
|
||||||
<form onSubmit={handleAddUser} style={{
|
<form onSubmit={handleAddUser} className="mb-4 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 space-y-3">
|
||||||
background: 'var(--color-surface-strong, #1e293b)',
|
<div className="flex flex-wrap gap-3">
|
||||||
borderRadius: '16px',
|
|
||||||
padding: '1rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '0.75rem',
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap' }}>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="Имя"
|
placeholder="Имя"
|
||||||
value={addForm.name}
|
value={addForm.name}
|
||||||
|
|
@ -205,139 +214,115 @@ export default function UserManagementPanel() {
|
||||||
onChange={(e) => setAddForm((f) => ({ ...f, email: e.target.value }))}
|
onChange={(e) => setAddForm((f) => ({ ...f, email: e.target.value }))}
|
||||||
className="flex-1 min-w-[180px]!"
|
className="flex-1 min-w-[180px]!"
|
||||||
/>
|
/>
|
||||||
<Select
|
<RoleDropdown
|
||||||
value={addForm.role}
|
value={addForm.role || 'manager'}
|
||||||
onChange={(e) => setAddForm((f) => ({ ...f, role: e.target.value }))}
|
onChange={(r) => setAddForm((f) => ({ ...f, role: r }))}
|
||||||
className="min-w-[140px]!"
|
/>
|
||||||
>
|
|
||||||
<option value="">Выберите роль…</option>
|
|
||||||
{ROLES.map((r) => <option key={r} value={r}>{ROLE_LABELS[r] || r}</option>)}
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
{addError && (
|
{addError && <div className="text-sm text-[var(--color-danger)]">{addError}</div>}
|
||||||
<div style={{ color: 'var(--color-danger, #ef4444)', fontSize: '0.85rem' }}>{addError}</div>
|
<div className="flex justify-end">
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={addSubmitting}
|
disabled={addSubmitting}
|
||||||
style={{
|
className="rounded-full bg-[var(--color-accent)] px-5 py-2 text-sm font-semibold text-white hover:opacity-90 transition disabled:opacity-50"
|
||||||
alignSelf: 'flex-end',
|
|
||||||
background: addSubmitting ? 'var(--color-text-muted, #94a3b8)' : 'var(--color-accent, #22c55e)',
|
|
||||||
color: '#fff',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
padding: '0.5rem 1.25rem',
|
|
||||||
cursor: addSubmitting ? 'wait' : 'pointer',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '0.85rem',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{addSubmitting ? 'Добавление…' : 'Добавить'}
|
{addSubmitting ? 'Добавление…' : 'Добавить'}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Error */}
|
{/* Error */}
|
||||||
{error && (
|
{error && (
|
||||||
<div style={{
|
<div className="mb-4 rounded-xl bg-[rgba(201,61,61,0.12)] px-4 py-3 text-sm text-[var(--color-danger)]">
|
||||||
background: 'rgba(201,61,61,0.12)',
|
|
||||||
color: 'var(--color-danger, #ef4444)',
|
|
||||||
borderRadius: '12px',
|
|
||||||
padding: '0.75rem 1rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
fontSize: '0.9rem',
|
|
||||||
}}>
|
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loading ? (
|
{/* User list */}
|
||||||
<div style={{ textAlign: 'center', padding: '2rem', color: 'var(--color-text-muted, #94a3b8)' }}>
|
<div className="space-y-2">
|
||||||
Загрузка&
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={mobileTableStyle}>
|
|
||||||
<div style={{ minWidth: mobile ? '600px' : 'auto' }}>
|
|
||||||
{/* Header */}
|
|
||||||
<div style={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: '2fr 1.5fr 1.2fr 1fr auto',
|
|
||||||
gap: '0.5rem',
|
|
||||||
padding: '0.5rem 0.75rem',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
color: 'var(--color-text-muted, #94a3b8)',
|
|
||||||
borderBottom: '1px solid var(--color-border, #334155)',
|
|
||||||
}}>
|
|
||||||
<span>Email</span><span>Имя</span><span>Роль</span><span>Создан</span><span></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{users.length === 0 && (
|
{users.length === 0 && (
|
||||||
<div style={{ textAlign: 'center', padding: '2rem', color: 'var(--color-text-muted, #94a3b8)' }}>
|
<div className="py-8 text-center text-[var(--color-text-muted)]">Нет пользователей</div>
|
||||||
Нет пользователей
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{users.map((user) => {
|
{users.map((user) => {
|
||||||
const rn = getRoleName(user);
|
const rn = getRoleName(user);
|
||||||
|
const isEditing = editingId === user.id;
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={user.id}
|
key={user.id}
|
||||||
style={{
|
className="rounded-[22px] border border-[var(--color-accent)] bg-[var(--color-surface-strong)] p-4 space-y-3"
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: '2fr 1.5fr 1.2fr 1fr auto',
|
|
||||||
gap: '0.5rem',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '0.6rem 0.75rem',
|
|
||||||
borderBottom: '1px solid var(--color-border, #334155)',
|
|
||||||
fontSize: '0.9rem',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{user.email}</span>
|
<div className="flex flex-wrap gap-3">
|
||||||
<span style={{ fontWeight: 500 }}>{user.name || '—'}</span>
|
<Input
|
||||||
|
placeholder="Имя"
|
||||||
|
value={editForm.name}
|
||||||
|
onChange={(e) => setEditForm((f) => ({ ...f, name: e.target.value }))}
|
||||||
|
className="flex-1 min-w-[140px]!"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="Email"
|
||||||
|
type="email"
|
||||||
|
value={editForm.email}
|
||||||
|
onChange={(e) => setEditForm((f) => ({ ...f, email: e.target.value }))}
|
||||||
|
className="flex-1 min-w-[180px]!"
|
||||||
|
/>
|
||||||
|
<RoleDropdown
|
||||||
|
value={editForm.role}
|
||||||
|
onChange={(r) => setEditForm((f) => ({ ...f, role: r }))}
|
||||||
|
onClose={undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<button
|
||||||
|
onClick={cancelEdit}
|
||||||
|
className="rounded-lg border border-[var(--color-border)] px-3 py-1.5 text-sm text-[var(--color-text-muted)] hover:bg-[var(--color-surface)] transition"
|
||||||
|
>Отмена</button>
|
||||||
|
<button
|
||||||
|
onClick={saveEdit}
|
||||||
|
disabled={saving}
|
||||||
|
className="rounded-full bg-[var(--color-accent)] px-4 py-1.5 text-sm font-semibold text-white hover:opacity-90 transition disabled:opacity-50"
|
||||||
|
>{saving ? 'Сохранение…' : 'Сохранить'}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{editingRoleId === user.id ? (
|
return (
|
||||||
<Select
|
<div
|
||||||
value={rn}
|
key={user.id}
|
||||||
onChange={(e) => handleRoleChange(user.id, e.target.value)}
|
className="flex flex-wrap items-center justify-between gap-3 rounded-[22px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4"
|
||||||
onBlur={() => setEditingRoleId(null)}
|
|
||||||
autoFocus
|
|
||||||
className="text-xs! py-1!"
|
|
||||||
>
|
>
|
||||||
{ROLES.map((r) => <option key={r} value={r}>{ROLE_LABELS[r] || r}</option>)}
|
<div className="min-w-0">
|
||||||
</Select>
|
<div className="font-medium truncate">{user.name || '—'}</div>
|
||||||
) : (
|
<div className="text-sm text-[var(--color-text-muted)] truncate">{user.email}</div>
|
||||||
<span onClick={() => setEditingRoleId(user.id)} style={{ cursor: 'pointer' }}>
|
<div className="text-sm text-[var(--color-text-muted)]">{fmtDate(user.created_at)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 flex-wrap">
|
||||||
<Badge tone={ROLE_TONES[rn] || 'neutral'}>{ROLE_LABELS[rn] || rn}</Badge>
|
<Badge tone={ROLE_TONES[rn] || 'neutral'}>{ROLE_LABELS[rn] || rn}</Badge>
|
||||||
</span>
|
<div className="flex gap-2">
|
||||||
)}
|
<button
|
||||||
|
onClick={() => startEdit(user)}
|
||||||
<span style={{ fontSize: '0.8rem', color: 'var(--color-text-muted, #94a3b8)' }}>{fmtDate(user.created_at)}</span>
|
className="rounded-lg border border-[var(--color-accent)] px-3 py-1.5 text-xs font-semibold text-[var(--color-accent)] hover:bg-[var(--color-accent-soft)] transition"
|
||||||
|
>Изменить</button>
|
||||||
<div style={{ display: 'flex', gap: '0.4rem' }}>
|
|
||||||
{deleteConfirmId === user.id ? (
|
{deleteConfirmId === user.id ? (
|
||||||
<>
|
<>
|
||||||
<button onClick={() => handleDeleteUser(user.id)} style={{
|
<button onClick={() => handleDeleteUser(user.id)} className="rounded-lg bg-[var(--color-danger)] px-3 py-1.5 text-xs font-semibold text-white transition">Да</button>
|
||||||
background: 'var(--color-danger, #ef4444)', color: '#fff', border: 'none',
|
<button onClick={() => setDeleteConfirmId(null)} className="rounded-lg border border-[var(--color-border)] px-3 py-1.5 text-xs text-[var(--color-text-muted)] transition">Нет</button>
|
||||||
borderRadius: '8px', padding: '0.25rem 0.5rem', cursor: 'pointer', fontSize: '0.8rem',
|
|
||||||
}}>Да</button>
|
|
||||||
<button onClick={() => setDeleteConfirmId(null)} style={{
|
|
||||||
background: 'transparent', color: 'var(--color-text-muted, #94a3b8)', border: '1px solid var(--color-border, #334155)',
|
|
||||||
borderRadius: '8px', padding: '0.25rem 0.5rem', cursor: 'pointer', fontSize: '0.8rem',
|
|
||||||
}}>Нет</button>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={() => setDeleteConfirmId(user.id)} style={{
|
<button
|
||||||
background: 'transparent', color: 'var(--color-danger, #ef4444)', border: '1px solid var(--color-danger, #ef4444)',
|
onClick={() => setDeleteConfirmId(user.id)}
|
||||||
borderRadius: '8px', padding: '0.25rem 0.5rem', cursor: 'pointer', fontSize: '0.8rem',
|
className="rounded-lg border border-[var(--color-danger)] px-3 py-1.5 text-xs font-semibold text-[var(--color-danger)] hover:bg-[rgba(201,61,61,0.08)] transition"
|
||||||
}}>Удалить</button>
|
>Удалить</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue