121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { ConnectionStatus } from '../types';
|
|
import { Wifi, WifiOff, Activity } from 'lucide-react';
|
|
|
|
interface Props {
|
|
status: ConnectionStatus;
|
|
onConnect: () => void;
|
|
onDisconnect: () => void;
|
|
baudRate: number;
|
|
setBaudRate: (rate: number) => void;
|
|
address: number;
|
|
setAddress: (addr: number) => void;
|
|
}
|
|
|
|
export const ConnectionPanel: React.FC<Props> = ({
|
|
status,
|
|
onConnect,
|
|
onDisconnect,
|
|
baudRate,
|
|
setBaudRate,
|
|
address,
|
|
setAddress
|
|
}) => {
|
|
const isConnected = status === ConnectionStatus.CONNECTED;
|
|
|
|
// Local state for address input to allow smooth typing without auto-formatting interference
|
|
const [localAddress, setLocalAddress] = useState(address.toString(16).toUpperCase().padStart(2, '0'));
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
|
|
// Sync from props only when not editing
|
|
useEffect(() => {
|
|
if (!isFocused) {
|
|
setLocalAddress(address.toString(16).toUpperCase().padStart(2, '0'));
|
|
}
|
|
}, [address, isFocused]);
|
|
|
|
const handleAddressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const val = e.target.value.toUpperCase();
|
|
// Validate Hex and Length
|
|
if (/^[0-9A-F]*$/.test(val) && val.length <= 2) {
|
|
setLocalAddress(val);
|
|
// Update parent state if valid
|
|
if (val !== '') {
|
|
setAddress(parseInt(val, 16));
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
setIsFocused(false);
|
|
// On blur, ensure the display matches the formatted prop value (pads single digits, etc.)
|
|
setLocalAddress(address.toString(16).toUpperCase().padStart(2, '0'));
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white p-4 rounded-xl shadow-sm border border-slate-200">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
{isConnected ? <Wifi className="text-emerald-500" /> : <WifiOff className="text-slate-400" />}
|
|
<h2 className="text-lg font-semibold text-slate-800">Connection</h2>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-600 mb-1">Device Model</label>
|
|
<select
|
|
disabled={isConnected}
|
|
className="w-full bg-slate-50 border border-slate-300 text-slate-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 disabled:opacity-50"
|
|
>
|
|
<option value="CR100">현승코리아 CR-100</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-600 mb-1">Baud Rate</label>
|
|
<select
|
|
disabled={isConnected}
|
|
value={baudRate}
|
|
onChange={(e) => setBaudRate(Number(e.target.value))}
|
|
className="w-full bg-slate-50 border border-slate-300 text-slate-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 disabled:opacity-50"
|
|
>
|
|
<option value={9600}>9600</option>
|
|
<option value={19200}>19200</option>
|
|
<option value={38400}>38400</option>
|
|
<option value={57600}>57600 (Default)</option>
|
|
<option value={115200}>115200</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-600 mb-1">Reader Address (Hex)</label>
|
|
<input
|
|
type="text"
|
|
value={localAddress}
|
|
onFocus={() => setIsFocused(true)}
|
|
onBlur={handleBlur}
|
|
onChange={handleAddressChange}
|
|
className="w-full bg-slate-50 border border-slate-300 text-slate-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 font-mono"
|
|
maxLength={2}
|
|
placeholder="FF"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
onClick={isConnected ? onDisconnect : onConnect}
|
|
className={`w-full flex items-center justify-center gap-2 font-medium rounded-lg text-sm px-5 py-2.5 transition-colors ${
|
|
isConnected
|
|
? 'bg-red-50 text-red-600 hover:bg-red-100 border border-red-200'
|
|
: 'bg-blue-600 text-white hover:bg-blue-700 shadow-md shadow-blue-500/20'
|
|
}`}
|
|
>
|
|
{isConnected ? 'Disconnect' : 'Connect Serial'}
|
|
{status === ConnectionStatus.CONNECTING && <Activity className="animate-spin h-4 w-4" />}
|
|
</button>
|
|
|
|
<div className="text-xs text-slate-400 text-center">
|
|
{status}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |