Преглед на файлове

Add a professional split Scheduler page with interactive calendar navigation and date-based meeting lists.

This keeps Scheduler integrated with existing meeting data refresh flow so selecting days, navigating months, and loading Zoom meetings stay in sync.

Made-with: Cursor
huzaifahayat12 преди 3 месеца
родител
ревизия
4e64fe8cb7
променени са 1 файла, в които са добавени 391 реда и са изтрити 9 реда
  1. 391 9
      zoom_app/ViewController.swift

+ 391 - 9
zoom_app/ViewController.swift

@@ -211,6 +211,16 @@ class ViewController: NSViewController {
     private weak var homeSearchField: NSTextField?
     private weak var homeSearchPill: NSView?
     private weak var homeSettingsView: NSView?
+    private weak var schedulerRootView: NSView?
+    private weak var schedulerMonthLabel: NSTextField?
+    private weak var schedulerCalendarGridStack: NSStackView?
+    private weak var schedulerSelectedDateLabel: NSTextField?
+    private weak var schedulerMeetingsListStack: NSStackView?
+    private weak var schedulerMeetingsEmptyLabel: NSTextField?
+    private weak var schedulerTodayButton: NSButton?
+    private var schedulerDayButtons: [Date: NSButton] = [:]
+    private var schedulerDateByButtonTag: [Int: Date] = [:]
+    private var nextSchedulerButtonTag: Int = 1
     private weak var settingsDarkModeSwitch: NSSwitch?
     private weak var settingsUpgradeButton: NSButton?
     private weak var settingsRestoreButton: NSButton?
@@ -244,6 +254,12 @@ class ViewController: NSViewController {
     private var lastKnownPremiumAccess = false
     private var allScheduledMeetings: [ScheduledMeeting] = []
     private var selectedMeetingsDayStart: Date = Calendar.current.startOfDay(for: Date())
+    private var schedulerVisibleMonthStart: Date = {
+        let calendar = Calendar.current
+        let today = Date()
+        let components = calendar.dateComponents([.year, .month], from: today)
+        return calendar.date(from: components) ?? calendar.startOfDay(for: today)
+    }()
     private var selectedHomeSidebarItem: String = "Home"
     private var homeSidebarRowViews: [String: NSView] = [:]
     private var homeSidebarIconViews: [String: NSImageView] = [:]
@@ -1586,6 +1602,8 @@ class ViewController: NSViewController {
                 meetingsStatusLabel?.stringValue = "Upcoming meetings"
                 emptyMeetingLabel?.stringValue = "No meetings match your search."
             }
+            applySchedulerMeetingsForSelectedDate()
+            renderSchedulerCalendarGrid()
             return
         }
 
@@ -1599,11 +1617,27 @@ class ViewController: NSViewController {
                 card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
             }
         }
+        applySchedulerMeetingsForSelectedDate()
+        renderSchedulerCalendarGrid()
+    }
+
+    private func meetingsForDay(_ dayStart: Date) -> [ScheduledMeeting] {
+        let calendar = Calendar.current
+        let safeDayStart = calendar.startOfDay(for: dayStart)
+        let dayEnd = calendar.date(byAdding: .day, value: 1, to: safeDayStart) ?? safeDayStart.addingTimeInterval(60 * 60 * 24)
+        return allScheduledMeetings
+            .filter { meeting in
+                meeting.start >= safeDayStart && meeting.start < dayEnd
+            }
+            .sorted(by: { $0.start < $1.start })
     }
 
     @MainActor
     private func setSelectedMeetingsDayStart(_ newDayStart: Date) {
-        selectedMeetingsDayStart = Calendar.current.startOfDay(for: newDayStart)
+        let calendar = Calendar.current
+        selectedMeetingsDayStart = calendar.startOfDay(for: newDayStart)
+        let monthComponents = calendar.dateComponents([.year, .month], from: selectedMeetingsDayStart)
+        schedulerVisibleMonthStart = calendar.date(from: monthComponents) ?? selectedMeetingsDayStart
         updateMeetingsDayUI()
         applyFilteredMeetings()
     }
