Complete legacy operator UI and playout migration

This commit is contained in:
2026-07-12 02:05:49 +09:00
parent d2e16bf0c2
commit ffb8f43c19
131 changed files with 48473 additions and 228 deletions

View File

@@ -6,6 +6,9 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
<title>MBN Stock WebView</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="manual-lists-ui.css">
<link rel="stylesheet" href="manual-financial-ui.css">
<link rel="stylesheet" href="operator-catalog-ui.css">
</head>
<body>
<div class="app-shell">
@@ -27,6 +30,7 @@
<button class="nav-item" data-market="kosdaq"><span>Q</span>코스닥</button>
<button class="nav-item" data-market="compare"><span></span>비교</button>
<button class="nav-item" data-market="theme"><span></span>테마</button>
<button class="nav-item" data-market="foreign-stock"><span></span>해외종목</button>
<button class="nav-item" data-market="expert"><span></span>전문가</button>
<button class="nav-item" data-market="halted"><span></span>정지</button>
</nav>
@@ -61,12 +65,278 @@
<div class="console-grid">
<section class="panel catalog-panel">
<div class="panel-header">
<div><p class="panel-kicker">CUT CATALOG</p><h2>그래픽 항목</h2></div>
<div><p class="panel-kicker">OPERATOR INPUT</p><h2>종목·그래픽 선택</h2></div>
<span id="catalogModeBadge" class="badge">Web UI</span>
</div>
<section id="stockWorkflow" class="stock-workflow" aria-labelledby="stockWorkflowTitle">
<div class="stock-workflow-header">
<div>
<p class="panel-kicker">LEGACY STOCK WORKFLOW</p>
<h3 id="stockWorkflowTitle">종목 검색</h3>
</div>
<div class="stock-workflow-tools">
<span id="stockSearchBadge" class="badge neutral">DB SEARCH</span>
<button id="stockWorkflowToggle" type="button" aria-expanded="true">접기</button>
</div>
</div>
<form id="stockSearchForm" class="stock-search-form" autocomplete="off">
<label for="stockSearchInput" class="visually-hidden">종목명</label>
<input id="stockSearchInput" type="search" maxlength="64" placeholder="예: 삼성" spellcheck="false">
<button id="stockSearchButton" type="submit">검색</button>
</form>
<div id="stockSearchState" class="stock-search-state" role="status" aria-live="polite">종목명을 입력하고 Enter를 누르세요.</div>
<div id="stockSearchResults" class="stock-search-results" role="listbox" aria-label="종목 검색 결과"></div>
<div id="selectedStock" class="selected-stock" hidden>
<span>선택 종목</span>
<strong id="selectedStockName"></strong>
<small id="selectedStockMeta"></small>
</div>
<div class="stock-cut-header">
<div><strong>종목용 컷</strong><small>더블클릭 또는 + 버튼으로 추가</small></div>
<label><input id="stockMa5" type="checkbox" checked>5일선</label>
<label><input id="stockMa20" type="checkbox" checked>20일선</label>
</div>
<div id="stockCutList" class="stock-cut-list" aria-label="종목용 컷 목록"></div>
<div id="manualStockActions" class="manual-stock-actions" aria-label="수동 재무 데이터"></div>
</section>
<section id="legacyFixedWorkflow" class="legacy-fixed-workflow" aria-labelledby="legacyFixedWorkflowTitle" hidden>
<div class="legacy-fixed-header">
<div>
<p class="panel-kicker">LEGACY RUNTIME CATALOG</p>
<h3 id="legacyFixedWorkflowTitle">원본 운영 항목</h3>
</div>
<div class="legacy-fixed-tools">
<span id="legacyFixedCount" class="badge neutral">0 ACTIONS</span>
<button id="legacyFixedToggle" type="button" aria-expanded="true">접기</button>
</div>
</div>
<label class="legacy-fixed-search">
<span></span>
<input id="legacyFixedSearch" type="search" maxlength="64" autocomplete="off" placeholder="원본 항목 검색">
</label>
<div id="legacyFixedState" class="legacy-fixed-state" role="status" aria-live="polite"></div>
<div id="legacyFixedSections" class="legacy-fixed-sections" aria-label="원본 운영 항목"></div>
</section>
<section id="industryWorkflow" class="industry-workflow" aria-labelledby="industryWorkflowTitle" hidden>
<div class="industry-workflow-header">
<div>
<p class="panel-kicker">LEGACY INDUSTRY WORKFLOW</p>
<h3 id="industryWorkflowTitle">업종 선택</h3>
</div>
<div class="industry-workflow-tools">
<span id="industryCount" class="badge neutral">DB</span>
<button id="industryRetry" type="button">다시 조회</button>
<button id="industryWorkflowToggle" type="button" aria-expanded="true">접기</button>
</div>
</div>
<label class="industry-search">
<span></span>
<input id="industrySearch" type="search" maxlength="64" autocomplete="off" placeholder="업종명 검색">
</label>
<div id="industryState" class="industry-state" role="status" aria-live="polite"></div>
<div id="industryResults" class="industry-results" role="listbox" aria-label="업종 목록"></div>
<div class="industry-pair" aria-label="업종 비교 선택">
<div><span>첫 번째</span><strong id="industryPrimary">선택 없음</strong></div>
<button id="industrySwap" type="button" title="두 업종 순서 교환"></button>
<div><span>두 번째</span><strong id="industrySecondary">선택 없음</strong></div>
<button id="industryClear" type="button">초기화</button>
</div>
<div id="industryCutList" class="industry-cut-list" aria-label="업종용 컷 목록"></div>
</section>
<section id="comparisonWorkflow" class="comparison-workflow" aria-labelledby="comparisonWorkflowTitle" hidden>
<div class="comparison-workflow-header">
<div>
<p class="panel-kicker">LEGACY COMPARISON WORKFLOW</p>
<h3 id="comparisonWorkflowTitle">종목 비교</h3>
</div>
<span id="comparisonPairCount" class="badge neutral">0 PAIRS</span>
</div>
<div id="comparisonSourceTabs" class="comparison-source-tabs" role="tablist" aria-label="비교 대상 종류">
<button type="button" class="active" data-comparison-source="domestic">국내·NXT</button>
<button type="button" data-comparison-source="fixed">지수·환율</button>
<button type="button" data-comparison-source="world">해외 종목</button>
</div>
<section id="comparisonDomesticPanel" class="comparison-source-panel">
<form id="comparisonDomesticSearchForm" class="comparison-search-form" autocomplete="off">
<input id="comparisonDomesticSearch" type="search" maxlength="64" placeholder="국내·NXT 종목명" aria-label="국내·NXT 종목명">
<button id="comparisonDomesticSearchButton" type="submit">검색</button>
</form>
<div id="comparisonDomesticState" class="comparison-search-state" role="status" aria-live="polite"></div>
<div id="comparisonDomesticResults" class="comparison-target-results" role="listbox" aria-label="국내·NXT 종목 검색 결과"></div>
</section>
<section id="comparisonFixedPanel" class="comparison-source-panel" hidden>
<div id="comparisonFixedTargets" class="comparison-target-results fixed" role="listbox" aria-label="고정 비교 대상"></div>
</section>
<section id="comparisonWorldPanel" class="comparison-source-panel" hidden>
<form id="comparisonWorldSearchForm" class="comparison-search-form" autocomplete="off">
<input id="comparisonWorldSearch" type="search" maxlength="64" placeholder="미국·대만 종목명" aria-label="해외 종목명">
<button id="comparisonWorldSearchButton" type="submit">검색</button>
</form>
<div id="comparisonWorldState" class="comparison-search-state" role="status" aria-live="polite"></div>
<div id="comparisonWorldResults" class="comparison-target-results" role="listbox" aria-label="해외 종목 검색 결과"></div>
</section>
<div class="comparison-draft" aria-label="비교쌍 편집">
<div><span>첫 번째</span><strong id="comparisonFirst">선택 없음</strong></div>
<button id="comparisonSwap" type="button" title="비교 대상 순서 교환"></button>
<div><span>두 번째</span><strong id="comparisonSecond">선택 없음</strong></div>
<button id="comparisonClear" type="button">초기화</button>
<button id="comparisonSavePair" class="primary" type="button">비교쌍 추가</button>
</div>
<div class="comparison-pair-header">
<div><strong>저장 비교쌍</strong><small>행을 선택한 뒤 컷을 추가합니다.</small></div>
<div>
<button id="comparisonMoveUp" type="button" title="위로"></button>
<button id="comparisonMoveDown" type="button" title="아래로"></button>
<button id="comparisonDeletePair" type="button">삭제</button>
<button id="comparisonDeleteAll" type="button">전체삭제</button>
</div>
</div>
<div id="comparisonPairList" class="comparison-pair-list" role="listbox" aria-label="저장 비교쌍"></div>
<div class="comparison-action-header">
<div><strong>비교용 컷</strong><small>원본 종목비교.ini 9개 액션</small></div>
<button id="comparisonYieldBatch" type="button">수익률 5종 일괄 추가</button>
</div>
<div id="comparisonActionList" class="comparison-action-list" aria-label="비교용 컷 목록"></div>
</section>
<section id="themeWorkflow" class="theme-workflow" aria-labelledby="themeWorkflowTitle" hidden>
<div class="theme-workflow-header">
<div>
<p class="panel-kicker">LEGACY THEME WORKFLOW</p>
<h3 id="themeWorkflowTitle">테마 선택</h3>
</div>
<span id="themeCount" class="badge neutral">READ ONLY</span>
</div>
<form id="themeSearchForm" class="theme-search-form" autocomplete="off">
<input id="themeSearch" type="search" maxlength="64" placeholder="테마명 · 빈 값은 전체" aria-label="테마명">
<select id="themeNxtSession" aria-label="NXT 세션">
<option value="preMarket">NXT 장전</option>
<option value="afterMarket">NXT 시간외</option>
</select>
<button id="themeSearchButton" type="submit">조회</button>
</form>
<div id="themeSearchState" class="theme-search-state" role="status" aria-live="polite"></div>
<div id="themeResults" class="theme-results" role="listbox" aria-label="테마 검색 결과"></div>
<div id="selectedTheme" class="selected-theme" hidden>
<div><span>선택 테마</span><strong id="selectedThemeTitle"></strong><small id="selectedThemeMeta"></small></div>
<label>정렬
<select id="themeSort">
<option value="INPUT_ORDER">입력순</option>
<option value="GAIN_DESC">상승률순</option>
<option value="GAIN_ASC">하락률순</option>
</select>
</label>
</div>
<div class="theme-preview-header">
<div><strong>테마 종목</strong><small>송출 전 read-only 미리보기</small></div>
<span id="themePreviewCount">0 ITEMS</span>
</div>
<div id="themePreviewState" class="theme-preview-state" role="status" aria-live="polite"></div>
<div id="themePreviewItems" class="theme-preview-items" aria-label="테마 종목 미리보기"></div>
<div class="theme-action-header"><strong>테마용 컷</strong><small>5·6·12개 단위 · 최대 20페이지</small></div>
<div id="themeActionList" class="theme-action-list" aria-label="테마용 컷 목록"></div>
<div class="theme-crud-notice">
<span>ThemeA · 트랜잭션 기반 추가·편집·삭제</span>
<button id="themeCatalogManageButton" type="button">테마 관리</button>
</div>
</section>
<section id="overseasWorkflow" class="overseas-workflow" aria-labelledby="overseasWorkflowTitle" hidden>
<div class="overseas-workflow-header">
<div>
<p class="panel-kicker">LEGACY FOREIGN INSTRUMENT WORKFLOW</p>
<h3 id="overseasWorkflowTitle">해외업종·해외종목</h3>
</div>
<span id="overseasCount" class="badge neutral">ORACLE</span>
</div>
<div id="overseasSourceTabs" class="overseas-source-tabs" role="tablist" aria-label="해외 검색 종류">
<button type="button" class="active" data-overseas-source="industry">미국 해외업종</button>
<button type="button" data-overseas-source="stock">US·TW 해외종목</button>
</div>
<form id="overseasSearchForm" class="overseas-search-form" autocomplete="off">
<input id="overseasSearch" type="search" maxlength="64" placeholder="빈 값은 전체 목록" aria-label="해외업종 또는 해외종목 검색어">
<button id="overseasSearchButton" type="submit">조회</button>
</form>
<div id="overseasSearchState" class="overseas-search-state" role="status" aria-live="polite"></div>
<div id="overseasResults" class="overseas-results" role="listbox" aria-label="해외업종·해외종목 검색 결과"></div>
<div id="selectedOverseas" class="selected-overseas" hidden>
<div>
<span>선택 대상</span>
<strong id="selectedOverseasName">-</strong>
<small id="selectedOverseasMeta">-</small>
</div>
<div class="overseas-ma-options" aria-label="해외종목 캔들 이동평균선">
<label><input id="overseasMa5" type="checkbox" checked>5일선</label>
<label><input id="overseasMa20" type="checkbox" checked>20일선</label>
</div>
</div>
<div class="overseas-action-header"><strong>해외 대상 컷</strong><small>업종 1개 · 종목 6개 닫힌 동작</small></div>
<div id="overseasActionList" class="overseas-action-list" aria-label="해외 대상 컷 목록"></div>
</section>
<section id="expertWorkflow" class="expert-workflow" aria-labelledby="expertWorkflowTitle" hidden>
<div class="expert-workflow-header">
<div>
<p class="panel-kicker">LEGACY EXPERT WORKFLOW</p>
<h3 id="expertWorkflowTitle">전문가 추천</h3>
</div>
<span id="expertCount" class="badge neutral">READ ONLY</span>
</div>
<form id="expertSearchForm" class="expert-search-form" autocomplete="off">
<input id="expertSearch" type="search" maxlength="64" placeholder="전문가명 · 빈 값은 전체" aria-label="전문가명">
<button id="expertSearchButton" type="submit">조회</button>
</form>
<div id="expertSearchState" class="expert-search-state" role="status" aria-live="polite"></div>
<div id="expertResults" class="expert-results" role="listbox" aria-label="전문가 검색 결과"></div>
<div id="selectedExpert" class="selected-expert" hidden>
<span>선택 전문가</span>
<strong id="selectedExpertName">-</strong>
<small id="selectedExpertMeta">-</small>
</div>
<div class="expert-preview-header">
<div><strong>추천 종목</strong><small>PLAYINDEX 순서 · read-only 미리보기</small></div>
<span id="expertPreviewCount">0 ITEMS</span>
</div>
<div id="expertPreviewState" class="expert-preview-state" role="status" aria-live="polite"></div>
<div id="expertPreviewItems" class="expert-preview-items" aria-label="전문가 추천 종목 미리보기"></div>
<div class="expert-action-header"><strong>전문가 추천 컷</strong><small>5개 단위 · 최대 20페이지</small></div>
<div id="expertActionList" class="expert-action-list" aria-label="전문가 추천 컷 목록"></div>
<div class="expert-crud-notice">
<span>EList · 전문가·추천종목 추가·편집·삭제</span>
<button id="expertCatalogManageButton" type="button">전문가 관리</button>
</div>
</section>
<section id="tradingHaltWorkflow" class="trading-halt-workflow" aria-labelledby="tradingHaltWorkflowTitle" hidden>
<div class="trading-halt-workflow-header">
<div>
<p class="panel-kicker">LEGACY TRADING HALT WORKFLOW</p>
<h3 id="tradingHaltWorkflowTitle">거래 정지 종목</h3>
</div>
<span id="tradingHaltCount" class="badge neutral">DB</span>
</div>
<form id="tradingHaltSearchForm" class="trading-halt-search-form" autocomplete="off">
<input id="tradingHaltSearch" type="search" maxlength="64" placeholder="종목명 · 빈 값은 전체" aria-label="거래 정지 종목명">
<button id="tradingHaltSearchButton" type="submit">조회</button>
</form>
<div id="tradingHaltSearchState" class="trading-halt-search-state" role="status" aria-live="polite"></div>
<div id="tradingHaltResults" class="trading-halt-results" role="listbox" aria-label="거래 정지 종목 검색 결과"></div>
<div id="selectedTradingHalt" class="selected-trading-halt" hidden>
<div>
<span>선택 종목</span>
<strong id="selectedTradingHaltName">-</strong>
<small id="selectedTradingHaltMeta">-</small>
</div>
<div class="trading-halt-ma-options" aria-label="캔들 이동평균선">
<label><input id="tradingHaltMa5" type="checkbox" checked>5일선</label>
<label><input id="tradingHaltMa20" type="checkbox" checked>20일선</label>
</div>
</div>
<div class="trading-halt-action-header">
<strong>거래 정지 컷</strong>
<small>원본 UC7 · 현재가와 120일 캔들</small>
</div>
<div id="tradingHaltActionList" class="trading-halt-action-list" aria-label="거래 정지 컷 목록"></div>
</section>
<label class="search-box">
<span></span>
<input id="catalogSearch" type="search" autocomplete="off" placeholder="종목·지수·그래픽 검색">
<input id="catalogSearch" type="search" autocomplete="off" placeholder="장면 코드·그래픽 검색">
<kbd>Ctrl K</kbd>
</label>
<div id="catalogTabs" class="catalog-tabs" role="tablist">
@@ -96,17 +366,64 @@
<span id="playlistCount" class="badge neutral">0 CUTS</span>
</div>
<div class="playlist-actions">
<button id="selectAllButton" type="button">전체 선택</button>
<button id="toggleAllEnabledButton" type="button">전체 송출</button>
<button id="selectAllButton" type="button">작업 전체선택</button>
<button id="moveUpButton" type="button" title="선택 항목 위로"></button>
<button id="moveDownButton" type="button" title="선택 항목 아래로"></button>
<button id="deleteButton" class="danger" type="button">삭제</button>
<span></span>
<button id="loadButton" type="button">불러오기</button>
<button id="saveButton" class="primary-soft" type="button">저장</button>
<button id="deleteAllButton" class="danger" type="button">전체삭제</button>
<div class="global-candle-options" aria-label="전체 캔들 이동평균선">
<span>캔들</span>
<label><input id="globalMa5" type="checkbox" checked>5일선</label>
<label><input id="globalMa20" type="checkbox" checked>20일선</label>
</div>
<button id="namedPlaylistOpenButton" class="database-soft" type="button" aria-expanded="false" aria-controls="namedPlaylistPanel">DB 저장·불러오기</button>
<button id="loadButton" type="button">로컬 불러오기</button>
<button id="saveButton" class="primary-soft" type="button">로컬 임시저장</button>
</div>
<section id="namedPlaylistPanel" class="named-playlist-panel" aria-labelledby="namedPlaylistTitle" hidden>
<header class="named-playlist-header">
<div>
<p class="panel-kicker">ORACLE · DC_LIST / PLAY_LIST</p>
<h3 id="namedPlaylistTitle">DB 플레이리스트</h3>
</div>
<div class="named-playlist-header-actions">
<span id="namedPlaylistStateBadge" class="badge neutral">대기</span>
<button id="namedPlaylistRefreshButton" type="button">목록 새로고침</button>
<button id="namedPlaylistCloseButton" type="button" aria-label="DB 플레이리스트 닫기">닫기</button>
</div>
</header>
<div id="namedPlaylistStatus" class="named-playlist-status" role="status" aria-live="polite">
Oracle에 저장된 이름 목록을 조회하세요. 로컬 임시저장과는 별도입니다.
</div>
<div class="named-playlist-layout">
<label class="named-playlist-definitions-label" for="namedPlaylistDefinitions">저장된 이름</label>
<select id="namedPlaylistDefinitions" class="named-playlist-definitions" size="6" aria-describedby="namedPlaylistSelectionSummary"></select>
<div id="namedPlaylistSelectionSummary" class="named-playlist-selection-summary">선택된 DB 정의 없음</div>
<div class="named-playlist-load-actions">
<button id="namedPlaylistLoadButton" class="primary-soft" type="button">선택 DB 불러오기</button>
<button id="namedPlaylistPageRetryButton" type="button" hidden>페이지 다시 조회</button>
<button id="namedPlaylistRestoreBackupButton" type="button">불러오기 전 상태 복원</button>
</div>
<div class="named-playlist-create">
<label for="namedPlaylistProgramCode">새 코드</label>
<input id="namedPlaylistProgramCode" type="text" inputmode="numeric" maxlength="8" autocomplete="off" placeholder="00000001">
<label for="namedPlaylistProgramTitle">새 이름</label>
<input id="namedPlaylistProgramTitle" type="text" maxlength="128" autocomplete="off" placeholder="예: 오전 방송">
<button id="namedPlaylistCreateButton" type="button">새 이름 만들기</button>
</div>
<div class="named-playlist-mutation-actions">
<button id="namedPlaylistReplaceButton" class="primary-soft" type="button">선택 정의에 현재 순서 저장</button>
<button id="namedPlaylistDeleteButton" class="danger" type="button">선택 정의 삭제</button>
</div>
</div>
<p id="namedPlaylistGuardSummary" class="named-playlist-guard-summary">
DB에서 읽은 각 행은 신뢰 복원과 현재 페이지 재계산을 통과해야 PREPARE할 수 있습니다.
</p>
</section>
<div class="table-wrap">
<table>
<thead><tr><th class="check-cell"></th><th class="number-cell">#</th><th>그래픽 컷</th><th>구분</th><th class="drag-cell">정렬</th></tr></thead>
<thead><tr><th class="check-cell">송출</th><th class="number-cell">#</th><th>종목·대상</th><th></th><th>세부사항</th><th class="page-cell">Page</th><th class="drag-cell">정렬</th></tr></thead>
<tbody id="playlistBody"></tbody>
</table>
<div id="playlistEmpty" class="empty-state">
@@ -137,6 +454,15 @@
<strong id="playoutSafetyMode">확인 중</strong>
</div>
</div>
<div class="background-control-bar" aria-label="장면 배경 설정">
<div>
<small>BACKGROUND</small>
<strong id="backgroundFileName">배경 없음</strong>
<span id="backgroundStateText">원본 F2/F3 배경 상태 확인 중</span>
</div>
<button id="chooseBackgroundButton" type="button">배경 파일 (F2)</button>
<button id="toggleBackgroundButton" type="button">배경 켜기/끄기 (F3)</button>
</div>
<div id="playoutStatusMessage" class="playout-status-message" role="status" aria-live="polite">송출 어댑터 상태를 확인하고 있습니다.</div>
<div class="playout-page-state" aria-label="장면 및 페이지 상태">
<span>SCENE <strong id="playoutSceneCode"></strong></span>
@@ -191,7 +517,7 @@
</datalist>
</form>
<div class="playout-controls">
<button id="prepareButton" class="prepare" type="button"><span>F2</span>PREPARE</button>
<button id="prepareButton" class="prepare" type="button"><span>PREP</span>PREPARE</button>
<button id="takeInButton" class="take-in" type="button"><span>F8</span>TAKE IN</button>
<button id="nextButton" type="button"><span></span>NEXT</button>
<button id="takeOutButton" class="take-out" type="button"><span>ESC</span>TAKE OUT</button>
@@ -207,6 +533,23 @@
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="playout-safety.js"></script>
<script src="operator-workflow.js"></script>
<script src="legacy-fixed-workflow.js"></script>
<script src="industry-workflow.js"></script>
<script src="comparison-workflow.js"></script>
<script src="theme-workflow.js"></script>
<script src="overseas-workflow.js"></script>
<script src="expert-workflow.js"></script>
<script src="trading-halt-workflow.js"></script>
<script src="candle-options-workflow.js"></script>
<script src="manual-lists-workflow.js"></script>
<script src="manual-financial-workflow.js"></script>
<script src="named-manual-restore-workflow.js"></script>
<script src="operator-catalog-workflow.js"></script>
<script src="manual-lists-ui.js"></script>
<script src="manual-financial-ui.js"></script>
<script src="operator-catalog-ui.js"></script>
<script src="named-playlist-workflow.js"></script>
<script src="app.js"></script>
</body>
</html>