Files
router-lists-ui/frontend/src/components/PurposeSelectInput.jsx
T

151 lines
4.3 KiB
React

import { useMemo, useRef, useState, useEffect } from 'react';
import {
IconRoute,
IconTopologyStar3,
IconActivity,
IconWorld,
IconShield,
IconDatabaseExport,
IconDots,
} from '@tabler/icons-react';
const PURPOSES = [
{
value: 'relay',
label: 'Relay VPS',
description: 'Трафик/ретрансляция',
icon: IconRoute,
},
{
value: 'bgp',
label: 'BGP сервер',
description: 'Маршрутизация и объявления префиксов',
icon: IconTopologyStar3,
},
{
value: 'monitoring',
label: 'Мониторинг',
description: 'Сбор метрик и алертов',
icon: IconActivity,
},
{
value: 'dns',
label: 'DNS сервер',
description: 'Резолвинг и обслуживание DNS-зон',
icon: IconWorld,
},
{
value: 'proxy',
label: 'Прокси сервер',
description: 'Проксирование HTTP(S)/SOCKS и т.п.',
icon: IconShield,
},
{
value: 'backup',
label: 'Backup сервер',
description: 'Резервные копии и архивы',
icon: IconDatabaseExport,
},
{
value: 'other',
label: 'Другое',
description: 'Свободное назначение',
icon: IconDots,
},
];
function PurposeSelectInput({ value, onChange, placeholder = 'Выберите назначение' }) {
const containerRef = useRef(null);
const [open, setOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const selected = useMemo(
() => PURPOSES.find((p) => p.value === value) || null,
[value]
);
useEffect(() => {
const handler = (e) => {
if (!containerRef.current) return;
if (!containerRef.current.contains(e.target)) setOpen(false);
};
document.addEventListener('click', handler);
return () => document.removeEventListener('click', handler);
}, []);
const handleSelect = (p) => {
onChange(p ? p.value : '');
setOpen(false);
};
return (
<div ref={containerRef} className="position-relative" style={{ width: '100%' }}>
<button
type="button"
className="form-select text-start d-flex align-items-center"
onClick={() => setOpen((prev) => !prev)}
>
{selected ? (
<span className="d-flex align-items-center">
<selected.icon size={18} className="me-2 text-primary" />
<span>{selected.label}</span>
</span>
) : (
<span className="text-muted">{placeholder}</span>
)}
</button>
{open && (
<div
className="dropdown-menu show"
style={{ display: 'block', width: '100%', maxHeight: 280, overflowY: 'auto' }}
>
<button
type="button"
className={`dropdown-item${activeIndex === -1 && !value ? ' active' : ''}`}
onMouseDown={(e) => {
e.preventDefault();
handleSelect(null);
}}
onMouseEnter={() => setActiveIndex(-1)}
>
<div className="text-muted small">Без назначения</div>
</button>
{PURPOSES.map((p, idx) => {
const Icon = p.icon;
return (
<button
type="button"
key={p.value}
className={`dropdown-item${
p.value === value || idx === activeIndex ? ' active' : ''
}`}
onMouseDown={(e) => {
e.preventDefault();
handleSelect(p);
}}
onMouseEnter={() => setActiveIndex(idx)}
>
<div className="d-flex align-items-start">
<span className="avatar me-2 bg-blue-lt text-blue border-0" style={{ width: 24, height: 24 }}>
<Icon size={16} />
</span>
<div className="flex-fill text-start">
<div className="fw-medium">{p.label}</div>
<div className="text-muted small text-truncate">{p.description}</div>
</div>
</div>
</button>
);
})}
</div>
)}
</div>
);
}
export { PURPOSES };
export default PurposeSelectInput;