feat(GatewayAutocomplete): add selected gateway info and free gateway warning to enhance user feedback
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 3m32s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 3m32s
This commit is contained in:
@@ -428,6 +428,37 @@ function GatewayAutocomplete({ label, value, onChange, gateways = [], required =
|
|||||||
|
|
||||||
const selectedGateway = gateways.find(g => g.name === value);
|
const selectedGateway = gateways.find(g => g.name === value);
|
||||||
|
|
||||||
|
const selectedGatewayInfo =
|
||||||
|
value && selectedGateway && !isOpen ? (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="d-flex align-items-center gap-2 p-2 bg-light rounded">
|
||||||
|
<span className="avatar avatar-xs bg-primary-lt">
|
||||||
|
{selectedGateway.country ? countryToFlag(selectedGateway.country) : <IconServer size={14} />}
|
||||||
|
</span>
|
||||||
|
<div className="flex-grow-1">
|
||||||
|
<div className="fw-bold">
|
||||||
|
<code>{selectedGateway.name}</code>
|
||||||
|
{selectedGateway.comment && <span className="text-muted ms-2">({selectedGateway.comment})</span>}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted small">
|
||||||
|
{[selectedGateway.serverDns].filter(Boolean).join(' · ')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{selectedGateway.primary && <span className="badge bg-green-lt text-green">Основной</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const freeGatewayWarning =
|
||||||
|
value && !selectedGateway && !isOpen ? (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="d-flex align-items-center gap-2 p-2 bg-warning-lt rounded">
|
||||||
|
<IconAlertTriangle size={16} className="text-warning" />
|
||||||
|
<div className="text-muted small">Gateway "{value}" — свободный ввод</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-3" ref={containerRef}>
|
<div className="mb-3" ref={containerRef}>
|
||||||
<label className={`form-label ${required ? 'required' : ''}`}>{label}</label>
|
<label className={`form-label ${required ? 'required' : ''}`}>{label}</label>
|
||||||
@@ -454,99 +485,72 @@ function GatewayAutocomplete({ label, value, onChange, gateways = [], required =
|
|||||||
|
|
||||||
{/* Карточки gateway под полем, ВСЕГДА видимы */}
|
{/* Карточки gateway под полем, ВСЕГДА видимы */}
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
{gateways.length === 0 ? (
|
{gateways.length === 0 ? (
|
||||||
<div className="text-muted small d-flex align-items-center">
|
<div className="text-muted small d-flex align-items-center">
|
||||||
<IconAlertTriangle size={14} className="me-2 text-warning" />
|
<IconAlertTriangle size={14} className="me-2 text-warning" />
|
||||||
<span>Нет доступных gateway для выбранного сервера</span>
|
<span>Нет доступных gateway для выбранного сервера</span>
|
||||||
</div>
|
</div>
|
||||||
) : filteredGateways.length === 0 ? (
|
) : filteredGateways.length === 0 ? (
|
||||||
<div className="text-muted small">
|
<div className="text-muted small">
|
||||||
Ничего не найдено. Уточните запрос по IP, имени или описанию.
|
Ничего не найдено. Уточните запрос по IP, имени или описанию.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="row row-cols-1 row-cols-md-3 g-2">
|
<div className="row row-cols-1 row-cols-md-3 g-2">
|
||||||
{filteredGateways.map((gw, idx) => {
|
{filteredGateways.map((gw, idx) => {
|
||||||
const isSelected = gw.name === value;
|
const isSelected = gw.name === value;
|
||||||
const isHighlighted = highlightedIndex === idx;
|
const isHighlighted = highlightedIndex === idx;
|
||||||
return (
|
return (
|
||||||
<div className="col" key={gw.name || idx}>
|
<div className="col" key={gw.name || idx}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-100 border-0 bg-transparent p-0 text-start"
|
className="w-100 border-0 bg-transparent p-0 text-start"
|
||||||
onClick={() => handleSelect(gw)}
|
onClick={() => handleSelect(gw)}
|
||||||
onMouseEnter={() => setHighlightedIndex(idx)}
|
onMouseEnter={() => setHighlightedIndex(idx)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'card h-100 gateway-card ' +
|
||||||
|
(isSelected ? 'bg-primary text-white border-primary shadow-sm ' : 'bg-white ') +
|
||||||
|
(!isSelected && isHighlighted ? 'border-primary ' : '')
|
||||||
|
}
|
||||||
|
style={{ borderRadius: '10px', transition: 'all 0.12s ease' }}
|
||||||
>
|
>
|
||||||
<div
|
<div className="card-body py-2 px-3">
|
||||||
className={
|
<div className="d-flex align-items-start gap-2 mb-1">
|
||||||
'card h-100 gateway-card ' +
|
<span className={`avatar avatar-sm ${isSelected ? 'bg-white text-primary' : 'bg-primary-lt'}`}>
|
||||||
(isSelected ? 'bg-primary text-white border-primary shadow-sm ' : 'bg-white ') +
|
{gw.country ? countryToFlag(gw.country) : <IconServer size={16} />}
|
||||||
(!isSelected && isHighlighted ? 'border-primary ' : '')
|
</span>
|
||||||
}
|
<div className="flex-grow-1 min-w-0">
|
||||||
style={{ borderRadius: '10px', transition: 'all 0.12s ease' }}
|
<div className="fw-bold text-truncate">
|
||||||
>
|
{gw.ip || gw.name}
|
||||||
<div className="card-body py-2 px-3">
|
|
||||||
<div className="d-flex align-items-start gap-2 mb-1">
|
|
||||||
<span className={`avatar avatar-sm ${isSelected ? 'bg-white text-primary' : 'bg-primary-lt'}`}>
|
|
||||||
{gw.country ? countryToFlag(gw.country) : <IconServer size={16} />}
|
|
||||||
</span>
|
|
||||||
<div className="flex-grow-1 min-w-0">
|
|
||||||
<div className="fw-bold text-truncate">
|
|
||||||
{gw.ip || gw.name}
|
|
||||||
</div>
|
|
||||||
<div className={`small text-truncate ${isSelected ? 'text-white-75' : 'text-muted'}`}>
|
|
||||||
{gw.comment || gw.name}
|
|
||||||
</div>
|
|
||||||
{gw.serverDns && (
|
|
||||||
<div className={`small text-truncate ${isSelected ? 'text-white-75' : 'text-muted'}`}>
|
|
||||||
{gw.serverDns}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{gw.primary && (
|
<div className={`small text-truncate ${isSelected ? 'text-white-75' : 'text-muted'}`}>
|
||||||
<span className={`badge ${isSelected ? 'bg-white text-primary' : 'bg-green-lt text-green'}`}>
|
{gw.comment || gw.name}
|
||||||
Основной
|
</div>
|
||||||
</span>
|
{gw.serverDns && (
|
||||||
|
<div className={`small text-truncate ${isSelected ? 'text-white-75' : 'text-muted'}`}>
|
||||||
|
{gw.serverDns}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{gw.primary && (
|
||||||
|
<span className={`badge ${isSelected ? 'bg-white text-primary' : 'bg-green-lt text-green'}`}>
|
||||||
|
Основной
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
);
|
</div>
|
||||||
})}
|
);
|
||||||
</div>
|
})}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
{value && selectedGateway && !isOpen && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<div className="d-flex align-items-center gap-2 p-2 bg-light rounded">
|
|
||||||
<span className="avatar avatar-xs bg-primary-lt">
|
|
||||||
{selectedGateway.country ? countryToFlag(selectedGateway.country) : <IconServer size={14} />}
|
|
||||||
</span>
|
|
||||||
<div className="flex-grow-1">
|
|
||||||
<div className="fw-bold">
|
|
||||||
<code>{selectedGateway.name}</code>
|
|
||||||
{selectedGateway.comment && <span className="text-muted ms-2">({selectedGateway.comment})</span>}
|
|
||||||
</div>
|
|
||||||
<div className="text-muted small">
|
|
||||||
{[selectedGateway.serverDns].filter(Boolean).join(' · ')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{selectedGateway.primary && <span className="badge bg-green-lt text-green">Основной</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Предупреждение если gateway не в списке */}
|
{selectedGatewayInfo}
|
||||||
{value && !selectedGateway && !isOpen && (
|
{freeGatewayWarning}
|
||||||
<div className="mt-2">
|
|
||||||
<div className="d-flex align-items-center gap-2 p-2 bg-warning-lt rounded">
|
|
||||||
<IconAlertTriangle size={16} className="text-warning" />
|
|
||||||
<div className="text-muted small">Gateway "{value}" — свободный ввод</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user