import { computed, onMounted, onUnmounted, ref, watch, type Ref } from 'vue' import { CornerDownLeft, Database, Sparkles, Sun, ListTodo } from 'lucide-vue-next' import { scheduleCenterApi, type ScheduleCenterCommanderSummary, type ScheduleCenterDateResponse, type ScheduleCenterDaySummary, } from '@/api/scheduleCenter' import { taskApi, type Task, type TaskDispatchStatus, type TaskQuadrant } from '@/api/task' import type { Conversation } from '@/api/conversation' export interface SidebarFocusItem { id: string label: string title: string meta: string tone: 'done' | 'doing' | 'pending' } export interface SidebarNewsItem { id: string title: string meta: string } export interface TodayStatusQuadrantViewTask { id: string title: string completed: boolean status: string dispatchStatus: string assigneeLabel: string } export interface TodayStatusQuadrantView { id: string title: string subtitle: string color: string glowColor: string icon: string tasks: TodayStatusQuadrantViewTask[] } export const sidebarCollapsedModules = [ { id: 'calendar', label: 'Calendar', icon: Sun }, { id: 'status', label: 'Status', icon: Database }, { id: 'focus', label: 'Focus', icon: Sparkles }, { id: 'kanban', label: 'Issues', icon: ListTodo }, { id: 'review', label: 'Review', icon: CornerDownLeft }, ] const ISSUE_QUADRANT_META: Record> = { 'urgent-important': { id: 'urgent-important', title: 'Important & Urgent', subtitle: 'CRITICAL', color: '#ff4757', glowColor: 'rgba(255, 71, 87, 0.4)', icon: '!', }, 'not-urgent-important': { id: 'not-urgent-important', title: 'Important & Planned', subtitle: 'PLANNED', color: '#ffd93d', glowColor: 'rgba(255, 217, 61, 0.4)', icon: 'P', }, 'urgent-not-important': { id: 'urgent-not-important', title: 'Urgent & Delegated', subtitle: 'DELEGATE', color: '#00d4ff', glowColor: 'rgba(0, 212, 255, 0.4)', icon: 'D', }, 'not-urgent-not-important': { id: 'not-urgent-not-important', title: 'Backlog / Low Priority', subtitle: 'BACKLOG', color: '#6bcf7f', glowColor: 'rgba(107, 207, 127, 0.4)', icon: 'B', }, } export function formatDateKey(date: Date) { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } function formatMonthKey(date: Date) { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') return `${year}-${month}` } function toneFromStatus(status: string) { if (status === 'done' || status === 'completed') return 'done' as const if (status === 'in_progress' || status === 'running' || status === 'queued') return 'doing' as const return 'pending' as const } function focusLabelByQuadrant(quadrant?: string | null) { if (!quadrant) return 'Task' if (quadrant === 'urgent-important') return 'Critical' if (quadrant === 'not-urgent-important') return 'Planned' if (quadrant === 'urgent-not-important') return 'Delegated' return 'Backlog' } function focusMeta(status: string, dispatchStatus: string, assigneeType?: string | null, assigneeId?: string | null) { const statusMap: Record = { todo: 'Pending', in_progress: 'In progress', done: 'Done', cancelled: 'Cancelled', } const dispatchMap: Record = { idle: 'Not dispatched', queued: 'Queued', running: 'Running', completed: 'Completed', failed: 'Failed', } const statusLabel = dispatchStatus !== 'idle' ? dispatchMap[dispatchStatus] : (statusMap[status] || 'Pending') if (assigneeType && assigneeId) return `${statusLabel} / ${assigneeType}:${assigneeId}` if (assigneeType) return `${statusLabel} / ${assigneeType}` return statusLabel } function deriveIssueQuadrant(task: Task): TaskQuadrant { if (task.quadrant) return task.quadrant if (task.priority === 'high' || task.priority === 'urgent') return 'urgent-important' if (task.status === 'in_progress') return 'not-urgent-important' if (task.priority === 'medium') return 'urgent-not-important' return 'not-urgent-not-important' } function buildIssueCommanderSummary(tasks: Task[]): ScheduleCenterCommanderSummary { const summary: ScheduleCenterCommanderSummary = { total: 0, queued: 0, running: 0, completed: 0, failed: 0, overall_status: 'idle', } tasks.forEach((task) => { const state = task.dispatch_status as TaskDispatchStatus if (state === 'idle') return summary.total += 1 if (state === 'queued') summary.queued += 1 if (state === 'running') summary.running += 1 if (state === 'completed') summary.completed += 1 if (state === 'failed') summary.failed += 1 }) if (summary.running > 0) { summary.overall_status = 'running' } else if (summary.queued > 0) { summary.overall_status = 'queued' } else if (summary.failed > 0 && summary.completed === 0) { summary.overall_status = 'failed' } return summary } export function useSidebarPlan( clientTimeRef: { value: Date }, loadDailyDigestFn: () => void, conversationsRef: Ref | Conversation[] = [], ) { const todayPlanDetail = ref(null) const monthPlanDays = ref([]) const issueTasks = ref([]) const selectedDate = ref(null) const conversationDateMap = computed(() => { const map = new Map() const conversations = Array.isArray(conversationsRef) ? conversationsRef : (conversationsRef.value ?? []) conversations.forEach((conv) => { map.set(formatDateKey(new Date(conv.updated_at)), true) }) return map }) const todayDateKey = computed(() => formatDateKey(clientTimeRef.value)) const monthPlanSummaryMap = computed(() => new Map(monthPlanDays.value.map((item) => [item.date, item]))) const issueStatusQuadrants = computed(() => ( Object.values(ISSUE_QUADRANT_META).map((meta) => ({ ...meta, tasks: issueTasks.value .filter((task) => deriveIssueQuadrant(task) === meta.id) .map((task) => ({ id: task.id, title: task.title, completed: task.status === 'done', status: task.status, dispatchStatus: task.dispatch_status, assigneeLabel: task.assignee_id ? `${task.assignee_type ?? 'owner'}:${task.assignee_id}` : (task.assignee_type ?? 'unassigned'), })), })) )) const issueCommanderSummary = computed(() => buildIssueCommanderSummary(issueTasks.value)) const calendarCells = computed(() => { const year = clientTimeRef.value.getFullYear() const month = clientTimeRef.value.getMonth() const daysInMonth = new Date(year, month + 1, 0).getDate() const firstDayOffset = (new Date(year, month, 1).getDay() + 6) % 7 const todayKey = todayDateKey.value const cells: Array<{ key: string; value: number | null; active: boolean; busy: boolean; selected: boolean; hasConversation: boolean }> = [] for (let index = 0; index < firstDayOffset; index += 1) { cells.push({ key: `blank-start-${index}`, value: null, active: false, busy: false, selected: false, hasConversation: false }) } for (let day = 1; day <= daysInMonth; day += 1) { const monthDate = new Date(year, month, day) const dateKey = formatDateKey(monthDate) const summary = monthPlanSummaryMap.value.get(dateKey) const busy = Boolean(summary && (summary.todo_total + summary.task_due_total + summary.goal_total + summary.reminder_total) > 0) const hasConv = conversationDateMap.value.get(dateKey) || false cells.push({ key: dateKey, value: day, active: dateKey === todayKey, busy, selected: dateKey === selectedDate.value, hasConversation: hasConv }) } while (cells.length % 7 !== 0) { cells.push({ key: `blank-end-${cells.length}`, value: null, active: false, busy: false, selected: false, hasConversation: false }) } return cells }) const calendarYear = computed(() => clientTimeRef.value.getFullYear()) const calendarMonth = computed(() => clientTimeRef.value.getMonth() + 1) const issueStatusCounters = computed(() => { const done = issueTasks.value.filter((item) => item.status === 'done').length const doing = issueTasks.value.filter((item) => item.status === 'in_progress').length const pending = issueTasks.value.filter((item) => item.status === 'todo').length const total = done + doing + pending return { done, doing, pending, total, completion: total > 0 ? Math.round((done / total) * 100) : 0 } }) const monthReviewStats = computed(() => monthPlanDays.value.reduce( (acc, item) => { acc.todoTotal += item.todo_total acc.todoCompleted += item.todo_completed acc.taskTotal += item.task_due_total acc.reminderTotal += item.reminder_total acc.goalTotal += item.goal_total acc.highPriorityTotal += item.high_priority_total if (item.todo_total + item.task_due_total + item.reminder_total + item.goal_total > 0) acc.activeDays += 1 return acc }, { todoTotal: 0, todoCompleted: 0, taskTotal: 0, reminderTotal: 0, goalTotal: 0, highPriorityTotal: 0, activeDays: 0 }, )) const sidebarWeekLabels = [ { label: 'M', isWeekend: false }, { label: 'T', isWeekend: false }, { label: 'W', isWeekend: false }, { label: 'T', isWeekend: false }, { label: 'F', isWeekend: false }, { label: 'S', isWeekend: true }, { label: 'S', isWeekend: true }, ] const sidebarStatusHeadline = computed(() => '') const sidebarStatusBreakdown = computed(() => [ { key: 'done', label: 'Completed', value: issueStatusCounters.value.done, tone: 'done' }, { key: 'doing', label: 'In Progress', value: issueStatusCounters.value.doing, tone: 'doing' }, { key: 'pending', label: 'Pending', value: issueStatusCounters.value.pending, tone: 'pending' }, { key: 'total', label: 'Total', value: issueStatusCounters.value.total, tone: 'total' }, ]) const sidebarFocusItems = computed(() => ( (todayPlanDetail.value?.focus_tasks ?? []).map((task) => ({ id: task.id, label: focusLabelByQuadrant(task.quadrant), title: task.title, meta: focusMeta(task.status, task.dispatch_status, task.assignee_type, task.assignee_id), tone: toneFromStatus(task.dispatch_status !== 'idle' ? task.dispatch_status : task.status), })) )) const sidebarReviewAchievements = computed(() => { const stats = monthReviewStats.value const items = [ stats.todoCompleted > 0 ? `Completed ${stats.todoCompleted} todos this month.` : '', stats.activeDays > 0 ? `${stats.activeDays} active planning days recorded this month.` : '', stats.highPriorityTotal > 0 ? `${stats.highPriorityTotal} high-priority items were tracked.` : '', ].filter(Boolean) if (items.length > 0) return items.slice(0, 3) return ['Monthly review data is still accumulating.'] }) const sidebarReviewReflections = computed(() => { const stats = monthReviewStats.value const pendingTodoCount = Math.max(stats.todoTotal - stats.todoCompleted, 0) const items = [ pendingTodoCount > 0 ? `${pendingTodoCount} todos are still open and may need to be broken down further.` : '', stats.highPriorityTotal >= 8 ? 'High-priority load is dense. Consider narrowing the active mainline.' : '', stats.reminderTotal >= Math.max(6, stats.activeDays) ? 'Reminder volume is high. A fixed review block may help reduce interruption cost.' : '', ].filter(Boolean) if (items.length > 0) return items.slice(0, 3) return ['Execution rhythm looks stable.'] }) const sidebarFeedItems = computed(() => [ { id: 'fallback-1', title: 'Task orchestration is replacing simple one-shot assistants in modern AI workflows.', meta: 'Industry' }, { id: 'fallback-2', title: 'Shared planning state across chat and execution surfaces is becoming a core product boundary.', meta: 'Product' }, { id: 'fallback-3', title: 'This feed is still placeholder content and can be replaced by a real RSS source later.', meta: 'System' }, ]) const topbarFeedItems = computed(() => ( sidebarFeedItems.value.length > 0 ? [...sidebarFeedItems.value, ...sidebarFeedItems.value] : [] )) async function loadIssueStatusSnapshot() { try { const response = await taskApi.list() issueTasks.value = response.data.filter((task) => task.status !== 'cancelled') } catch (err) { console.warn('Failed to load issue status snapshot:', err) issueTasks.value = [] } } async function loadDailyPlanSnapshot(date = new Date()) { const dateKey = formatDateKey(date) const monthKey = formatMonthKey(date) try { const [todayResponse, monthResponse] = await Promise.all([ scheduleCenterApi.date(dateKey), scheduleCenterApi.month(monthKey), ]) todayPlanDetail.value = todayResponse.data monthPlanDays.value = monthResponse.data.days } catch (err) { console.warn('Failed to load sidebar plan snapshot:', err) todayPlanDetail.value = null monthPlanDays.value = [] } } async function loadSidebarPlanSnapshot(date = new Date()) { await Promise.all([ loadDailyPlanSnapshot(date), loadIssueStatusSnapshot(), ]) } watch(todayDateKey, (next, previous) => { if (next === previous) return void loadDailyDigestFn() void loadSidebarPlanSnapshot(clientTimeRef.value) }) function selectCalendarDate(dateKey: string) { selectedDate.value = dateKey } onMounted(() => { void loadDailyDigestFn() void loadSidebarPlanSnapshot(clientTimeRef.value) if (typeof window !== 'undefined') { window.addEventListener('jarvis:today-status-refresh', handleExternalRefresh) } }) function handleExternalRefresh() { void loadSidebarPlanSnapshot(clientTimeRef.value) } onUnmounted(() => { if (typeof window !== 'undefined') { window.removeEventListener('jarvis:today-status-refresh', handleExternalRefresh) } }) return { todayPlanDetail, monthPlanDays, todayDateKey, monthPlanSummaryMap, calendarCells, calendarYear, calendarMonth, issueStatusCounters, monthReviewStats, sidebarWeekLabels, sidebarStatusHeadline, sidebarStatusBreakdown, sidebarFocusItems, issueStatusQuadrants, issueCommanderSummary, sidebarReviewAchievements, sidebarReviewReflections, sidebarFeedItems, topbarFeedItems, loadSidebarPlanSnapshot, sidebarCollapsedModules, selectedDate, selectCalendarDate, conversationDateMap, } }