feat: Equipment page — KKT hides connection, Windows printer, COM retry; agent update banner
- EquipmentPage: KKT hides USB/COM/Network buttons (DTO handles connection) - EquipmentPage: Printer adds Windows connection type — lists installed printers from agent - EquipmentPage: COM port section has retry button (↺) - api.ts: add listPrinters, extend connection type - workstations.ts: add /printers endpoint (list_printers command) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -192,9 +192,10 @@ function DeviceForm({
|
||||
const handleTypeChange = (t: 'kkt' | 'printer' | 'netup') => {
|
||||
setType(t)
|
||||
if (t === 'netup') setConnection('network')
|
||||
if (t === 'kkt') setConnection('com')
|
||||
}
|
||||
const [name, setName] = useState(initial?.name ?? '')
|
||||
const [connection, setConnection] = useState<'usb' | 'network' | 'com'>(initial?.connection ?? 'network')
|
||||
const [connection, setConnection] = useState<'usb' | 'network' | 'com' | 'windows'>(initial?.connection ?? 'network')
|
||||
const [networkHost, setNetworkHost] = useState(initial?.networkHost ?? '')
|
||||
const [networkPort, setNetworkPort] = useState(String(initial?.networkPort ?? 9100))
|
||||
const [comPort, setComPort] = useState(initial?.comPort ?? '')
|
||||
@@ -205,33 +206,70 @@ function DeviceForm({
|
||||
const [dtoPass, setDtoPass] = useState(String(initial?.config?.dto_pass ?? '30'))
|
||||
const [dtoDeviceId, setDtoDeviceId] = useState(String(initial?.config?.dto_device_id ?? ''))
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [availablePorts, setAvailablePorts] = useState<{ port: string; description?: string }[] | null>(null)
|
||||
const [portsLoading, setPortsLoading] = useState(false)
|
||||
const [availablePorts, setAvailablePorts] = useState<{ port: string; description?: string }[] | null>(null)
|
||||
const [portsLoading, setPortsLoading] = useState(false)
|
||||
const [availablePrinters, setAvailablePrinters] = useState<{ name: string; isDefault: boolean }[] | null>(null)
|
||||
const [printersLoading, setPrintersLoading] = useState(false)
|
||||
const [windowsPrinter, setWindowsPrinter] = useState(String(initial?.config?.windows_printer ?? ''))
|
||||
|
||||
// Загружаем порты когда выбирается COM
|
||||
useEffect(() => {
|
||||
if (connection !== 'com' || !wsOnline || availablePorts !== null) return
|
||||
const loadPorts = () => {
|
||||
if (!wsOnline) return
|
||||
setAvailablePorts(null)
|
||||
setPortsLoading(true)
|
||||
api.workstations.listPorts(slug, wsId)
|
||||
.then(r => setAvailablePorts(r.ports))
|
||||
.catch(() => setAvailablePorts([]))
|
||||
.finally(() => setPortsLoading(false))
|
||||
}, [connection, wsOnline, slug, wsId, availablePorts])
|
||||
}
|
||||
|
||||
const loadPrinters = () => {
|
||||
if (!wsOnline) return
|
||||
setAvailablePrinters(null)
|
||||
setPrintersLoading(true)
|
||||
api.workstations.listPrinters(slug, wsId)
|
||||
.then(r => setAvailablePrinters(r.printers))
|
||||
.catch(() => setAvailablePrinters([]))
|
||||
.finally(() => setPrintersLoading(false))
|
||||
}
|
||||
|
||||
// Загружаем порты когда выбирается COM
|
||||
useEffect(() => {
|
||||
if (connection !== 'com' || !wsOnline || availablePorts !== null) return
|
||||
loadPorts()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [connection, wsOnline])
|
||||
|
||||
// Загружаем принтеры когда выбирается Windows
|
||||
useEffect(() => {
|
||||
if (connection !== 'windows' || !wsOnline || availablePrinters !== null) return
|
||||
loadPrinters()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [connection, wsOnline])
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name.trim()) return
|
||||
setSaving(true)
|
||||
const configData: Record<string, unknown> = {}
|
||||
if (type === 'kkt' && fiscalMode) {
|
||||
configData.dto_user = dtoUser
|
||||
configData.dto_pass = dtoPass
|
||||
if (dtoDeviceId.trim()) configData.dto_device_id = dtoDeviceId.trim()
|
||||
}
|
||||
if (type === 'printer' && connection === 'windows' && windowsPrinter.trim()) {
|
||||
configData.windows_printer = windowsPrinter.trim()
|
||||
}
|
||||
|
||||
await onSave({
|
||||
type,
|
||||
name: name.trim(),
|
||||
connection,
|
||||
connection: type === 'kkt' ? 'com' : connection,
|
||||
networkHost: connection === 'network' ? networkHost.trim() : undefined,
|
||||
networkPort: connection === 'network' ? Number(networkPort) : undefined,
|
||||
comPort: connection === 'com' ? comPort.trim() : undefined,
|
||||
purpose: purpose as WorkstationDevice['purpose'],
|
||||
fiscalMode: type === 'kkt' ? fiscalMode : undefined,
|
||||
dtoPort: type === 'kkt' && fiscalMode ? Number(dtoPort) : undefined,
|
||||
config: type === 'kkt' && fiscalMode ? { dto_user: dtoUser, dto_pass: dtoPass, dto_device_id: dtoDeviceId.trim() || undefined } : undefined,
|
||||
config: Object.keys(configData).length ? configData : undefined,
|
||||
})
|
||||
setSaving(false)
|
||||
}
|
||||
@@ -354,14 +392,15 @@ function DeviceForm({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{type !== 'netup' && (
|
||||
{type === 'printer' && (
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-500 mb-1">Подключение</label>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{([
|
||||
{ id: 'network', icon: <Network size={14} />, label: 'Сеть (TCP)' },
|
||||
{ id: 'windows', icon: <Monitor size={14} />, label: 'Windows принтер' },
|
||||
{ id: 'com', icon: <Cable size={14} />, label: 'COM-порт' },
|
||||
{ id: 'usb', icon: <Usb size={14} />, label: 'USB' },
|
||||
{ id: 'network', icon: <Network size={14} />, label: 'Сеть (TCP)' },
|
||||
{ id: 'com', icon: <Cable size={14} />, label: 'COM-порт' },
|
||||
] as const).map(c => (
|
||||
<button
|
||||
key={c.id}
|
||||
@@ -403,11 +442,57 @@ function DeviceForm({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{connection === 'windows' && (
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-500 mb-1 flex items-center gap-2">
|
||||
Принтер Windows
|
||||
{printersLoading && <span className="text-slate-400 font-normal">загрузка...</span>}
|
||||
{wsOnline && !printersLoading && (
|
||||
<button type="button" onClick={loadPrinters} className="text-brand-500 hover:text-brand-700 font-normal">
|
||||
<RefreshCw size={11} />
|
||||
</button>
|
||||
)}
|
||||
</label>
|
||||
{wsOnline && availablePrinters && availablePrinters.length > 0 ? (
|
||||
<select
|
||||
value={windowsPrinter}
|
||||
onChange={e => setWindowsPrinter(e.target.value)}
|
||||
className="w-full input text-sm"
|
||||
>
|
||||
<option value="">— выберите принтер —</option>
|
||||
{availablePrinters.map(p => (
|
||||
<option key={p.name} value={p.name}>
|
||||
{p.name}{p.isDefault ? ' (по умолчанию)' : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
value={windowsPrinter}
|
||||
onChange={e => setWindowsPrinter(e.target.value)}
|
||||
className="w-full input text-sm"
|
||||
placeholder="Microsoft Print to PDF"
|
||||
/>
|
||||
)}
|
||||
{wsOnline && availablePrinters !== null && availablePrinters.length === 0 && (
|
||||
<p className="text-xs text-amber-500 mt-1">Принтеры не найдены. Убедитесь что принтер установлен в Windows.</p>
|
||||
)}
|
||||
{!wsOnline && (
|
||||
<p className="text-xs text-slate-400 mt-1">Терминал офлайн — введите имя принтера вручную</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{connection === 'com' && (
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-500 mb-1">
|
||||
<label className="block text-xs font-medium text-slate-500 mb-1 flex items-center gap-2">
|
||||
COM-порт
|
||||
{portsLoading && <span className="ml-2 text-slate-400 font-normal">загрузка...</span>}
|
||||
{portsLoading && <span className="text-slate-400 font-normal">загрузка...</span>}
|
||||
{wsOnline && !portsLoading && (
|
||||
<button type="button" onClick={loadPorts} className="text-brand-500 hover:text-brand-700 font-normal">
|
||||
<RefreshCw size={11} />
|
||||
</button>
|
||||
)}
|
||||
</label>
|
||||
{wsOnline && availablePorts && availablePorts.length > 0 ? (
|
||||
<select
|
||||
@@ -431,7 +516,7 @@ function DeviceForm({
|
||||
/>
|
||||
)}
|
||||
{wsOnline && availablePorts !== null && availablePorts.length === 0 && (
|
||||
<p className="text-xs text-amber-500 mt-1">COM-порты не найдены на терминале</p>
|
||||
<p className="text-xs text-amber-500 mt-1">COM-порты не найдены — нажмите ↺ для повтора</p>
|
||||
)}
|
||||
{!wsOnline && (
|
||||
<p className="text-xs text-slate-400 mt-1">Терминал офлайн — введите порт вручную (COM1, COM3…)</p>
|
||||
|
||||
Reference in New Issue
Block a user