diff --git a/src/Hc900Crawler/Controllers/PointBuilderController.cs b/src/Hc900Crawler/Controllers/PointBuilderController.cs index 9eed2bd..a0098ce 100644 --- a/src/Hc900Crawler/Controllers/PointBuilderController.cs +++ b/src/Hc900Crawler/Controllers/PointBuilderController.cs @@ -332,6 +332,36 @@ public class PointBuilderController : ControllerBase return Ok(new { success = true, count, message = $"{count}개 포인트 추가 완료" }); } + public sealed record RealtimeBulkDto(List? Ids, string? ControllerId); + + // 전체 태그 목록에서 선택(id)한 태그를 realtime_table에 즉시 라이브로 편입 + [HttpPost("realtime-add")] + public async Task RealtimeAdd([FromBody] RealtimeBulkDto dto) + { + if (dto.Ids == null || dto.Ids.Count == 0) + return BadRequest(new { success = false, error = "ids는 최소 1개 이상 필요합니다" }); + if (string.IsNullOrEmpty(dto.ControllerId)) + return BadRequest(new { success = false, error = "controllerId는 필수입니다" }); + + var count = await _db.Hc900RealtimeAddByIdsAsync(dto.ControllerId, dto.Ids); + _log.LogInformation("[PointBuilder] 실시간 추가: {Count}개 (controller={Ctrl})", count, dto.ControllerId); + return Ok(new { success = true, count, message = $"{count}개 실시간 편입 완료" }); + } + + // 선택(id)한 태그를 realtime_table에서 제거 + [HttpPost("realtime-remove")] + public async Task RealtimeRemove([FromBody] RealtimeBulkDto dto) + { + if (dto.Ids == null || dto.Ids.Count == 0) + return BadRequest(new { success = false, error = "ids는 최소 1개 이상 필요합니다" }); + if (string.IsNullOrEmpty(dto.ControllerId)) + return BadRequest(new { success = false, error = "controllerId는 필수입니다" }); + + var count = await _db.Hc900RealtimeRemoveByIdsAsync(dto.ControllerId, dto.Ids); + _log.LogInformation("[PointBuilder] 실시간 제거: {Count}개 (controller={Ctrl})", count, dto.ControllerId); + return Ok(new { success = true, count, message = $"{count}개 실시간 제거 완료" }); + } + [HttpGet("points")] public async Task GetPoints( [FromQuery] string? rt = null, // null/"" = 전체, "only" = 실시간(realtime_table만), "exclude" = 실시간 제외 diff --git a/src/Hc900Crawler/wwwroot/js/pb.js b/src/Hc900Crawler/wwwroot/js/pb.js index 301b11f..bad234b 100644 --- a/src/Hc900Crawler/wwwroot/js/pb.js +++ b/src/Hc900Crawler/wwwroot/js/pb.js @@ -70,6 +70,13 @@ async function pbPreview() { } } +function pbClosePreview() { + const area = document.getElementById('pb-preview-area'); + if (area) area.style.display = 'none'; + const chk = document.getElementById('pb-chk-all-preview'); + if (chk) chk.checked = false; +} + async function pbBuild() { if (!confirm('모든 활성 태그를 해제하고 조건에 맞는 태그만 활성화합니다. 계속하시겠습니까?')) return; const payload = pbCollectAllGroups(); @@ -322,6 +329,39 @@ function pbUpdateBulkBar() { const checked = document.querySelectorAll('#pb-points-tbody .pb-point-chk:checked'); const bar = document.getElementById('pb-points-bulk'); bar.hidden = checked.length === 0; + const cnt = document.getElementById('pb-points-selcount'); + if (cnt) cnt.textContent = checked.length ? `${checked.length}개 선택` : ''; +} + +function pbSelectedPointIds() { + return Array.from(document.querySelectorAll('#pb-points-tbody .pb-point-chk:checked')) + .map(cb => parseInt(cb.value)).filter(n => !isNaN(n)); +} + +// 선택 태그를 realtime_table에 즉시 라이브로 편입 +async function pbRealtimeAdd() { + const ids = pbSelectedPointIds(); + const ctrl = pbGetControllerId(); + if (ids.length === 0) return; + if (!ctrl) { alert('컨트롤러를 선택해주세요.'); return; } + if (!confirm(`[${ctrl}] 선택한 ${ids.length}개를 실시간(realtime_table)에 추가합니다. 계속하시겠습니까?`)) return; + const res = await pbApi('/api/pointbuilder/realtime-add', 'POST', { ids, controllerId: ctrl }); + alert(res.message || `${res.count}개 실시간 편입`); + pbRefresh(); + pbLoadSummary(); +} + +// 선택 태그를 realtime_table에서 제거 +async function pbRealtimeRemove() { + const ids = pbSelectedPointIds(); + const ctrl = pbGetControllerId(); + if (ids.length === 0) return; + if (!ctrl) { alert('컨트롤러를 선택해주세요.'); return; } + if (!confirm(`[${ctrl}] 선택한 ${ids.length}개를 실시간(realtime_table)에서 제거합니다. 계속하시겠습니까?`)) return; + const res = await pbApi('/api/pointbuilder/realtime-remove', 'POST', { ids, controllerId: ctrl }); + alert(res.message || `${res.count}개 실시간 제거`); + pbRefresh(); + pbLoadSummary(); } async function pbDeleteOne(id) { @@ -333,19 +373,6 @@ async function pbDeleteOne(id) { } } -async function pbBulkActivate(active) { - const ids = Array.from(document.querySelectorAll('#pb-points-tbody .pb-point-chk:checked')) - .map(cb => parseInt(cb.value)); - const ctrl = pbGetControllerId(); - if (ids.length === 0) return; - if (!confirm(`[${ctrl || '전체'}] 선택한 ${ids.length}개를 ${active ? '활성화' : '비활성화'}합니다. 계속하시겠습니까?`)) return; - const res = await pbApi('/api/hc900/tags/bulk-active', 'POST', { ids, active, controllerId: ctrl || undefined }); - if (res) { - pbRefresh(); - pbLoadSummary(); - } -} - // ── Sinam xlsx 파싱 ──────────────────────────────────────────────────────────── let _pbSinamBusy = false; diff --git a/src/Hc900Crawler/wwwroot/panes/pb.html b/src/Hc900Crawler/wwwroot/panes/pb.html index 9dc9f22..0af2b24 100644 --- a/src/Hc900Crawler/wwwroot/panes/pb.html +++ b/src/Hc900Crawler/wwwroot/panes/pb.html @@ -189,10 +189,11 @@