변경 목적: Agent Compare 아래 비교본의 개발 문서와 런처 소스를 기준으로 현재 AX Commander에 빠져 있던 신규 런처 기능을 동일한 흐름으로 옮겨, 비교본 수준의 기능 폭을 현재 제품에 반영했습니다. 핵심 수정사항: 비교본의 신규 런처 핸들러 다수를 src/AxCopilot/Handlers로 이식하고 App.xaml.cs 등록 흐름에 연결했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/UUID/JWT/QR 등 AX Commander 기능을 추가했습니다. 핵심 수정사항: 신규 기능이 실제 동작하도록 AppSettings 확장, SchedulerService/FileTagService/NotificationCenterService/IconCacheService/UrlTemplateEngine/PomodoroService 추가, 배치 이름변경/세션/스케줄/매크로 편집 창 추가, NotificationService와 Symbols 보강, QR/OCR용 csproj 의존성과 Windows 타겟 프레임워크를 반영했습니다. 문서 반영: README.md와 docs/DEVELOPMENT.md에 비교본 기반 런처 기능 이식 이력과 검증 결과를 업데이트했습니다. 검증 결과: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 실행 기준 경고 0개, 오류 0개를 확인했습니다.
251 lines
11 KiB
C#
251 lines
11 KiB
C#
using System.Text.Json;
|
|
using System.Windows;
|
|
using AxCopilot.SDK;
|
|
using AxCopilot.Services;
|
|
|
|
namespace AxCopilot.Handlers;
|
|
|
|
/// <summary>
|
|
/// L25-4: 오늘 업무 통합 뷰. "today" 프리픽스로 사용합니다.
|
|
///
|
|
/// 예: today → 오늘 날짜/요일/공휴일 + 할일 + 알림 + 공휴일 현황
|
|
/// </summary>
|
|
public class TodayHandler : IActionHandler
|
|
{
|
|
public string? Prefix => "today";
|
|
|
|
public PluginMetadata Metadata => new(
|
|
"오늘",
|
|
"오늘 업무 통합 뷰 — 날짜·할일·알림·공휴일",
|
|
"1.0",
|
|
"AX");
|
|
|
|
private static readonly string TodoPath = System.IO.Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
"AxCopilot", "todos.json");
|
|
|
|
private static readonly string[] DayNames = ["일", "월", "화", "수", "목", "금", "토"];
|
|
|
|
// 2025~2027 주요 공휴일 (CalHandler에 직접 접근 불가하므로 독립 정의)
|
|
private static readonly Dictionary<DateOnly, string> Holidays = new()
|
|
{
|
|
// 2025
|
|
{ new DateOnly(2025, 1, 1), "신정" },
|
|
{ new DateOnly(2025, 1, 28), "설날연휴" },
|
|
{ new DateOnly(2025, 1, 29), "설날" },
|
|
{ new DateOnly(2025, 1, 30), "설날연휴" },
|
|
{ new DateOnly(2025, 3, 1), "삼일절" },
|
|
{ new DateOnly(2025, 3, 3), "대체공휴일" },
|
|
{ new DateOnly(2025, 5, 5), "어린이날" },
|
|
{ new DateOnly(2025, 5, 6), "부처님오신날" },
|
|
{ new DateOnly(2025, 6, 6), "현충일" },
|
|
{ new DateOnly(2025, 8, 15), "광복절" },
|
|
{ new DateOnly(2025, 10, 3), "개천절" },
|
|
{ new DateOnly(2025, 10, 5), "추석연휴" },
|
|
{ new DateOnly(2025, 10, 6), "추석" },
|
|
{ new DateOnly(2025, 10, 7), "추석연휴" },
|
|
{ new DateOnly(2025, 10, 8), "대체공휴일" },
|
|
{ new DateOnly(2025, 10, 9), "한글날" },
|
|
{ new DateOnly(2025, 12, 25), "크리스마스" },
|
|
|
|
// 2026
|
|
{ new DateOnly(2026, 1, 1), "신정" },
|
|
{ new DateOnly(2026, 2, 17), "설날연휴" },
|
|
{ new DateOnly(2026, 2, 18), "설날" },
|
|
{ new DateOnly(2026, 2, 19), "설날연휴" },
|
|
{ new DateOnly(2026, 3, 1), "삼일절" },
|
|
{ new DateOnly(2026, 3, 2), "대체공휴일" },
|
|
{ new DateOnly(2026, 5, 5), "어린이날" },
|
|
{ new DateOnly(2026, 5, 24), "부처님오신날" },
|
|
{ new DateOnly(2026, 5, 25), "대체공휴일" },
|
|
{ new DateOnly(2026, 6, 6), "현충일" },
|
|
{ new DateOnly(2026, 6, 8), "대체공휴일" },
|
|
{ new DateOnly(2026, 8, 15), "광복절" },
|
|
{ new DateOnly(2026, 8, 17), "대체공휴일" },
|
|
{ new DateOnly(2026, 9, 24), "추석연휴" },
|
|
{ new DateOnly(2026, 9, 25), "추석" },
|
|
{ new DateOnly(2026, 9, 26), "추석연휴" },
|
|
{ new DateOnly(2026, 10, 3), "개천절" },
|
|
{ new DateOnly(2026, 10, 5), "대체공휴일" },
|
|
{ new DateOnly(2026, 10, 9), "한글날" },
|
|
{ new DateOnly(2026, 12, 25), "크리스마스" },
|
|
|
|
// 2027
|
|
{ new DateOnly(2027, 1, 1), "신정" },
|
|
{ new DateOnly(2027, 2, 7), "설날연휴" },
|
|
{ new DateOnly(2027, 2, 8), "설날" },
|
|
{ new DateOnly(2027, 2, 9), "설날연휴" },
|
|
{ new DateOnly(2027, 3, 1), "삼일절" },
|
|
{ new DateOnly(2027, 5, 5), "어린이날" },
|
|
{ new DateOnly(2027, 5, 13), "부처님오신날" },
|
|
{ new DateOnly(2027, 6, 6), "현충일" },
|
|
{ new DateOnly(2027, 6, 7), "대체공휴일" },
|
|
{ new DateOnly(2027, 8, 15), "광복절" },
|
|
{ new DateOnly(2027, 8, 16), "대체공휴일" },
|
|
{ new DateOnly(2027, 9, 13), "추석연휴" },
|
|
{ new DateOnly(2027, 9, 14), "추석" },
|
|
{ new DateOnly(2027, 9, 15), "추석연휴" },
|
|
{ new DateOnly(2027, 10, 3), "개천절" },
|
|
{ new DateOnly(2027, 10, 4), "대체공휴일" },
|
|
{ new DateOnly(2027, 10, 9), "한글날" },
|
|
{ new DateOnly(2027, 12, 25), "크리스마스" },
|
|
{ new DateOnly(2027, 12, 27), "대체공휴일" },
|
|
};
|
|
|
|
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
|
{
|
|
var items = new List<LauncherItem>();
|
|
var today = DateOnly.FromDateTime(DateTime.Today);
|
|
var now = DateTime.Now;
|
|
|
|
// ─── 항목1: 날짜 헤더 ────────────────────────────────────────────────
|
|
var dow = DayNames[(int)today.DayOfWeek];
|
|
var isHol = Holidays.TryGetValue(today, out var holName);
|
|
var isWknd = today.DayOfWeek == DayOfWeek.Saturday || today.DayOfWeek == DayOfWeek.Sunday;
|
|
string status;
|
|
if (isHol) status = $"공휴일 — {holName}";
|
|
else if (isWknd) status = "주말";
|
|
else status = "평일 (업무일)";
|
|
|
|
items.Add(new LauncherItem(
|
|
$"{today:yyyy년 MM월 dd일} ({dow}요일)",
|
|
status,
|
|
null, ("copy", $"{today:yyyy-MM-dd} ({dow}) {status}"),
|
|
Symbol: "\uE8BF"));
|
|
|
|
// ─── 항목2: 할일 ─────────────────────────────────────────────────────
|
|
var (pendingCount, recentTitles) = LoadPendingTodos();
|
|
var todoSub = pendingCount == 0
|
|
? "미완료 할일 없음"
|
|
: string.Join(" / ", recentTitles.Take(3));
|
|
items.Add(new LauncherItem(
|
|
$"미완료 할일 {pendingCount}건",
|
|
todoSub,
|
|
null, null, Symbol: "\uE762"));
|
|
|
|
// ─── 항목3: 알림 ─────────────────────────────────────────────────────
|
|
var todayReminders = RemindHandler.GetTodayReminders();
|
|
if (todayReminders.Count == 0)
|
|
{
|
|
items.Add(new LauncherItem("오늘 알림 없음",
|
|
"remind HH:mm 메시지 로 알림을 설정하세요",
|
|
null, null, Symbol: "\uE787"));
|
|
}
|
|
else
|
|
{
|
|
var remindSub = string.Join(" / ",
|
|
todayReminders.Take(3).Select(r => $"{r.Time:HH:mm} {r.Message}"));
|
|
items.Add(new LauncherItem(
|
|
$"오늘 알림 {todayReminders.Count}건",
|
|
remindSub,
|
|
null, null, Symbol: "\uE787"));
|
|
}
|
|
|
|
// ─── 항목4: 다음 공휴일 ──────────────────────────────────────────────
|
|
var nextHol = Holidays.Keys
|
|
.Where(d => d > today)
|
|
.OrderBy(d => d)
|
|
.FirstOrDefault();
|
|
|
|
if (nextHol != default)
|
|
{
|
|
var diff = nextHol.DayNumber - today.DayNumber;
|
|
var nextDow = DayNames[(int)nextHol.DayOfWeek];
|
|
items.Add(new LauncherItem(
|
|
$"다음 공휴일: {nextHol:MM/dd} ({nextDow}) {Holidays[nextHol]}",
|
|
$"D-{diff}일",
|
|
null, ("copy", $"{nextHol:yyyy-MM-dd} {Holidays[nextHol]} D-{diff}"),
|
|
Symbol: "\uE787"));
|
|
}
|
|
else
|
|
{
|
|
items.Add(new LauncherItem("다음 공휴일 정보 없음", "",
|
|
null, null, Symbol: "\uE787"));
|
|
}
|
|
|
|
// ─── 항목5: 이번달 잔여 업무일 ───────────────────────────────────────
|
|
var lastDay = new DateOnly(today.Year, today.Month,
|
|
DateTime.DaysInMonth(today.Year, today.Month));
|
|
var remaining = CountWorkdaysFrom(today, lastDay);
|
|
var total = CountWorkdays(today.Year, today.Month);
|
|
items.Add(new LauncherItem(
|
|
$"이번달 잔여 업무일 {remaining}일",
|
|
$"{today.Year}년 {today.Month}월 총 업무일: {total}일",
|
|
null, null, Symbol: "\uE8BF"));
|
|
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
|
{
|
|
if (item.Data is ("copy", string text) && !string.IsNullOrWhiteSpace(text))
|
|
{
|
|
try
|
|
{
|
|
Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(text));
|
|
NotificationService.Notify("오늘", "클립보드에 복사했습니다.");
|
|
}
|
|
catch { }
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
// ── 할일 파싱 ────────────────────────────────────────────────────────────
|
|
|
|
private static (int pending, List<string> titles) LoadPendingTodos()
|
|
{
|
|
try
|
|
{
|
|
if (!System.IO.File.Exists(TodoPath)) return (0, []);
|
|
var json = System.IO.File.ReadAllText(TodoPath, System.Text.Encoding.UTF8);
|
|
using var doc = JsonDocument.Parse(json);
|
|
var root = doc.RootElement;
|
|
if (root.ValueKind != JsonValueKind.Array) return (0, []);
|
|
|
|
var pending = 0;
|
|
var titles = new List<string>();
|
|
foreach (var el in root.EnumerateArray())
|
|
{
|
|
var done = false;
|
|
if (el.TryGetProperty("done", out var doneProp))
|
|
done = doneProp.GetBoolean();
|
|
if (done) continue;
|
|
|
|
pending++;
|
|
if (titles.Count < 3 && el.TryGetProperty("text", out var textProp))
|
|
titles.Add(textProp.GetString() ?? "");
|
|
}
|
|
return (pending, titles);
|
|
}
|
|
catch { return (0, []); }
|
|
}
|
|
|
|
// ── 업무일 계산 ──────────────────────────────────────────────────────────
|
|
|
|
private static bool IsHoliday(DateOnly d) =>
|
|
Holidays.ContainsKey(d) ||
|
|
d.DayOfWeek == DayOfWeek.Saturday ||
|
|
d.DayOfWeek == DayOfWeek.Sunday;
|
|
|
|
private static int CountWorkdays(int year, int month)
|
|
{
|
|
var days = DateTime.DaysInMonth(year, month);
|
|
var count = 0;
|
|
for (var i = 1; i <= days; i++)
|
|
if (!IsHoliday(new DateOnly(year, month, i))) count++;
|
|
return count;
|
|
}
|
|
|
|
private static int CountWorkdaysFrom(DateOnly from, DateOnly to)
|
|
{
|
|
var count = 0;
|
|
var cur = from;
|
|
while (cur <= to)
|
|
{
|
|
if (!IsHoliday(cur)) count++;
|
|
cur = cur.AddDays(1);
|
|
}
|
|
return count;
|
|
}
|
|
}
|