diff --git a/src/pages/stock/screener/Screener.css b/src/pages/stock/screener/Screener.css index 231b358..43e2617 100644 --- a/src/pages/stock/screener/Screener.css +++ b/src/pages/stock/screener/Screener.css @@ -49,3 +49,13 @@ margin-bottom: 16px; } .screener-card h3 { margin: 0 0 12px 0; font-size: 15px; } + +.node-card { + background: #0a0f1a; + border: 1px solid #1f2937; + border-radius: 6px; + padding: 10px; + font-size: 13px; +} +.node-card-header { font-weight: 500; margin-bottom: 6px; } +.weight-row, .param-row { display: flex; align-items: center; gap: 6px; margin-top: 6px; } diff --git a/src/pages/stock/screener/components/NodeCard.jsx b/src/pages/stock/screener/components/NodeCard.jsx new file mode 100644 index 0000000..7626285 --- /dev/null +++ b/src/pages/stock/screener/components/NodeCard.jsx @@ -0,0 +1,80 @@ +import React from 'react'; + +export default function NodeCard({ meta, weight, params, onWeightChange, onParamsChange }) { + const enabled = (weight ?? 0) > 0; + + return ( +
+
+ +
+
+
+ 가중치 + onWeightChange(parseFloat(e.target.value))} + style={{ flex: 1 }} + /> + {(weight ?? 0).toFixed(1)} +
+ {Object.entries(meta.param_schema?.properties || {}).map(([key, prop]) => ( + onParamsChange({ ...params, [key]: v })} + /> + ))} +
+
+ ); +} + +function ParamRow({ paramKey, prop, value, disabled, onChange }) { + const type = prop.type; + if (type === 'integer' || type === 'number') { + return ( +
+ {paramKey} + onChange(type === 'integer' ? parseInt(e.target.value, 10) : parseFloat(e.target.value))} + style={{ width: 80 }} + /> +
+ ); + } + if (type === 'boolean') { + return ( +
+ +
+ ); + } + // object/array는 MVP에서 read-only JSON 표시 (RsRating의 weights 등) + return ( +
+ {paramKey}: {JSON.stringify(value)} +
+ ); +} diff --git a/src/pages/stock/screener/components/NodePanel.jsx b/src/pages/stock/screener/components/NodePanel.jsx index bdfac8b..6953e26 100644 --- a/src/pages/stock/screener/components/NodePanel.jsx +++ b/src/pages/stock/screener/components/NodePanel.jsx @@ -1,3 +1,21 @@ +import NodeCard from './NodeCard'; + export default function NodePanel({ meta, weights, params, onWeights, onParams }) { - return

점수 노드 ({meta.length})

TODO: 노드별 카드 (Task 4.4)

; + return ( +
+

점수 노드 ({meta.length})

+
+ {meta.map((m) => ( + onWeights({ ...weights, [m.name]: w })} + onParamsChange={(p) => onParams({ ...params, [m.name]: p })} + /> + ))} +
+
+ ); }