From 5e54c6c47582345f96caadd80a73c3a073abb737 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Fri, 13 Mar 2026 15:23:35 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(ui):=20=EB=B0=B0=EC=B9=98=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=88=ED=84=B0=EB=A7=81=20UI=20=EC=B5=9C=EC=A0=81=ED=99=94?= =?UTF-8?q?=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 대시보드 퀵 네비게이션 제거 - 작업 목록 기본 뷰/정렬 변경, jobName 서브텍스트 제거 - 실행 이력 AIS 필터 프리셋 버튼 추가 - 스케줄 카드/테이블 뷰 토글, 등록/수정 폼 모달 전환 Co-Authored-By: Claude Opus 4.6 --- frontend/src/pages/Dashboard.tsx | 28 --- frontend/src/pages/Executions.tsx | 35 +++ frontend/src/pages/Jobs.tsx | 42 ++-- frontend/src/pages/Recollects.tsx | 2 +- frontend/src/pages/Schedules.tsx | 370 ++++++++++++++++++++---------- 5 files changed, 304 insertions(+), 173 deletions(-) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 6e33fdb..cc8475a 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -193,34 +193,6 @@ export default function Dashboard() { /> - {/* Quick Navigation */} -
- - 작업 관리 - - - 실행 이력 - - - 스케줄 관리 - - - 타임라인 - -
- {/* Running Jobs */}

diff --git a/frontend/src/pages/Executions.tsx b/frontend/src/pages/Executions.tsx index 20546b2..e3b45de 100644 --- a/frontend/src/pages/Executions.tsx +++ b/frontend/src/pages/Executions.tsx @@ -67,6 +67,9 @@ export default function Executions() { return map; }, [displayNames]); + const aisJobs = useMemo(() => jobs.filter(j => j.toLowerCase().startsWith('ais')), [jobs]); + const nonAisJobs = useMemo(() => jobs.filter(j => !j.toLowerCase().startsWith('ais')), [jobs]); + const loadJobs = useCallback(async () => { try { const data = await batchApi.getJobs(); @@ -287,6 +290,38 @@ export default function Executions() { )} +
+ + + +
{selectedJobs.length > 0 && ( - @@ -316,9 +316,6 @@ export default function Jobs() {

{getJobLabel(job)}

- {job.displayName && ( -

{job.jobName}

- )}
{isRunning && ( @@ -436,12 +433,7 @@ export default function Jobs() { {isRunning && ( )} -
- {getJobLabel(job)} - {job.displayName && ( -

{job.jobName}

- )} -
+ {getJobLabel(job)}
diff --git a/frontend/src/pages/Recollects.tsx b/frontend/src/pages/Recollects.tsx index 7eb4e3b..16ba5db 100644 --- a/frontend/src/pages/Recollects.tsx +++ b/frontend/src/pages/Recollects.tsx @@ -477,7 +477,7 @@ export default function Recollects() { - + diff --git a/frontend/src/pages/Schedules.tsx b/frontend/src/pages/Schedules.tsx index 3993886..83e1b0d 100644 --- a/frontend/src/pages/Schedules.tsx +++ b/frontend/src/pages/Schedules.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { batchApi, type ScheduleResponse, type JobDisplayName } from '../api/batchApi'; import { formatDateTime } from '../utils/formatters'; import { useToastContext } from '../contexts/ToastContext'; @@ -8,6 +8,7 @@ import LoadingSpinner from '../components/LoadingSpinner'; import { getNextExecutions } from '../utils/cronPreview'; type ScheduleMode = 'new' | 'existing'; +type ScheduleViewMode = 'card' | 'table'; interface ConfirmAction { type: 'toggle' | 'delete'; @@ -95,11 +96,14 @@ export default function Schedules() { const [listLoading, setListLoading] = useState(true); const [displayNames, setDisplayNames] = useState([]); + // View mode state + const [viewMode, setViewMode] = useState('table'); + // Confirm modal state const [confirmAction, setConfirmAction] = useState(null); - // 폼 영역 ref (편집 버튼 클릭 시 스크롤) - const formRef = useRef(null); + // Form modal state + const [formOpen, setFormOpen] = useState(false); const loadSchedules = useCallback(async () => { try { @@ -186,8 +190,8 @@ export default function Schedules() { showToast('스케줄이 등록되었습니다', 'success'); } await loadSchedules(); - // Reload schedule info for current job - await handleJobSelect(selectedJob); + setFormOpen(false); + resetForm(); } catch (err) { const message = err instanceof Error ? err.message : '저장 실패'; showToast(message, 'error'); @@ -216,12 +220,10 @@ export default function Schedules() { await batchApi.deleteSchedule(schedule.jobName); showToast(`${schedule.jobName} 스케줄이 삭제되었습니다`, 'success'); await loadSchedules(); - // Clear form if deleted schedule was selected + // Close form if deleted schedule was being edited if (selectedJob === schedule.jobName) { - setSelectedJob(''); - setCronExpression(''); - setDescription(''); - setScheduleMode('new'); + resetForm(); + setFormOpen(false); } } catch (err) { const message = err instanceof Error ? err.message : '삭제 실패'; @@ -230,124 +232,159 @@ export default function Schedules() { setConfirmAction(null); }; + const resetForm = () => { + setSelectedJob(''); + setCronExpression(''); + setDescription(''); + setScheduleMode('new'); + }; + const handleEditFromCard = (schedule: ScheduleResponse) => { setSelectedJob(schedule.jobName); setCronExpression(schedule.cronExpression); setDescription(schedule.description ?? ''); setScheduleMode('existing'); - formRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + setFormOpen(true); + }; + + const handleNewSchedule = () => { + resetForm(); + setFormOpen(true); }; return (
- {/* Form Section */} -
-

스케줄 등록 / 수정

- -
- {/* Job Select */} -
- -
- - {selectedJob && ( - - {scheduleMode === 'existing' ? '기존 스케줄' : '새 스케줄'} - - )} - {formLoading && ( -
- )} + + + +
-
- {/* Cron Expression */} -
- - setCronExpression(e.target.value)} - placeholder="0 0/15 * * * ?" - className="w-full rounded-lg border border-wing-border px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-wing-accent focus:border-wing-accent" - disabled={!selectedJob || formLoading} - /> -
+
+ {/* Job Select */} +
+ +
+ + {selectedJob && ( + + {scheduleMode === 'existing' ? '기존 스케줄' : '새 스케줄'} + + )} + {formLoading && ( +
+ )} +
+
- {/* Cron Presets */} -
- -
- {CRON_PRESETS.map(({ label, cron }) => ( - - ))} + /> +
+ + {/* Cron Presets */} +
+ +
+ {CRON_PRESETS.map(({ label, cron }) => ( + + ))} +
+
+ + {/* Cron Preview */} + {cronExpression.trim() && ( + + )} + + {/* Description */} +
+ + setDescription(e.target.value)} + placeholder="스케줄 설명 (선택)" + className="w-full rounded-lg border border-wing-border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-wing-accent focus:border-wing-accent" + disabled={!selectedJob || formLoading} + /> +
+
+ + {/* Modal Footer */} +
+ +
- - {/* Cron Preview */} - {cronExpression.trim() && ( - - )} - - {/* Description */} -
- - setDescription(e.target.value)} - placeholder="스케줄 설명 (선택)" - className="w-full rounded-lg border border-wing-border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-wing-accent focus:border-wing-accent" - disabled={!selectedJob || formLoading} - /> -
- - {/* Save Button */} -
- -
-
+ )} {/* Schedule List */}
@@ -360,19 +397,47 @@ export default function Schedules() { )} - +
+ +
+ + +
+ +
{listLoading ? ( ) : schedules.length === 0 ? ( - - ) : ( + + ) : viewMode === 'card' ? (
{schedules.map((schedule) => (
))}
+ ) : ( +
+
API명배치 작업명 마지막 수집 완료일시 경과시간 상태
+ + + + + + + + + + + + {schedules.map((schedule) => ( + + + + + + + + + ))} + +
작업명Cron 표현식상태다음 실행이전 실행액션
+
+ {displayNameMap[schedule.jobName] || schedule.jobName} +
+ {schedule.description && ( +
{schedule.description}
+ )} +
+ {schedule.cronExpression} + + + {schedule.active ? '활성' : '비활성'} + + {formatDateTime(schedule.nextFireTime)}{schedule.previousFireTime ? formatDateTime(schedule.previousFireTime) : '-'} +
+ + + +
+
+ )} -- 2.45.2 From dd80b144f52ecf7c032ac1e7c38272f9b002631a Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Fri, 13 Mar 2026 15:24:13 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 22ced13..e789218 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -30,6 +30,7 @@ - IMO 기반 Risk 상세 조회 bypass API 추가 (#39) - 배치 작업 목록 한글 표시명 추가 (#40) - Job 한글 표시명 DB 관리 및 전체 화면 통합 (#45) +- 배치 모니터링 UI 최적화: 대시보드 퀵 네비 제거, AIS 필터 프리셋, 스케줄 뷰 토글 및 폼 모달 전환 (#46) ### 수정 - 자동 재수집 JobParameter 오버플로우 수정 (VARCHAR 2500 제한 해결) -- 2.45.2