Feature 3: Read-only Google Calendar panel
- CalendarEvent model with summary, start, end, location - New scope calendar.readonly in SCOPES - fetch_upcoming_events() in ApiClient (15 events, next 7 days) - Focus::Calendar variant, Tab cycle includes Calendar - Body layout: left column split into Tasks (flex) + Calendar (8) - render_calendar_panel with day headers and scroll - refresh_calendar() called on initial sync and Ctrl+R - Up/Down scroll Calendar panel when focused
This commit is contained in:
+14
-3
@@ -23,6 +23,7 @@ pub struct SyncStats {
|
||||
pub struct App {
|
||||
pub lists: Vec<TaskList>,
|
||||
pub tasks: Vec<Task>,
|
||||
pub calendar_events: Vec<CalendarEvent>,
|
||||
pub selected_list: usize,
|
||||
pub selected_task: usize,
|
||||
pub focus: Focus,
|
||||
@@ -38,6 +39,7 @@ pub struct App {
|
||||
pub task_list_scroll: u16,
|
||||
pub detail_scroll: u16,
|
||||
pub notes_scroll: u16,
|
||||
pub calendar_scroll: u16,
|
||||
pub db: Arc<Db>,
|
||||
#[allow(dead_code)]
|
||||
pub api_client: Arc<ApiClient>,
|
||||
@@ -66,7 +68,7 @@ pub enum SyncCommand {
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(db: Arc<Db>, api_client: Arc<ApiClient>, sync_tx: mpsc::Sender<SyncCommand>) -> Self {
|
||||
pub fn new(db: Arc<Db>, api_client: Arc<ApiClient>, sync_tx: mpsc::Sender<SyncCommand>, _calendar_events_shared: Arc<tokio::sync::Mutex<Vec<CalendarEvent>>>) -> Self {
|
||||
let has_token = api_client.has_token();
|
||||
let (auth_tx, auth_rx) = std_mpsc::channel();
|
||||
|
||||
@@ -91,6 +93,7 @@ impl App {
|
||||
Self {
|
||||
lists,
|
||||
tasks,
|
||||
calendar_events: Vec::new(),
|
||||
selected_list: 0,
|
||||
selected_task: 0,
|
||||
focus: Focus::Tabs,
|
||||
@@ -106,6 +109,7 @@ impl App {
|
||||
task_list_scroll: 0,
|
||||
detail_scroll: 0,
|
||||
notes_scroll: 0,
|
||||
calendar_scroll: 0,
|
||||
db,
|
||||
api_client,
|
||||
needs_auth: !has_token,
|
||||
@@ -282,7 +286,8 @@ impl App {
|
||||
self.focus = match self.focus {
|
||||
Focus::Tabs => Focus::TaskList,
|
||||
Focus::TaskList => Focus::Detail,
|
||||
Focus::Detail => Focus::Tabs,
|
||||
Focus::Detail => Focus::Calendar,
|
||||
Focus::Calendar => Focus::Tabs,
|
||||
};
|
||||
}
|
||||
KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
|
||||
@@ -305,6 +310,9 @@ impl App {
|
||||
Focus::Detail => {
|
||||
self.detail_scroll = self.detail_scroll.saturating_sub(1);
|
||||
}
|
||||
Focus::Calendar => {
|
||||
self.calendar_scroll = self.calendar_scroll.saturating_sub(1);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
KeyCode::Down => match self.focus {
|
||||
@@ -317,6 +325,9 @@ impl App {
|
||||
Focus::Detail => {
|
||||
self.detail_scroll += 1;
|
||||
}
|
||||
Focus::Calendar => {
|
||||
self.calendar_scroll += 1;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
@@ -729,7 +740,7 @@ impl App {
|
||||
self.load_tasks();
|
||||
}
|
||||
}
|
||||
Focus::TaskList | Focus::Detail => {
|
||||
Focus::TaskList | Focus::Detail | Focus::Calendar => {
|
||||
if !self.tasks.is_empty() && self.selected_task < self.tasks.len() {
|
||||
let task = &self.tasks[self.selected_task];
|
||||
let task_id = task.id.clone();
|
||||
|
||||
Reference in New Issue
Block a user