Files
invyone/frontend/components/control/ControlToolbar.tsx
T
2026-04-10 13:33:37 +09:00

129 lines
4.9 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import { Eye, Wrench, Save, FolderOpen } from 'lucide-react';
import { useControlMode } from './hooks/useControlMode';
import { getBusinessRuleList, getBusinessRuleInfo, insertBusinessRule, updateBusinessRule } from '@/lib/api/businessRule';
import { toast } from 'sonner';
interface ControlToolbarProps {
dashboardId: string;
}
export function ControlToolbar({ dashboardId }: ControlToolbarProps) {
const { mode, setMode, ruleNodes, ruleConnections, activeRuleId, setActiveRuleId, setRuleNodes, setRuleConnections } = useControlMode();
const [ruleList, setRuleList] = useState<Record<string, any>[]>([]);
const [showRuleList, setShowRuleList] = useState(false);
// ★ 편집 모드 진입 시 기존 규칙 목록 로드
useEffect(() => {
if (mode !== 'edit') return;
getBusinessRuleList(dashboardId)
.then((res) => setRuleList(res?.list ?? res?.data ?? []))
.catch(() => setRuleList([]));
}, [mode, dashboardId]);
// ★ 기존 규칙 로드 → 편집 상태 복원
const handleLoadRule = useCallback(async (ruleId: string) => {
try {
const detail = await getBusinessRuleInfo(ruleId);
if (!detail) { toast.error('규칙을 찾을 수 없습니다'); return; }
setRuleNodes(detail.nodes ?? []);
setRuleConnections(detail.connections ?? []);
setActiveRuleId(ruleId);
setShowRuleList(false);
toast.success(`"${detail.name ?? ruleId}" 로드됨`);
} catch {
toast.error('규칙 로드 실패');
}
}, [setRuleNodes, setRuleConnections, setActiveRuleId]);
const handleSave = async () => {
try {
const data = {
name: `규칙 ${new Date().toLocaleString('ko-KR')}`,
nodes: ruleNodes,
connections: ruleConnections,
};
if (activeRuleId) {
await updateBusinessRule(activeRuleId, data);
toast.success('규칙 저장됨');
} else {
const result = await insertBusinessRule(dashboardId, data);
if (result?.rule_id) setActiveRuleId(result.rule_id);
toast.success('규칙 생성됨');
}
} catch {
toast.error('저장 실패');
}
};
return (
<div className="ctrl-toolbar">
<span style={{ fontWeight: 700, color: 'var(--ctrl-cyan)', marginRight: '.5rem' }}> </span>
<div className="ctrl-toolbar-mode">
<button
className={`ctrl-mode-btn${mode === 'view' ? ' on' : ''}`}
onClick={() => setMode('view')}
>
<Eye size={12} style={{ marginRight: 3 }} />
</button>
<button
className={`ctrl-mode-btn${mode === 'edit' ? ' on' : ''}`}
onClick={() => setMode('edit')}
>
<Wrench size={12} style={{ marginRight: 3 }} />
</button>
</div>
{mode === 'edit' && (
<div style={{ marginLeft: 'auto', display: 'flex', gap: '.3rem', position: 'relative' }}>
{/* ★ 기존 규칙 로드 버튼 */}
<button className="ctrl-mode-btn" onClick={() => setShowRuleList(!showRuleList)}>
<FolderOpen size={12} style={{ marginRight: 3 }} />
{ruleList.length > 0 ? ` (${ruleList.length})` : ''}
</button>
{/* ★ 규칙 목록 드롭다운 */}
{showRuleList && ruleList.length > 0 && (
<div style={{
position: 'absolute', top: '100%', right: 0, marginTop: 4,
background: 'var(--ctrl-glass-strong, rgba(17,16,42,.65))',
border: '1px solid var(--ctrl-glass-border, rgba(162,155,254,.12))',
borderRadius: 8, padding: '.3rem', minWidth: 200, zIndex: 100,
backdropFilter: 'blur(20px) saturate(1.4)',
}}>
{ruleList.map((rule) => {
const id = rule.rule_id ?? rule.RULE_ID;
const name = rule.name ?? rule.NAME ?? id;
const isActive = id === activeRuleId;
return (
<button
key={id}
onClick={() => handleLoadRule(id)}
style={{
display: 'block', width: '100%', textAlign: 'left',
padding: '.3rem .5rem', borderRadius: 6, border: 'none',
background: isActive ? 'rgba(0,206,201,.12)' : 'transparent',
color: isActive ? 'var(--ctrl-cyan)' : 'var(--v5-text-sec)',
fontSize: '.55rem', cursor: 'pointer',
}}
>
{name}
</button>
);
})}
</div>
)}
{ruleNodes.length > 0 && (
<button className="ctrl-mode-btn" onClick={handleSave}>
<Save size={12} style={{ marginRight: 3 }} />
</button>
)}
</div>
)}
</div>
);
}