import React, { useState, useMemo, useEffect } from 'react'; import { Star, Plus, Trash2, Search, X, FolderPlus, ArrowRight, LayoutGrid, Globe, Edit3, Save, GripVertical } from 'lucide-react'; import { StockItem, WatchlistGroup, MarketType } from '../types'; import { DbService } from '../services/dbService'; import { StockRow } from '../components/StockRow'; interface WatchlistManagementProps { marketMode: MarketType; stocks: StockItem[]; groups: WatchlistGroup[]; onRefresh: () => void; } const WatchlistManagement: React.FC = ({ marketMode, stocks, groups, onRefresh }) => { const [selectedGroupId, setSelectedGroupId] = useState(null); const [showAddGroupModal, setShowAddGroupModal] = useState(false); const [showRenameModal, setShowRenameModal] = useState(false); const [editingGroup, setEditingGroup] = useState(null); const [newGroupName, setNewGroupName] = useState(''); const [renameValue, setRenameValue] = useState(''); const [stockSearch, setStockSearch] = useState(''); const [draggedItemIndex, setDraggedItemIndex] = useState(null); const dbService = useMemo(() => new DbService(), []); const filteredGroups = useMemo(() => { return groups.filter(g => g.market === marketMode); }, [groups, marketMode]); useEffect(() => { if (filteredGroups.length > 0) { if (!selectedGroupId || !filteredGroups.find(g => g.id === selectedGroupId)) { setSelectedGroupId(filteredGroups[0].id); } } else { setSelectedGroupId(null); } }, [marketMode, filteredGroups, selectedGroupId]); const selectedGroup = useMemo(() => filteredGroups.find(g => g.id === selectedGroupId), [filteredGroups, selectedGroupId] ); const filteredSearchStocks = useMemo(() => { if (!stockSearch) return []; return stocks.filter(s => s.market === marketMode && (s.name.includes(stockSearch) || s.code.toLowerCase().includes(stockSearch.toLowerCase())) && !(selectedGroup?.codes.includes(s.code)) ).slice(0, 5); }, [stocks, stockSearch, marketMode, selectedGroup]); const handleAddGroup = async () => { if (!newGroupName.trim()) return; const newGroup: WatchlistGroup = { id: 'grp_' + Date.now(), name: newGroupName, codes: [], market: marketMode }; await dbService.saveWatchlistGroup(newGroup); setNewGroupName(''); setShowAddGroupModal(false); onRefresh(); setSelectedGroupId(newGroup.id); }; const handleRenameGroup = async () => { if (!editingGroup || !renameValue.trim()) return; const updated = { ...editingGroup, name: renameValue }; await dbService.updateWatchlistGroup(updated); setEditingGroup(null); setRenameValue(''); setShowRenameModal(false); onRefresh(); }; const handleDeleteGroup = async (id: string) => { if (!confirm('그룹을 삭제하시겠습니까?')) return; await dbService.deleteWatchlistGroup(id); onRefresh(); if (selectedGroupId === id) setSelectedGroupId(null); }; const handleAddStockToGroup = async (code: string) => { if (!selectedGroup) return; const updated = { ...selectedGroup, codes: [...selectedGroup.codes, code] }; await dbService.updateWatchlistGroup(updated); setStockSearch(''); onRefresh(); }; const handleRemoveStockFromGroup = async (code: string) => { if (!selectedGroup) return; const stock = stocks.find(s => s.code === code); const stockName = stock ? stock.name : code; if (!confirm(`'${stockName}' 종목을 그룹에서 삭제하시겠습니까?`)) return; const updated = { ...selectedGroup, codes: selectedGroup.codes.filter(c => c !== code) }; await dbService.updateWatchlistGroup(updated); onRefresh(); }; const onDragStart = (e: React.DragEvent, index: number) => { setDraggedItemIndex(index); e.dataTransfer.effectAllowed = 'move'; }; const onDrop = async (e: React.DragEvent, targetIndex: number) => { if (!selectedGroup || draggedItemIndex === null || draggedItemIndex === targetIndex) return; const newCodes = [...selectedGroup.codes]; const [movedItem] = newCodes.splice(draggedItemIndex, 1); newCodes.splice(targetIndex, 0, movedItem); const updated = { ...selectedGroup, codes: newCodes }; await dbService.updateWatchlistGroup(updated); setDraggedItemIndex(null); onRefresh(); }; return (

{marketMode === MarketType.DOMESTIC ? '국내' : '해외'} 관심그룹

{filteredGroups.map(group => (
setSelectedGroupId(group.id)} className={`p-4 rounded-xl border-2 transition-all cursor-pointer group flex justify-between items-center ${selectedGroupId === group.id ? 'border-blue-500 bg-blue-50/20' : 'border-transparent bg-slate-50/70 hover:bg-white hover:border-slate-200'}`}>

{group.name}

{group.codes.length} 종목

))}
{selectedGroup ? ( <>

{selectedGroup.name}

편집 모드
setStockSearch(e.target.value)} /> {filteredSearchStocks.length > 0 && (
{filteredSearchStocks.map(s => (
handleAddStockToGroup(s.code)} className="p-3 hover:bg-blue-50 cursor-pointer flex justify-between items-center border-b last:border-none border-slate-50">
{s.code.substring(0,2)}

{s.name}

))}
)}
{selectedGroup.codes.map((code, idx) => { const stock = stocks.find(s => s.code === code); if (!stock) return null; return ( {}} onToggleWatchlist={() => handleRemoveStockFromGroup(code)} isWatchlisted={true} /> ); })}
) : (

그룹을 선택하세요

)}
{/* 모달은 기존 코드 유지 (생략 가능하나 유저 요청에 따라 전체 포함) */} {showAddGroupModal && (

새 그룹 생성

setNewGroupName(e.target.value)} />
)} {showRenameModal && (

그룹 명칭 변경

setRenameValue(e.target.value)} />
)}
); }; export default WatchlistManagement;