128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import React from 'react';
|
|
import type { StrategyTemplateDef } from '../../utils/strategyPresets';
|
|
import type { UserStrategyTemplate } from '../../utils/strategyTemplateStorage';
|
|
|
|
export type TemplatePaletteRow = {
|
|
key: string;
|
|
source: 'user' | 'builtin';
|
|
label: string;
|
|
description?: string;
|
|
signal: 'buy' | 'sell';
|
|
user?: UserStrategyTemplate;
|
|
builtinId?: string;
|
|
builtin?: StrategyTemplateDef;
|
|
};
|
|
|
|
interface Props {
|
|
rows: TemplatePaletteRow[];
|
|
searchQuery: string;
|
|
onSearchChange: (query: string) => void;
|
|
selectedKey: string | null;
|
|
onSelectKey: (key: string | null) => void;
|
|
onApply: (row: TemplatePaletteRow) => void;
|
|
onAdd: () => void;
|
|
onEdit: () => void;
|
|
onDelete: () => void;
|
|
}
|
|
|
|
export default function TemplatePaletteTab({
|
|
rows,
|
|
searchQuery,
|
|
onSearchChange,
|
|
selectedKey,
|
|
onSelectKey,
|
|
onApply,
|
|
onAdd,
|
|
onEdit,
|
|
onDelete,
|
|
}: Props) {
|
|
return (
|
|
<div className="se-palette-section se-palette-section--templates se-palette-section--scroll">
|
|
<div className="se-palette-toolbar">
|
|
<span className="se-palette-toolbar-title">템플릿</span>
|
|
<div className="se-palette-toolbar-actions" role="toolbar" aria-label="템플릿 관리">
|
|
<button
|
|
type="button"
|
|
className="se-palette-tool-btn"
|
|
title="현재 조건을 템플릿으로 추가"
|
|
aria-label="템플릿 추가"
|
|
onClick={onAdd}
|
|
>
|
|
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden>
|
|
<path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="se-palette-tool-btn"
|
|
title="선택 템플릿 수정"
|
|
aria-label="템플릿 수정"
|
|
disabled={!selectedKey}
|
|
onClick={onEdit}
|
|
>
|
|
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden>
|
|
<path d="M4 20h4l10-10-4-4L4 16v4z" stroke="currentColor" strokeWidth="1.75" fill="none" strokeLinejoin="round" />
|
|
<path d="M14 6l4 4" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="se-palette-tool-btn se-palette-tool-btn--danger"
|
|
title="선택 템플릿 삭제"
|
|
aria-label="템플릿 삭제"
|
|
disabled={!selectedKey}
|
|
onClick={onDelete}
|
|
>
|
|
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden>
|
|
<path d="M6 7h12M9 7V5h6v2M10 11v6M14 11v6M8 7l1 12h6l1-12" stroke="currentColor" strokeWidth="1.75" fill="none" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<input
|
|
className="se-palette-search"
|
|
placeholder="템플릿 검색 (RSI, MA, 매수…)"
|
|
value={searchQuery}
|
|
onChange={e => onSearchChange(e.target.value)}
|
|
/>
|
|
|
|
<div className="se-template-list">
|
|
{rows.length === 0 ? (
|
|
<div className="se-template-empty">검색 결과가 없습니다</div>
|
|
) : (
|
|
rows.map(row => (
|
|
<div
|
|
key={row.key}
|
|
className={`se-template-row${selectedKey === row.key ? ' se-template-row--selected' : ''}`}
|
|
onClick={() => onSelectKey(row.key)}
|
|
onDoubleClick={() => onApply(row)}
|
|
role="button"
|
|
tabIndex={0}
|
|
onKeyDown={e => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
onApply(row);
|
|
}
|
|
}}
|
|
>
|
|
<div className="se-template-item">
|
|
<span className="se-template-label">
|
|
{row.source === 'user' && <span className="se-template-user-badge">내 템플릿</span>}
|
|
{row.label}
|
|
</span>
|
|
{row.description && (
|
|
<span className="se-template-desc">{row.description}</span>
|
|
)}
|
|
<span className={`se-template-signal se-template-signal--${row.signal}`}>
|
|
{row.signal === 'buy' ? '매수' : '매도'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|