From 4b1b7f4be8e76e648569d35352cba4f94c56d21b Mon Sep 17 00:00:00 2001 From: HotelSync Date: Wed, 1 Apr 2026 18:04:26 +0300 Subject: [PATCH] feat: agent status badge + auto workstation detection in BookingDetailPanel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../bookings/BookingDetailPanel.tsx | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/bookings/BookingDetailPanel.tsx b/src/components/bookings/BookingDetailPanel.tsx index 37aca99..b5124dc 100644 --- a/src/components/bookings/BookingDetailPanel.tsx +++ b/src/components/bookings/BookingDetailPanel.tsx @@ -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>([]) const [cardEncoderPickerOpen, setCardEncoderPickerOpen] = useState(false) const [selectedEncoderId, setSelectedEncoderId] = useState('') + const [agentIdentity, setAgentIdentity] = useState('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 ))} + + {/* Статус агента — показываем только если агент не запущен */} + {agentIdentity === null && ( +
+ + + Агент не запущен · ключи недоступны + +
+ )} {/* Tab content */}