Files
WebUITest-RealProjecT/FrontEnd/components/SettingsModal.tsx
2025-11-25 20:14:41 +09:00

167 lines
9.1 KiB
TypeScript

import React from 'react';
import { Settings, RotateCw, ChevronDown, ChevronRight } from 'lucide-react';
import { ConfigItem } from '../types';
import { comms } from '../communication';
interface SettingsModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (config: ConfigItem[]) => void;
}
const TechButton = ({ children, onClick, active = false, variant = 'blue', className = '' }: any) => {
const colors: any = {
blue: 'from-blue-600 to-cyan-600 hover:shadow-glow-blue border-cyan-400/30',
red: 'from-red-600 to-pink-600 hover:shadow-glow-red border-red-400/30',
amber: 'from-amber-500 to-orange-600 hover:shadow-orange-500/50 border-orange-400/30',
green: 'from-emerald-500 to-green-600 hover:shadow-green-500/50 border-green-400/30'
};
return (
<button
onClick={onClick}
className={`
relative px-4 py-2 font-tech font-bold tracking-wider uppercase transition-all duration-300
clip-tech border-b-2 border-r-2
${active ? `bg-gradient-to-r ${colors[variant]} text-white` : 'bg-slate-800/50 text-slate-400 hover:text-white hover:bg-slate-700/50 border-slate-600'}
${className}
`}
>
{active && <div className="absolute inset-0 bg-white/20 animate-pulse pointer-events-none"></div>}
{children}
</button>
);
};
export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, onSave }) => {
const [localConfig, setLocalConfig] = React.useState<ConfigItem[]>([]);
const [expandedGroups, setExpandedGroups] = React.useState<Set<string>>(new Set());
const [isRefreshing, setIsRefreshing] = React.useState(false);
// Fetch config data when modal opens
React.useEffect(() => {
if (isOpen) {
const fetchConfig = async () => {
setIsRefreshing(true);
try {
const configStr = await comms.getConfig();
const config: ConfigItem[] = JSON.parse(configStr);
setLocalConfig(config);
// Auto-expand all groups initially
const groups = new Set<string>(config.map(c => c.Group));
setExpandedGroups(groups);
} catch (e) {
console.error('Failed to fetch config:', e);
}
setIsRefreshing(false);
};
fetchConfig();
}
}, [isOpen]);
const handleChange = (idx: number, newValue: string) => {
setLocalConfig(prev => {
const next = [...prev];
next[idx] = { ...next[idx], Value: newValue };
return next;
});
};
const toggleGroup = (group: string) => {
setExpandedGroups(prev => {
const next = new Set(prev);
if (next.has(group)) next.delete(group);
else next.add(group);
return next;
});
};
// Group items by category
const groupedConfig = React.useMemo(() => {
const groups: { [key: string]: { item: ConfigItem, originalIdx: number }[] } = {};
localConfig.forEach((item, idx) => {
if (!groups[item.Group]) groups[item.Group] = [];
groups[item.Group].push({ item, originalIdx: idx });
});
return groups;
}, [localConfig]);
if (!isOpen) return null;
return (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="w-[900px] glass-holo p-8 border border-neon-blue shadow-glow-blue relative flex flex-col max-h-[90vh]">
<button onClick={onClose} className="absolute top-4 right-4 text-slate-400 hover:text-white"></button>
<h2 className="text-2xl font-tech font-bold text-neon-blue mb-8 border-b border-white/10 pb-4 flex items-center gap-3 flex-none">
<Settings className="animate-spin-slow" /> SYSTEM CONFIGURATION
</h2>
{isRefreshing ? (
<div className="h-64 flex flex-col items-center justify-center gap-4 animate-pulse flex-1">
<RotateCw className="w-12 h-12 text-neon-blue animate-spin" />
<div className="text-xl font-tech text-neon-blue tracking-widest">FETCHING CONFIGURATION...</div>
</div>
) : (
<div className="flex-1 overflow-y-auto custom-scrollbar pr-2 mb-8">
<div className="space-y-6">
{Object.entries(groupedConfig).map(([groupName, items]) => (
<div key={groupName} className="border border-white/10 bg-black/20">
<button
onClick={() => toggleGroup(groupName)}
className="w-full flex items-center gap-2 p-3 bg-white/5 hover:bg-white/10 transition-colors text-left"
>
{expandedGroups.has(groupName) ? <ChevronDown className="w-4 h-4 text-neon-blue" /> : <ChevronRight className="w-4 h-4 text-slate-400" />}
<span className="font-tech font-bold text-lg text-white tracking-wider">{groupName}</span>
<span className="text-xs text-slate-500 ml-auto">{items.length} ITEMS</span>
</button>
{expandedGroups.has(groupName) && (
<div className="p-4 space-y-4">
{items.map(({ item, originalIdx }) => (
<div key={originalIdx} className="grid grid-cols-[250px_1fr] gap-6 items-start group">
<div>
<div className="text-sm font-bold text-neon-blue mb-1">{item.Key}</div>
<div className="text-xs text-slate-400 leading-tight">{item.Description}</div>
</div>
<div>
{item.Type === 'Boolean' ? (
<div className="flex items-center gap-3 h-full">
<button
onClick={() => handleChange(originalIdx, item.Value === 'true' ? 'false' : 'true')}
className={`w-12 h-6 rounded-full p-1 transition-colors ${item.Value === 'true' ? 'bg-neon-green' : 'bg-slate-700'}`}
>
<div className={`w-4 h-4 rounded-full bg-white shadow transition-transform ${item.Value === 'true' ? 'translate-x-6' : 'translate-x-0'}`} />
</button>
<span className={`font-mono text-sm font-bold ${item.Value === 'true' ? 'text-neon-green' : 'text-slate-400'}`}>
{item.Value.toUpperCase()}
</span>
</div>
) : (
<input
type={item.Type === 'Number' ? 'number' : 'text'}
value={item.Value}
onChange={(e) => handleChange(originalIdx, e.target.value)}
className="w-full bg-black/50 border border-slate-700 text-white font-mono text-sm px-3 py-2 focus:border-neon-blue focus:outline-none transition-colors hover:border-slate-500"
/>
)}
</div>
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
)}
<div className="flex justify-end gap-4 flex-none pt-4 border-t border-white/10">
<TechButton onClick={onClose}>CANCEL</TechButton>
<TechButton variant="blue" active onClick={() => { onSave(localConfig); onClose(); }}>SAVE CONFIG</TechButton>
</div>
</div>
</div>
);
};