@@ -1618,6 +1652,11 @@ class ViewController: NSViewController {
         let isToday = Calendar.current.isDate(dayStart, inSameDayAs: Date())
         meetingsTodayButton?.isEnabled = isToday == false
         meetingsTodayButton?.alphaValue = isToday ? 0.55 : 1.0
+        schedulerTodayButton?.isEnabled = isToday == false
+        schedulerTodayButton?.alphaValue = isToday ? 0.55 : 1.0
+        updateSchedulerDateHeader()
+        applySchedulerMeetingsForSelectedDate()
+        renderSchedulerCalendarGrid()
     }
 
     private func meetingsDayDisplayName(for dayStart: Date) -> String {
@@ -1654,6 +1693,166 @@ class ViewController: NSViewController {
         }
     }
 
+    private func updateSchedulerDateHeader() {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "MMMM yyyy"
+        schedulerMonthLabel?.stringValue = formatter.string(from: schedulerVisibleMonthStart)
+
+        let selectedFormatter = DateFormatter()
+        selectedFormatter.dateFormat = "EEEE, MMM d"
+        schedulerSelectedDateLabel?.stringValue = selectedFormatter.string(from: selectedMeetingsDayStart)
+    }
+
+    @MainActor
+    private func applySchedulerMeetingsForSelectedDate() {
+        guard let stack = schedulerMeetingsListStack else { return }
+        stack.arrangedSubviews.forEach { view in
+            stack.removeArrangedSubview(view)
+            view.removeFromSuperview()
+        }
+
+        let meetings = meetingsForDay(selectedMeetingsDayStart)
+        if meetings.isEmpty {
+            schedulerMeetingsEmptyLabel?.isHidden = false
+            schedulerMeetingsEmptyLabel?.stringValue = "No meetings scheduled for \(meetingsDayDisplayName(for: selectedMeetingsDayStart))."
+            return
+        }
+
+        schedulerMeetingsEmptyLabel?.isHidden = true
+        for meeting in meetings {
+            let card = makeMeetingRowCard(meeting)
+            stack.addArrangedSubview(card)
+            if card.constraints.contains(where: { $0.firstAttribute == .width }) == false {
+                card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
+            }
+        }
+    }
+
+    @MainActor
+    private func renderSchedulerCalendarGrid() {
+        guard let gridStack = schedulerCalendarGridStack else { return }
+        schedulerDayButtons = [:]
+        schedulerDateByButtonTag = [:]
+        nextSchedulerButtonTag = 1
+        gridStack.arrangedSubviews.forEach { row in
+            gridStack.removeArrangedSubview(row)
+            row.removeFromSuperview()
+        }
+
+        let calendar = Calendar.current
+        let monthStart = schedulerVisibleMonthStart
+        let monthRange = calendar.range(of: .day, in: .month, for: monthStart) ?? 1..<32
+        let monthWeekday = calendar.component(.weekday, from: monthStart)
+        let leadingSlots = monthWeekday - 1
+        let totalSlots = 42
+
+        for week in 0..<6 {
+            let row = NSStackView()
+            row.orientation = .horizontal
+            row.distribution = .fillEqually
+            row.spacing = 6
+            row.translatesAutoresizingMaskIntoConstraints = false
+            gridStack.addArrangedSubview(row)
+
+            for weekday in 0..<7 {
+                let cellIndex = (week * 7) + weekday
+                let dayOffset = cellIndex - leadingSlots
+                let dayDate = calendar.date(byAdding: .day, value: dayOffset, to: monthStart) ?? monthStart
+                let dayNumber = calendar.component(.day, from: dayDate)
+                let inCurrentMonth = monthRange.contains(dayNumber) && calendar.isDate(dayDate, equalTo: monthStart, toGranularity: .month)
+                let hasMeetings = meetingsForDay(dayDate).isEmpty == false
+                let button = makeSchedulerDayCell(
+                    day: dayNumber,
+                    date: dayDate,
+                    inCurrentMonth: inCurrentMonth,
+                    selected: calendar.isDate(dayDate, inSameDayAs: selectedMeetingsDayStart),
+                    isToday: calendar.isDateInToday(dayDate),
+                    hasMeetings: hasMeetings
+                )
+                row.addArrangedSubview(button)
+                schedulerDayButtons[calendar.startOfDay(for: dayDate)] = button
+            }
+        }
+
+        // Keep a consistent 6x7 grid footprint.
+        if totalSlots == 42 {
+            gridStack.needsLayout = true
+        }
+    }
+
+    private func makeSchedulerDayCell(
+        day: Int,
+        date: Date,
+        inCurrentMonth: Bool,
+        selected: Bool,
+        isToday: Bool,
+        hasMeetings: Bool
+    ) -> NSButton {
+        let button = NSButton(title: "\(day)", target: self, action: #selector(schedulerDayTapped(_:)))
+        button.isBordered = false
+        button.bezelStyle = .shadowlessSquare
+        button.focusRingType = .none
+        button.font = .systemFont(ofSize: 16, weight: selected ? .semibold : .regular)
+        button.alignment = .center
+        button.wantsLayer = true
+        button.layer?.cornerRadius = 16
+        button.layer?.borderWidth = selected ? 1.5 : (hasMeetings ? 1 : 0)
+        button.layer?.backgroundColor = selected
+            ? accentBlue.withAlphaComponent(palette.isDarkMode ? 0.92 : 0.16).cgColor
+            : NSColor.clear.cgColor
+        if selected {
+            button.layer?.borderColor = accentBlue.cgColor
+        } else if isToday {
+            button.layer?.borderColor = accentOrange.withAlphaComponent(0.8).cgColor
+            button.layer?.borderWidth = 1
+        } else if hasMeetings {
+            button.layer?.borderColor = accentBlue.withAlphaComponent(0.45).cgColor
+        } else {
+            button.layer?.borderColor = NSColor.clear.cgColor
+        }
+        button.contentTintColor = selected
+            ? primaryText
+            : (inCurrentMonth ? primaryText : mutedText.withAlphaComponent(0.75))
+        let tag = nextSchedulerButtonTag
+        nextSchedulerButtonTag += 1
+        button.tag = tag
+        schedulerDateByButtonTag[tag] = Calendar.current.startOfDay(for: date)
+        button.translatesAutoresizingMaskIntoConstraints = false
+        button.heightAnchor.constraint(equalToConstant: 34).isActive = true
+        return button
+    }
+
+    @objc private func schedulerDayTapped(_ sender: NSButton) {
+        guard let selectedDate = schedulerDateByButtonTag[sender.tag] else { return }
+        Task { @MainActor in
+            self.setSelectedMeetingsDayStart(selectedDate)
+        }
+    }
+
+    @objc private func schedulerPrevMonthTapped() {
+        let calendar = Calendar.current
+        let prev = calendar.date(byAdding: .month, value: -1, to: schedulerVisibleMonthStart) ?? schedulerVisibleMonthStart
+        let monthComponents = calendar.dateComponents([.year, .month], from: prev)
+        schedulerVisibleMonthStart = calendar.date(from: monthComponents) ?? prev
+        updateSchedulerDateHeader()
+        renderSchedulerCalendarGrid()
+    }
+
+    @objc private func schedulerNextMonthTapped() {
+        let calendar = Calendar.current
+        let next = calendar.date(byAdding: .month, value: 1, to: schedulerVisibleMonthStart) ?? schedulerVisibleMonthStart
+        let monthComponents = calendar.dateComponents([.year, .month], from: next)
+        schedulerVisibleMonthStart = calendar.date(from: monthComponents) ?? next
+        updateSchedulerDateHeader()
+        renderSchedulerCalendarGrid()
+    }
+
+    @objc private func schedulerTodayTapped() {
+        Task { @MainActor in
+            self.setSelectedMeetingsDayStart(Date())
+        }
+    }
+
     private func loadScheduledMeetings() async {
         if isLoadingMeetings { return }
         isLoadingMeetings = true
@@ -1682,7 +1881,7 @@ class ViewController: NSViewController {
                 } else if case ZoomOAuthError.missingClientSecret = error {
                     self.meetingsStatusLabel?.stringValue = "Zoom OAuth app not configured."
                     self.promptForZoomOAuthCredentialsIfNeeded()
-                } else if case ZoomOAuthError.missingRequiredScope(let scopeMessage) = error {
+                } else if case ZoomOAuthError.missingRequiredScope = error {
                     self.zoomOAuth.clearSavedTokens()
                     self.meetingsStatusLabel?.stringValue = "Zoom permissions are missing. Update your Zoom app scopes, then sign in again."
                 } else if case ZoomOAuthError.rateLimited(let retryAfterSeconds) = error {
@@ -3163,6 +3362,108 @@ class ViewController: NSViewController {
         let settingsView = makeSettingsView()
         settingsView.isHidden = selectedHomeSidebarItem != "Settings"
 
+        let schedulerRoot = NSView()
+        schedulerRoot.isHidden = selectedHomeSidebarItem != "Scheduler"
+
+        let schedulerSplit = NSStackView()
+        schedulerSplit.orientation = .horizontal
+        schedulerSplit.spacing = 14
+        schedulerSplit.distribution = .fillEqually
+        schedulerSplit.alignment = .top
+
+        let schedulerCalendarPanel = NSView()
+        schedulerCalendarPanel.wantsLayer = true
+        schedulerCalendarPanel.layer?.backgroundColor = secondaryCardBackground.withAlphaComponent(0.94).cgColor
+        schedulerCalendarPanel.layer?.cornerRadius = 16
+        schedulerCalendarPanel.layer?.borderWidth = 1
+        schedulerCalendarPanel.layer?.borderColor = palette.inputBorder.cgColor
+
+        let schedulerMeetingsPanel = NSView()
+        schedulerMeetingsPanel.wantsLayer = true
+        schedulerMeetingsPanel.layer?.backgroundColor = secondaryCardBackground.withAlphaComponent(0.94).cgColor
+        schedulerMeetingsPanel.layer?.cornerRadius = 16
+        schedulerMeetingsPanel.layer?.borderWidth = 1
+        schedulerMeetingsPanel.layer?.borderColor = palette.inputBorder.cgColor
+
+        let schedulerCalendarTopRow = NSStackView()
+        schedulerCalendarTopRow.orientation = .horizontal
+        schedulerCalendarTopRow.alignment = .centerY
+        schedulerCalendarTopRow.distribution = .fill
+        schedulerCalendarTopRow.spacing = 10
+
+        let schedulerConnectLabel = makeLabel("Connect calendar", size: 18, color: accentBlue, weight: .semibold, centered: false)
+        let schedulerPlusButton = NSButton(title: "", target: nil, action: nil)
+        schedulerPlusButton.isBordered = false
+        schedulerPlusButton.bezelStyle = .shadowlessSquare
+        schedulerPlusButton.focusRingType = .none
+        schedulerPlusButton.wantsLayer = true
+        schedulerPlusButton.layer?.backgroundColor = accentBlue.cgColor
+        schedulerPlusButton.layer?.cornerRadius = 14
+        schedulerPlusButton.contentTintColor = .white
+        schedulerPlusButton.toolTip = "Connect calendar"
+        if let image = NSImage(systemSymbolName: "plus", accessibilityDescription: "Connect calendar") {
+            schedulerPlusButton.image = image
+            schedulerPlusButton.imagePosition = .imageOnly
+        }
+        schedulerPlusButton.translatesAutoresizingMaskIntoConstraints = false
+        schedulerPlusButton.widthAnchor.constraint(equalToConstant: 28).isActive = true
+        schedulerPlusButton.heightAnchor.constraint(equalToConstant: 28).isActive = true
+
+        let schedulerMonthRow = NSStackView()
+        schedulerMonthRow.orientation = .horizontal
+        schedulerMonthRow.alignment = .centerY
+        schedulerMonthRow.spacing = 8
+
+        let schedulerPrevMonthButton = makeNavGlyphButton(symbol: "chevron.left", action: #selector(schedulerPrevMonthTapped), dimension: 18, pointSize: 10, toolTip: "Previous month")
+        let schedulerMonthLabel = makeLabel("-", size: 30, color: primaryText, weight: .semibold, centered: false)
+        let schedulerNextMonthButton = makeNavGlyphButton(symbol: "chevron.right", action: #selector(schedulerNextMonthTapped), dimension: 18, pointSize: 10, toolTip: "Next month")
+        [schedulerPrevMonthButton, schedulerMonthLabel, schedulerNextMonthButton].forEach { schedulerMonthRow.addArrangedSubview($0) }
+
+        let schedulerWeekdaysRow = NSStackView()
+        schedulerWeekdaysRow.orientation = .horizontal
+        schedulerWeekdaysRow.distribution = .fillEqually
+        schedulerWeekdaysRow.spacing = 4
+        let weekdaySymbols = ["S", "M", "T", "W", "T", "F", "S"]
+        for symbol in weekdaySymbols {
+            let weekdayLabel = makeLabel(symbol, size: 12, color: mutedText, weight: .medium, centered: true)
+            schedulerWeekdaysRow.addArrangedSubview(weekdayLabel)
+        }
+
+        let schedulerCalendarGrid = NSStackView()
+        schedulerCalendarGrid.orientation = .vertical
+        schedulerCalendarGrid.distribution = .fillEqually
+        schedulerCalendarGrid.spacing = 6
+
+        let schedulerRightTopRow = NSStackView()
+        schedulerRightTopRow.orientation = .horizontal
+        schedulerRightTopRow.alignment = .centerY
+        schedulerRightTopRow.spacing = 10
+
+        let schedulerSelectedDateLabel = makeLabel("-", size: 30, color: primaryText, weight: .semibold, centered: false)
+        let schedulerTodayButton = makeMeetingsDayChipButton(title: "Today", symbol: "calendar", action: #selector(schedulerTodayTapped))
+        schedulerTodayButton.toolTip = "Jump to today"
+        [schedulerTodayButton].forEach { schedulerRightTopRow.addArrangedSubview($0) }
+
+        let schedulerMeetingsHeader = makeLabel("Scheduled meetings", size: 13, color: secondaryText, weight: .medium, centered: false)
+
+        let schedulerMeetingsScroll = NSScrollView()
+        schedulerMeetingsScroll.drawsBackground = false
+        schedulerMeetingsScroll.hasVerticalScroller = true
+        schedulerMeetingsScroll.hasHorizontalScroller = false
+        schedulerMeetingsScroll.autohidesScrollers = true
+        let schedulerMeetingsDocument = FlippedView()
+        let schedulerMeetingsStack = NSStackView()
+        schedulerMeetingsStack.orientation = .vertical
+        schedulerMeetingsStack.spacing = 12
+        schedulerMeetingsStack.alignment = .leading
+        schedulerMeetingsStack.setContentHuggingPriority(.required, for: .vertical)
+        schedulerMeetingsStack.setContentCompressionResistancePriority(.required, for: .vertical)
+        schedulerMeetingsScroll.documentView = schedulerMeetingsDocument
+        schedulerMeetingsDocument.addSubview(schedulerMeetingsStack)
+
+        let schedulerEmptyLabel = makeLabel("No meetings scheduled for this date.", size: 15, color: secondaryText, weight: .regular, centered: true)
+        schedulerEmptyLabel.isHidden = true
+
         let contentColumn = NSView()
         contentColumn.translatesAutoresizingMaskIntoConstraints = false
         content.addSubview(topBar)
@@ -3181,10 +3482,20 @@ class ViewController: NSViewController {
         [searchIcon, searchField, searchHintLabel].forEach {
             searchPill.addSubview($0)
         }
-        [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, refreshMeetingsButton, placeholder, settingsView].forEach {
+        [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, refreshMeetingsButton, placeholder, settingsView, schedulerRoot].forEach {
             $0.translatesAutoresizingMaskIntoConstraints = false
             contentColumn.addSubview($0)
         }
+        [schedulerSplit, schedulerCalendarPanel, schedulerMeetingsPanel, schedulerCalendarTopRow, schedulerConnectLabel, schedulerPlusButton, schedulerMonthRow, schedulerWeekdaysRow, schedulerCalendarGrid, schedulerRightTopRow, schedulerSelectedDateLabel, schedulerTodayButton, schedulerMeetingsHeader, schedulerMeetingsScroll, schedulerEmptyLabel, schedulerMeetingsDocument, schedulerMeetingsStack].forEach {
+            $0.translatesAutoresizingMaskIntoConstraints = false
+        }
+        schedulerRoot.addSubview(schedulerSplit)
+        [schedulerCalendarPanel, schedulerMeetingsPanel].forEach { schedulerSplit.addArrangedSubview($0) }
+        [schedulerCalendarTopRow, schedulerMonthRow, schedulerWeekdaysRow, schedulerCalendarGrid].forEach { schedulerCalendarPanel.addSubview($0) }
+        schedulerCalendarTopRow.addArrangedSubview(schedulerConnectLabel)
+        schedulerCalendarTopRow.addArrangedSubview(NSView())
+        schedulerCalendarTopRow.addArrangedSubview(schedulerPlusButton)
+        [schedulerSelectedDateLabel, schedulerRightTopRow, schedulerMeetingsHeader, schedulerMeetingsScroll, schedulerEmptyLabel].forEach { schedulerMeetingsPanel.addSubview($0) }
         topBar.translatesAutoresizingMaskIntoConstraints = false
         topBarDivider.translatesAutoresizingMaskIntoConstraints = false
         meetingsDocument.translatesAutoresizingMaskIntoConstraints = false
@@ -3292,7 +3603,60 @@ class ViewController: NSViewController {
             settingsView.topAnchor.constraint(equalTo: contentColumn.topAnchor),
             settingsView.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor),
             settingsView.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor),
-            settingsView.bottomAnchor.constraint(equalTo: contentColumn.bottomAnchor)
+            settingsView.bottomAnchor.constraint(equalTo: contentColumn.bottomAnchor),
+
+            schedulerRoot.topAnchor.constraint(equalTo: contentColumn.topAnchor, constant: 8),
+            schedulerRoot.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 8),
+            schedulerRoot.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -8),
+            schedulerRoot.bottomAnchor.constraint(equalTo: contentColumn.bottomAnchor, constant: -8),
+
+            schedulerSplit.topAnchor.constraint(equalTo: schedulerRoot.topAnchor),
+            schedulerSplit.leadingAnchor.constraint(equalTo: schedulerRoot.leadingAnchor),
+            schedulerSplit.trailingAnchor.constraint(equalTo: schedulerRoot.trailingAnchor),
+            schedulerSplit.bottomAnchor.constraint(equalTo: schedulerRoot.bottomAnchor),
+
+            schedulerCalendarTopRow.topAnchor.constraint(equalTo: schedulerCalendarPanel.topAnchor, constant: 18),
+            schedulerCalendarTopRow.leadingAnchor.constraint(equalTo: schedulerCalendarPanel.leadingAnchor, constant: 16),
+            schedulerCalendarTopRow.trailingAnchor.constraint(equalTo: schedulerCalendarPanel.trailingAnchor, constant: -16),
+
+            schedulerMonthRow.topAnchor.constraint(equalTo: schedulerCalendarTopRow.bottomAnchor, constant: 18),
+            schedulerMonthRow.leadingAnchor.constraint(equalTo: schedulerCalendarPanel.leadingAnchor, constant: 16),
+            schedulerMonthRow.trailingAnchor.constraint(lessThanOrEqualTo: schedulerCalendarPanel.trailingAnchor, constant: -16),
+
+            schedulerWeekdaysRow.topAnchor.constraint(equalTo: schedulerMonthRow.bottomAnchor, constant: 14),
+            schedulerWeekdaysRow.leadingAnchor.constraint(equalTo: schedulerCalendarPanel.leadingAnchor, constant: 16),
+            schedulerWeekdaysRow.trailingAnchor.constraint(equalTo: schedulerCalendarPanel.trailingAnchor, constant: -16),
+
+            schedulerCalendarGrid.topAnchor.constraint(equalTo: schedulerWeekdaysRow.bottomAnchor, constant: 10),
+            schedulerCalendarGrid.leadingAnchor.constraint(equalTo: schedulerCalendarPanel.leadingAnchor, constant: 16),
+            schedulerCalendarGrid.trailingAnchor.constraint(equalTo: schedulerCalendarPanel.trailingAnchor, constant: -16),
+            schedulerCalendarGrid.bottomAnchor.constraint(equalTo: schedulerCalendarPanel.bottomAnchor, constant: -16),
+            schedulerCalendarGrid.heightAnchor.constraint(greaterThanOrEqualToConstant: 280),
+
+            schedulerSelectedDateLabel.topAnchor.constraint(equalTo: schedulerMeetingsPanel.topAnchor, constant: 18),
+            schedulerSelectedDateLabel.leadingAnchor.constraint(equalTo: schedulerMeetingsPanel.leadingAnchor, constant: 16),
+            schedulerSelectedDateLabel.trailingAnchor.constraint(lessThanOrEqualTo: schedulerRightTopRow.leadingAnchor, constant: -10),
+
+            schedulerRightTopRow.centerYAnchor.constraint(equalTo: schedulerSelectedDateLabel.centerYAnchor),
+            schedulerRightTopRow.trailingAnchor.constraint(equalTo: schedulerMeetingsPanel.trailingAnchor, constant: -16),
+
+            schedulerMeetingsHeader.topAnchor.constraint(equalTo: schedulerSelectedDateLabel.bottomAnchor, constant: 12),
+            schedulerMeetingsHeader.leadingAnchor.constraint(equalTo: schedulerMeetingsPanel.leadingAnchor, constant: 16),
+            schedulerMeetingsHeader.trailingAnchor.constraint(equalTo: schedulerMeetingsPanel.trailingAnchor, constant: -16),
+
+            schedulerMeetingsScroll.topAnchor.constraint(equalTo: schedulerMeetingsHeader.bottomAnchor, constant: 10),
+            schedulerMeetingsScroll.leadingAnchor.constraint(equalTo: schedulerMeetingsPanel.leadingAnchor, constant: 14),
+            schedulerMeetingsScroll.trailingAnchor.constraint(equalTo: schedulerMeetingsPanel.trailingAnchor, constant: -14),
+            schedulerMeetingsScroll.bottomAnchor.constraint(equalTo: schedulerMeetingsPanel.bottomAnchor, constant: -14),
+
+            schedulerMeetingsDocument.widthAnchor.constraint(equalTo: schedulerMeetingsScroll.contentView.widthAnchor),
+            schedulerMeetingsStack.topAnchor.constraint(equalTo: schedulerMeetingsDocument.topAnchor),
+            schedulerMeetingsStack.leadingAnchor.constraint(equalTo: schedulerMeetingsDocument.leadingAnchor),
+            schedulerMeetingsStack.trailingAnchor.constraint(equalTo: schedulerMeetingsDocument.trailingAnchor),
+            schedulerMeetingsStack.bottomAnchor.constraint(lessThanOrEqualTo: schedulerMeetingsDocument.bottomAnchor),
+
+            schedulerEmptyLabel.centerXAnchor.constraint(equalTo: schedulerMeetingsPanel.centerXAnchor),
+            schedulerEmptyLabel.centerYAnchor.constraint(equalTo: schedulerMeetingsPanel.centerYAnchor)
         ])
 
         timeLabel = timeTitle
@@ -3304,6 +3668,13 @@ class ViewController: NSViewController {
         homeMeetingsPanel = panel
         homePlaceholderLabel = placeholder
         homeSettingsView = settingsView
+        schedulerRootView = schedulerRoot
+        self.schedulerMonthLabel = schedulerMonthLabel
+        schedulerCalendarGridStack = schedulerCalendarGrid
+        self.schedulerSelectedDateLabel = schedulerSelectedDateLabel
+        schedulerMeetingsListStack = schedulerMeetingsStack
+        schedulerMeetingsEmptyLabel = schedulerEmptyLabel
+        self.schedulerTodayButton = schedulerTodayButton
         meetingsDayHeaderLabel = panelHeader
         meetingsListStack = meetingsStack
         meetingsStatusLabel = meetingsStatus
@@ -3315,6 +3686,9 @@ class ViewController: NSViewController {
         updateClock()
         updateMeetingsDayUI()
         applyFilteredMeetings()
+        renderSchedulerCalendarGrid()
+        updateSchedulerDateHeader()
+        applySchedulerMeetingsForSelectedDate()
 
         homeSearchField = searchField
         homeSearchPill = searchPill
@@ -3323,7 +3697,9 @@ class ViewController: NSViewController {
             object: searchField,
             queue: .main
         ) { [weak self] _ in
-            self?.applyFilteredMeetings()
+            Task { @MainActor [weak self] in
+                self?.applyFilteredMeetings()
+            }
             updateSearchHintVisibility()
         }
 
@@ -3591,10 +3967,9 @@ class ViewController: NSViewController {
     private func updateSelectedHomeSectionUI() {
         let isHome = selectedHomeSidebarItem == "Home"
         let isSettings = selectedHomeSidebarItem == "Settings"
-        let title = selectedHomeSidebarItem
-
-        homeWelcomeLabel?.stringValue = title
-        homeWelcomeLabel?.isHidden = isSettings
+        let isScheduler = selectedHomeSidebarItem == "Scheduler"
+        homeWelcomeLabel?.stringValue = "Home"
+        homeWelcomeLabel?.isHidden = (isHome == false) || isSettings
 
         let dashboardViews: [NSView?] = [
             homeTimeLabelView,
@@ -3620,6 +3995,7 @@ class ViewController: NSViewController {
             emptyMeetingLabel?.isHidden = hasMeetingCards
         }
         homeSettingsView?.isHidden = isSettings == false
+        schedulerRootView?.isHidden = isScheduler == false
 
         if isHome {
             homePlaceholderLabel?.isHidden = true
@@ -3627,6 +4003,12 @@ class ViewController: NSViewController {
             // Keep non-Home pages empty for now.
             homePlaceholderLabel?.isHidden = true
         }
+
+        if isScheduler {
+            updateSchedulerDateHeader()
+            applySchedulerMeetingsForSelectedDate()
+            renderSchedulerCalendarGrid()
+        }
     }
 
     private func sidebarSymbolName(for item: String, filled: Bool = false) -> String {