feat: agent status badge + auto workstation detection in BookingDetailPanel

- Check localhost:9000 on panel open via getIdentity()
- If local agent is running → use its workstation_id directly, no picker
- If agent is offline → show subtle badge 'Агент не запущен · ключи недоступны' under tabs
- If agent offline but multiple server-side encoders online → still show picker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 18:04:26 +03:00
parent 1c1c88a37f
commit 4b1b7f4be8

View File

@@ -12,6 +12,7 @@ import {
} from '../../lib/utils'
import type { Booking, Room } from '../../types'
import { api, type BookingGuest, type BookingGuestPayload, type GuestApiType } from '../../lib/api'
import { getIdentity, type AgentIdentity } from '../../lib/agent'
const fmtDate = (iso: string) =>
new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long' }).format(new Date(iso))
@@ -146,6 +147,12 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
setEarlyOpen(false)
}, [booking.id])
// Проверяем локальный агент (localhost:9000) при открытии панели
useEffect(() => {
setAgentIdentity('loading')
getIdentity().then(identity => setAgentIdentity(identity))
}, [booking.id])
// Parse ФИО string into form fields
const parseFio = (val: string) => {
const parts = val.trim().split(/\s+/)
@@ -306,6 +313,7 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
const [cardEncoders, setCardEncoders] = useState<Array<{ id: string; name: string }>>([])
const [cardEncoderPickerOpen, setCardEncoderPickerOpen] = useState(false)
const [selectedEncoderId, setSelectedEncoderId] = useState('')
const [agentIdentity, setAgentIdentity] = useState<AgentIdentity | null | 'loading'>('loading')
const freeRoomsForRelocate = (rooms ?? []).filter(r =>
r.id !== booking.roomId &&
@@ -400,7 +408,13 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
const handleIssueCard = async () => {
if (!slug || cardIssuing) return
// Load available encoders
// Если локальный агент запущен — используем его рабочее место напрямую
if (agentIdentity && agentIdentity !== 'loading' && agentIdentity.workstation_id) {
await doIssueCard(agentIdentity.workstation_id)
return
}
// Агент не запущен — ищем онлайн-рабочие места с энкодером через сервер
let encoders: Array<{ id: string; name: string }> = []
try {
const wsList = await api.workstations.list(slug)
@@ -415,12 +429,11 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
}
if (encoders.length === 1) {
// Auto-issue
await doIssueCard(encoders[0].id)
return
}
// Multiple encoders — show picker
// Несколько энкодеров — показываем выбор
setCardEncoders(encoders)
setSelectedEncoderId(encoders[0].id)
setCardEncoderPickerOpen(true)
@@ -552,6 +565,16 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
</button>
))}
</div>
{/* Статус агента — показываем только если агент не запущен */}
{agentIdentity === null && (
<div className="flex items-center gap-1.5 mt-2 px-1">
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-slate-500 shrink-0" />
<span className="text-[11px] text-slate-400 dark:text-slate-500">
Агент не запущен · ключи недоступны
</span>
</div>
)}
</div>
{/* Tab content */}