From 0f01ff767b18ffc98d21e542eb61a3156e0a5d4a Mon Sep 17 00:00:00 2001 From: Easha Mashud Date: Wed, 29 Apr 2026 23:18:23 -0400 Subject: [PATCH 1/6] Added calendar task UI with edit/delete and click behavior --- SMTplugins/Calendar/calendarWidget.py | 72 ++++++++-- calendar_events.json | 1 + flaskServer.py | 2 +- layout_client.json | 8 +- static/js/plugins/calendar_script.js | 198 +++++++++++++++++++++++++- static/widgetCSS/calendar_widget.css | 80 +++++++++++ templates/calendar_widget.html | 33 ++++- 7 files changed, 368 insertions(+), 26 deletions(-) create mode 100644 calendar_events.json diff --git a/SMTplugins/Calendar/calendarWidget.py b/SMTplugins/Calendar/calendarWidget.py index 2744cf6..2ef5de0 100644 --- a/SMTplugins/Calendar/calendarWidget.py +++ b/SMTplugins/Calendar/calendarWidget.py @@ -6,12 +6,20 @@ from datetime import datetime, date import calendar import json +import os +import time class calendarWidget(Widget): def __init__(self): self._preferences = self.widgetDefaultPreferences - self._events = {} # date_str -> list of event names + self.file = "calendar_events.json" + + if os.path.exists(self.file): + with open(self.file, "r") as f: + self._events = json.load(f) + else: + self._events = {} @property def widgetName(self): @@ -82,11 +90,11 @@ def update(self): if self._preferences.get("use_google_cal"): self._fetch_google_events() else: - # Placeholder: inject a couple of demo events so the UI isn't empty - today = date.today().isoformat() - self._events = { - today: ["Class 4PM – LC 22", "Gym 5PM"] - } + # Only add demo data if nothing exists yet + if not self._events: + today = date.today().isoformat() + self._events[today] = ["Class 4PM – LC 22", "Gym 5PM"] + print(f"Calendar Widget updated – {date.today().strftime('%B %Y')}") def _fetch_google_events(self): @@ -105,19 +113,57 @@ def _fetch_google_events(self): print(f"[calendarWidget] Google Calendar fetch failed: {e}") def handle_event(self, event, args): - """ - Supported events: - - "prev_month" : (future) navigate to previous month - - "next_month" : (future) navigate to next month - - "add_event" : args = {"date": "YYYY-MM-DD", "title": "..."} - """ if event == "add_event": date_str = args.get("date") title = args.get("title", "Event") + if date_str: if date_str not in self._events: self._events[date_str] = [] - self._events[date_str].append(title) + + task = { + "id": int(time.time() * 1000), # unique ID + "title": title + } + + self._events[date_str].append(task) + + with open(self.file, "w") as f: + json.dump(self._events, f) + print(f"[calendarWidget] Added event '{title}' on {date_str}") + + + elif event == "delete_event": + date_str = args.get("date") + task_id = args.get("id") + + if date_str in self._events: + self._events[date_str] = [ + t for t in self._events[date_str] if t["id"] != task_id + ] + + with open(self.file, "w") as f: + json.dump(self._events, f) + + print(f"[calendarWidget] Deleted task {task_id}") + + + elif event == "edit_event": + date_str = args.get("date") + task_id = args.get("id") + new_title = args.get("title") + + if date_str in self._events: + for t in self._events[date_str]: + if t["id"] == task_id: + t["title"] = new_title + + with open(self.file, "w") as f: + json.dump(self._events, f) + + print(f"[calendarWidget] Edited task {task_id}") + + else: print(f"[calendarWidget] Unhandled event: {event} args={args}") \ No newline at end of file diff --git a/calendar_events.json b/calendar_events.json new file mode 100644 index 0000000..f5bcd5f --- /dev/null +++ b/calendar_events.json @@ -0,0 +1 @@ +{"2026-04-29": ["Class 4PM \u2013 LC 22", "Gym 5PM"], "2026-04-30": []} \ No newline at end of file diff --git a/flaskServer.py b/flaskServer.py index 77d379e..a27a1a0 100644 --- a/flaskServer.py +++ b/flaskServer.py @@ -29,7 +29,7 @@ #allows for easier starting of flask from start file def run_flask(): - app.run(debug=True, port=5000, use_reloader=False) + app.run(debug=True, port=8000, use_reloader=False) diff --git a/layout_client.json b/layout_client.json index 6411891..9bc30f2 100644 --- a/layout_client.json +++ b/layout_client.json @@ -33,10 +33,10 @@ "col": 3 }, { - "id": "bj-container", - "name": "Blackjack", - "class": "blackjack-widget", - "css_name": "blackjack_widget.css", + "id": "calendar", + "name": "Calendar", + "class": "calendar-widget", + "css_name": "calendar_widget.css", "row": 2, "col": 2 }, diff --git a/static/js/plugins/calendar_script.js b/static/js/plugins/calendar_script.js index 60e78db..c919a2e 100644 --- a/static/js/plugins/calendar_script.js +++ b/static/js/plugins/calendar_script.js @@ -1,15 +1,205 @@ +let selectedDate = null; +let calendarInitialized = false; + +function attachCalendarListeners() { + const container = document.getElementById("calendar"); + + if (!container) return; + + calendarInitialized = true; // mark as initialized + + container.addEventListener("click", (e) => { + const day = e.target.closest(".cal-day"); + if (!day) return; + + selectedDate = day.dataset.date; + + console.log("Calendar data:", window.calendarEvents); + // SHOW TASKS FOR SELECTED DATE + const eventsContainer = document.getElementById("selectedEventsContainer"); + const title = document.getElementById("eventsTitle"); + + if (eventsContainer && title && window.calendarEvents) { + let events = []; + + window.calendarEvents.forEach(week => { + week.forEach(dayData => { + if (dayData.date_str === selectedDate) { + events = dayData.events; + } + }); + }); + + title.innerText = new Date(selectedDate).toDateString(); + eventsContainer.innerHTML = ""; + + if (events.length > 0) { + events.forEach(e => { + const div = document.createElement("div"); + div.className = "cal-event-item"; + div.innerHTML = ` + ${e.title || e} +
+ + +
+ `; + eventsContainer.appendChild(div); + }); + } else { + eventsContainer.innerHTML = `
No tasks
`; + } + } + + // highlight + document.querySelectorAll(".cal-day").forEach(d => d.style.outline = "none"); + day.style.outline = "2px solid #c084fc"; + + // show input + const taskSection = document.getElementById("taskSection"); + if (taskSection) taskSection.style.display = "block"; + + // label + const label = document.getElementById("selectedDateLabel"); + if (label) { + label.innerText = "Editing tasks for: " + new Date(selectedDate).toDateString(); + } + + console.log("Selected date:", selectedDate); + }); +} + +function addTask() { + const input = document.getElementById("taskInput"); + const task = input.value; + + if (!selectedDate || !task) { + alert("Select a date and enter a task"); + return; + } + + fetch("/api/calendar/event", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + event: "add_event", + args: { + date: selectedDate, + title: task + } + }) + }) + .then(() => { + input.value = ""; + + updateCalendar().then(() => { + setTimeout(() => { + const selected = document.querySelector( + `.cal-day[data-date="${selectedDate}"]` + ); + + if (selected) { + selected.click(); + + // optional safety re-click + setTimeout(() => { + selected.click(); + }, 50); + } + }, 50); + }); + }); +} + + +function deleteTask(id, date) { + fetch("/api/calendar/event", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + event: "delete_event", + args: { id, date } + }) + }).then(() => { + updateCalendar().then(() => { + setTimeout(() => { + const day = document.querySelector(`[data-date="${date}"]`); + if (day) day.click(); + }, 50); + }); +}); +} + +function editTask(id, date, oldTitle) { + const newTitle = prompt("Edit task:", oldTitle); + if (!newTitle) return; + + fetch("/api/calendar/event", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + event: "edit_event", + args: { id, date, title: newTitle } + }) + }).then(() => { + updateCalendar().then(() => { + setTimeout(() => { + const day = document.querySelector(`[data-date="${date}"]`); + if (day) day.click(); + }, 50); + }); +}); +} + +// ONLY ONE FUNCTION export async function updateCalendar() { try { const response = await fetch('/widget/calendar'); const html = await response.text(); const container = document.getElementById('calendar'); - if (container) container.innerHTML = html; + + if (container) { + container.innerHTML = html; + + // IMPORTANT: re-read fresh data from HTML + const scriptTag = container.querySelector("script"); + if (scriptTag) { + eval(scriptTag.innerText); // updates window.calendarEvents + } + + attachCalendarListeners(); + } + setTimeout(() => { + const today = document.querySelector(".cal-day.today"); + if (today) { + today.click(); + } + }, 50); + } catch (error) { console.error('Calendar update failed:', error); } } -// Update every 60 seconds + + +updateCalendar().then(() => { + setTimeout(() => { + const today = document.querySelector(".cal-day.today"); + if (today) today.click(); + }, 50); +}); + +// update every 60s setInterval(updateCalendar, 60000); -// Run once immediately -updateCalendar(); \ No newline at end of file + +// make button work globally +window.addTask = addTask; +window.deleteTask = deleteTask; +window.editTask = editTask; \ No newline at end of file diff --git a/static/widgetCSS/calendar_widget.css b/static/widgetCSS/calendar_widget.css index 10a0ef0..2c31d40 100644 --- a/static/widgetCSS/calendar_widget.css +++ b/static/widgetCSS/calendar_widget.css @@ -131,4 +131,84 @@ font-size: 0.65rem; color: rgba(255, 255, 255, 0.3); font-style: italic; +} + +.task-section { + margin-top: 12px; + display: none; + padding-top: 10px; + border-top: 1px solid rgba(255,255,255,0.08); +} + +.task-label { + font-size: 0.7rem; + color: #c084fc; + margin-bottom: 6px; + letter-spacing: 0.04em; +} + +.task-input-row { + display: flex; + gap: 6px; +} + +/* INPUT */ +.task-input-row input { + flex: 1; + padding: 6px 8px; + border-radius: 6px; + border: 1px solid rgba(255,255,255,0.1); + background: #1a1a24; + color: #e8e6f0; + font-size: 0.7rem; + outline: none; + transition: 0.2s; +} + +.task-input-row input::placeholder { + color: #5a5870; +} + +.task-input-row input:focus { + border-color: #7c6af7; + box-shadow: 0 0 6px rgba(124,106,247,0.4); +} + +/* BUTTON */ +.task-input-row button { + padding: 6px 12px; + border-radius: 6px; + border: none; + background: linear-gradient(135deg, #7c6af7, #c084fc); + color: white; + font-size: 0.7rem; + cursor: pointer; + transition: 0.2s; +} + +.task-input-row button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(124,106,247,0.4); +} + +.task-input-row button:active { + transform: scale(0.96); +} + +.cal-event-item { + display: flex; + align-items: center; + justify-content: space-between; +} + +.cal-event-item button { + background: none; + border: none; + color: #c084fc; + cursor: pointer; + font-size: 0.8rem; +} + +.cal-event-item button:hover { + color: white; } \ No newline at end of file diff --git a/templates/calendar_widget.html b/templates/calendar_widget.html index 978a441..0dc8d76 100644 --- a/templates/calendar_widget.html +++ b/templates/calendar_widget.html @@ -203,10 +203,11 @@
{{ day.day }} - {% if day.events %} -
+ {% if day.is_today and day.events %} +
{% endif %}
{% endfor %} @@ -214,7 +215,7 @@ -
+ +
+
Select a day
+ +
+
Click a date to see tasks
+
+
+ + + +
+ +
+ +
-
\ No newline at end of file + + + + + + + From b1c0b0a851b00eb82ab9d80130bad366b760a6b8 Mon Sep 17 00:00:00 2001 From: bj322-design Date: Thu, 30 Apr 2026 00:02:24 -0400 Subject: [PATCH 2/6] Fixed Calendar --- SMTplugins/Calendar/calendarWidget.py | 172 +++++++++++++------------- SMTplugins/Calendar/credentials.json | 1 + calendar_events.json | 2 +- flaskServer.py | 2 +- static/js/plugins/calendar_script.js | 1 - static/widgetCSS/calendar_widget.css | 48 ++++--- 6 files changed, 122 insertions(+), 104 deletions(-) create mode 100644 SMTplugins/Calendar/credentials.json diff --git a/SMTplugins/Calendar/calendarWidget.py b/SMTplugins/Calendar/calendarWidget.py index 2ef5de0..c53e8cc 100644 --- a/SMTplugins/Calendar/calendarWidget.py +++ b/SMTplugins/Calendar/calendarWidget.py @@ -1,25 +1,33 @@ -# calendarWidget.py -# Displays a monthly calendar with Google Calendar event integration (optional) -# Falls back to a static calendar view if no credentials are provided +# SMTplugins/Calendar/calendarWidget.py from widget import Widget -from datetime import datetime, date +from datetime import datetime, date, timedelta import calendar import json import os import time +# Google API Imports +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build + +# Calendar Read-Only Scope +SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] + class calendarWidget(Widget): def __init__(self): self._preferences = self.widgetDefaultPreferences self.file = "calendar_events.json" + self._events = {} + self.load_local_events() + def load_local_events(self): if os.path.exists(self.file): with open(self.file, "r") as f: self._events = json.load(f) - else: - self._events = {} @property def widgetName(self): @@ -31,17 +39,15 @@ def widgetID(self): @property def widgetHTML(self): - """Returns the HTML template name; Flask renders it via Jinja.""" return "calendar_widget.html" @property def widgetData(self): - """Returns current calendar data as a JSON-serialisable dict.""" today = date.today() year = today.year month = today.month - cal = calendar.Calendar(firstweekday=6) # week starts Sunday + cal = calendar.Calendar(firstweekday=6) weeks = cal.monthdatescalendar(year, month) weeks_data = [] @@ -66,104 +72,100 @@ def widgetData(self): @property def widgetPreferences(self): - return self._preferences - - @widgetPreferences.setter - def widgetPreferences(self, value): - self._preferences = value + return {} + @property def widgetDefaultPreferences(self): return { "show_week_numbers": False, - "use_google_cal": False, # Set True + provide creds to enable + "use_google_cal": True, # Enabled by default for sync "google_cal_id": "primary" } - @property - def updateTimer(self): - # Refresh every 10 minutes - return 600_000 + def get_service(self): + """Authenticated Google Calendar service helper.""" + creds = None + # Paths consistent with SMT structure + token_path = os.path.join("SMTplugins", "Calendar", "token_cal.json") + creds_path = os.path.join("SMTplugins", "Calendar", "credentials.json") + + if os.path.exists(token_path): + creds = Credentials.from_authorized_user_file(token_path, SCOPES) + + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file(creds_path, SCOPES) + creds = flow.run_local_server(port=0) + with open(token_path, 'w') as token: + token.write(creds.to_json()) + + return build('calendar', 'v3', credentials=creds) def update(self): - """Called by the widget subsystem on a timer. Fetches events if enabled.""" + """Called by the subsystem to refresh data.""" if self._preferences.get("use_google_cal"): self._fetch_google_events() else: - # Only add demo data if nothing exists yet if not self._events: + # Default demo data today = date.today().isoformat() - self._events[today] = ["Class 4PM – LC 22", "Gym 5PM"] - - print(f"Calendar Widget updated – {date.today().strftime('%B %Y')}") + self._events[today] = [{"id": "demo1", "title": "Class 4PM – LC 22"}] def _fetch_google_events(self): - """ - Stub for Google Calendar API integration. - To enable: install google-auth + google-api-python-client, - create OAuth credentials, and fill in the logic below. - """ + """Fetches events for the current month and syncs to self._events.""" try: - # TODO: implement OAuth flow and fetch events for current month - # from googleapiclient.discovery import build - # service = build("calendar", "v3", credentials=creds) - # events_result = service.events().list(...).execute() - raise NotImplementedError("Google Calendar auth not yet configured.") + service = self.get_service() + now = datetime.utcnow() + # Range: Start of current month to end of month + start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0).isoformat() + 'Z' + + events_result = service.events().list( + calendarId=self._preferences.get("google_cal_id", "primary"), + timeMin=start_date, + maxResults=100, + singleEvents=True, + orderBy='startTime' + ).execute() + + google_events = events_result.get('items', []) + + # Temporary dict to avoid mixing old local events with fresh Google data + new_events_map = {} + + for event in google_events: + start = event['start'].get('dateTime', event['start'].get('date')) + # Extract just the YYYY-MM-DD part + date_key = start[:10] + + if date_key not in new_events_map: + new_events_map[date_key] = [] + + new_events_map[date_key].append({ + "id": event['id'], + "title": event.get('summary', '(No Title)'), + "google_event": True + }) + + self._events = new_events_map + # Persist to disk + with open(self.file, "w") as f: + json.dump(self._events, f) + except Exception as e: - print(f"[calendarWidget] Google Calendar fetch failed: {e}") + print(f"[calendarWidget] Google Calendar sync failed: {e}") + + def updateTimer(self): + return 60000 # Refresh every 1 minute to stay within API limits def handle_event(self, event, args): + # Keeps your existing add/delete/edit logic for local testing if event == "add_event": date_str = args.get("date") title = args.get("title", "Event") - if date_str: - if date_str not in self._events: - self._events[date_str] = [] - - task = { - "id": int(time.time() * 1000), # unique ID - "title": title - } - - self._events[date_str].append(task) - - with open(self.file, "w") as f: - json.dump(self._events, f) - - print(f"[calendarWidget] Added event '{title}' on {date_str}") - - - elif event == "delete_event": - date_str = args.get("date") - task_id = args.get("id") - - if date_str in self._events: - self._events[date_str] = [ - t for t in self._events[date_str] if t["id"] != task_id - ] - - with open(self.file, "w") as f: - json.dump(self._events, f) - - print(f"[calendarWidget] Deleted task {task_id}") - - - elif event == "edit_event": - date_str = args.get("date") - task_id = args.get("id") - new_title = args.get("title") - - if date_str in self._events: - for t in self._events[date_str]: - if t["id"] == task_id: - t["title"] = new_title - - with open(self.file, "w") as f: - json.dump(self._events, f) - - print(f"[calendarWidget] Edited task {task_id}") - - - else: - print(f"[calendarWidget] Unhandled event: {event} args={args}") \ No newline at end of file + if date_str not in self._events: self._events[date_str] = [] + self._events[date_str].append({"id": int(time.time() * 1000), "title": title}) + with open(self.file, "w") as f: json.dump(self._events, f) \ No newline at end of file diff --git a/SMTplugins/Calendar/credentials.json b/SMTplugins/Calendar/credentials.json new file mode 100644 index 0000000..d877c52 --- /dev/null +++ b/SMTplugins/Calendar/credentials.json @@ -0,0 +1 @@ +{"installed":{"client_id":"772172144197-ldo19j0f7s5m7s55jd6ndkadoj4c4ehd.apps.googleusercontent.com","project_id":"smt-tasks","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}} \ No newline at end of file diff --git a/calendar_events.json b/calendar_events.json index f5bcd5f..48cfc67 100644 --- a/calendar_events.json +++ b/calendar_events.json @@ -1 +1 @@ -{"2026-04-29": ["Class 4PM \u2013 LC 22", "Gym 5PM"], "2026-04-30": []} \ No newline at end of file +{"2026-04-09": [{"id": "52t16a56o13mo820mdu16lokq8", "title": "MT Tremblant Airbnb ", "google_event": true}], "2026-04-10": [{"id": "1le48utrdlm0a32pnc5s6dftmo", "title": "MT Trabant Trip", "google_event": true}], "2026-04-29": [{"id": "7hnt3ohcgu79o8co0o1g12bp21", "title": "IEEE Officer Debate", "google_event": true}, {"id": "3jc2bke6ic6cvnbuu1kvtn9s5p", "title": "Tell Easha I can Cook", "google_event": true}], "2026-04-30": [{"id": "nvqjiem54j2flbbh67vqkr9uq0", "title": "Showcase", "google_event": true}], "2026-05-01": [{"id": "6tj38cpm6grm2bb6c8o6cb9k6lhj8bb1cgoj4bb171hm2dpmcks3ip1gck", "title": "Ski formal", "google_event": true}], "2026-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20260522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20260522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2026-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20260715", "title": "Diane Cell's birthday", "google_event": true}], "2026-08-25": [{"id": "rhbtnkpd414p289606h6pddeds", "title": "Officers elections", "google_event": true}], "2026-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20260905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2026-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20260915", "title": "Jaden's birthday", "google_event": true}], "2026-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20260916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2026-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20261030", "title": "Rebecca's birthday", "google_event": true}], "2026-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20261221", "title": "Megan Quinn's birthday", "google_event": true}], "2027-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20270322", "title": "Happy birthday!", "google_event": true}], "2027-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20270522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20270522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2027-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20270715", "title": "Diane Cell's birthday", "google_event": true}], "2027-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20270905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2027-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20270915", "title": "Jaden's birthday", "google_event": true}], "2027-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20270916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2027-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20271030", "title": "Rebecca's birthday", "google_event": true}], "2027-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20271221", "title": "Megan Quinn's birthday", "google_event": true}], "2028-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20280322", "title": "Happy birthday!", "google_event": true}], "2028-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20280522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20280522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2028-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20280715", "title": "Diane Cell's birthday", "google_event": true}], "2028-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20280905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2028-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20280915", "title": "Jaden's birthday", "google_event": true}], "2028-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20280916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2028-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20281030", "title": "Rebecca's birthday", "google_event": true}], "2028-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20281221", "title": "Megan Quinn's birthday", "google_event": true}], "2029-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20290322", "title": "Happy birthday!", "google_event": true}], "2029-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20290522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20290522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2029-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20290715", "title": "Diane Cell's birthday", "google_event": true}], "2029-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20290905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2029-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20290915", "title": "Jaden's birthday", "google_event": true}], "2029-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20290916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2029-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20291030", "title": "Rebecca's birthday", "google_event": true}], "2029-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20291221", "title": "Megan Quinn's birthday", "google_event": true}], "2030-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20300322", "title": "Happy birthday!", "google_event": true}], "2030-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20300522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20300522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2030-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20300715", "title": "Diane Cell's birthday", "google_event": true}], "2030-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20300905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2030-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20300915", "title": "Jaden's birthday", "google_event": true}], "2030-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20300916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2030-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20301030", "title": "Rebecca's birthday", "google_event": true}], "2030-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20301221", "title": "Megan Quinn's birthday", "google_event": true}], "2031-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20310322", "title": "Happy birthday!", "google_event": true}], "2031-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20310522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20310522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2031-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20310715", "title": "Diane Cell's birthday", "google_event": true}], "2031-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20310905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2031-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20310915", "title": "Jaden's birthday", "google_event": true}], "2031-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20310916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2031-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20311030", "title": "Rebecca's birthday", "google_event": true}], "2031-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20311221", "title": "Megan Quinn's birthday", "google_event": true}], "2032-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20320322", "title": "Happy birthday!", "google_event": true}], "2032-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20320522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20320522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2032-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20320715", "title": "Diane Cell's birthday", "google_event": true}], "2032-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20320905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2032-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20320915", "title": "Jaden's birthday", "google_event": true}], "2032-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20320916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2032-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20321030", "title": "Rebecca's birthday", "google_event": true}], "2032-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20321221", "title": "Megan Quinn's birthday", "google_event": true}], "2033-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20330322", "title": "Happy birthday!", "google_event": true}], "2033-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20330522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20330522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2033-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20330715", "title": "Diane Cell's birthday", "google_event": true}], "2033-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20330905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2033-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20330915", "title": "Jaden's birthday", "google_event": true}], "2033-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20330916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2033-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20331030", "title": "Rebecca's birthday", "google_event": true}], "2033-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20331221", "title": "Megan Quinn's birthday", "google_event": true}], "2034-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20340322", "title": "Happy birthday!", "google_event": true}], "2034-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20340522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20340522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2034-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20340715", "title": "Diane Cell's birthday", "google_event": true}], "2034-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20340905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2034-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20340915", "title": "Jaden's birthday", "google_event": true}], "2034-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20340916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2034-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20341030", "title": "Rebecca's birthday", "google_event": true}], "2034-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20341221", "title": "Megan Quinn's birthday", "google_event": true}], "2035-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20350322", "title": "Happy birthday!", "google_event": true}], "2035-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20350522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20350522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2035-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20350715", "title": "Diane Cell's birthday", "google_event": true}], "2035-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20350905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2035-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20350915", "title": "Jaden's birthday", "google_event": true}], "2035-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20350916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2035-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20351030", "title": "Rebecca's birthday", "google_event": true}], "2035-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20351221", "title": "Megan Quinn's birthday", "google_event": true}], "2036-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20360322", "title": "Happy birthday!", "google_event": true}], "2036-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20360522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20360522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2036-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20360715", "title": "Diane Cell's birthday", "google_event": true}]} \ No newline at end of file diff --git a/flaskServer.py b/flaskServer.py index a27a1a0..77d379e 100644 --- a/flaskServer.py +++ b/flaskServer.py @@ -29,7 +29,7 @@ #allows for easier starting of flask from start file def run_flask(): - app.run(debug=True, port=8000, use_reloader=False) + app.run(debug=True, port=5000, use_reloader=False) diff --git a/static/js/plugins/calendar_script.js b/static/js/plugins/calendar_script.js index c919a2e..a40d886 100644 --- a/static/js/plugins/calendar_script.js +++ b/static/js/plugins/calendar_script.js @@ -198,7 +198,6 @@ updateCalendar().then(() => { // update every 60s setInterval(updateCalendar, 60000); - // make button work globally window.addTask = addTask; window.deleteTask = deleteTask; diff --git a/static/widgetCSS/calendar_widget.css b/static/widgetCSS/calendar_widget.css index 2c31d40..0e700e7 100644 --- a/static/widgetCSS/calendar_widget.css +++ b/static/widgetCSS/calendar_widget.css @@ -1,18 +1,26 @@ +.widget-Calendar{ + align-items: center; + justify-content: center; + overflow: hidden; + width: 100%; + height: auto; +} .calendar-widget { display: flex; - justify-content: center; - align-items: center; - width: fit-content; - height: fit-content; + flex-direction: column; /* Changed to column to allow sections to stack properly */ + width: fit-content; /* Fill parent width */ + height: fit-content; /* Allow height to grow with content */ background: rgba(20, 20, 20, 0.8); border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); overflow: hidden; + box-sizing: border-box; + } #calendar { - width: fit-content; - height: fit-content; + width: 100%; + height: auto; padding: 16px; box-sizing: border-box; font-family: 'Geist Mono', monospace; @@ -24,6 +32,7 @@ justify-content: space-between; align-items: baseline; margin-bottom: 12px; + width: 100%; } .cal-month-name { @@ -42,8 +51,9 @@ .cal-grid { display: grid; - grid-template-columns: repeat(7, 1fr); - gap: 2px; + grid-template-columns: repeat(7, 1fr); /* 7 equal columns across the full width */ + gap: 4px; /* Slightly increased gap for a cleaner look on wide screens */ + width: 100%; } .cal-day-header { @@ -56,7 +66,8 @@ } .cal-day { - aspect-ratio: 1; + /* aspect-ratio: 1; -> Keep this to ensure days stay square as they get wider */ + aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; @@ -64,6 +75,7 @@ border-radius: 6px; cursor: default; transition: background 0.15s ease; + width: 100%; /* Ensure the div fills its grid cell */ } .cal-day:hover { @@ -104,7 +116,9 @@ .cal-events-section { margin-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.1); - padding-top: 8px; + padding: 12px 16px; /* Added horizontal padding to match #calendar */ + width: 100%; + box-sizing: border-box; } .cal-events-title { @@ -118,12 +132,13 @@ .cal-event-item { display: flex; align-items: center; - padding: 4px 8px; + justify-content: space-between; + padding: 6px 10px; /* Slightly more padding for the wider view */ background: rgba(0, 255, 153, 0.08); border-left: 2px solid #00ff99; border-radius: 0 4px 4px 0; margin-bottom: 4px; - font-size: 0.65rem; + font-size: 0.75rem; /* Slightly larger text for better readability on wide screens */ color: #eceff4; } @@ -134,10 +149,11 @@ } .task-section { - margin-top: 12px; - display: none; - padding-top: 10px; - border-top: 1px solid rgba(255,255,255,0.08); + margin-top: 12px; + padding: 10px 16px; + border-top: 1px solid rgba(255,255,255,0.08); + width: 100%; + box-sizing: border-box; } .task-label { From 23d86861048fa6ee73cc413305fd94561da924a8 Mon Sep 17 00:00:00 2001 From: Easha Mashud Date: Thu, 30 Apr 2026 00:06:03 -0400 Subject: [PATCH 3/6] Add Calendar Arrows that change the months --- SMTplugins/Calendar/calendarWidget.py | 180 +++++++++++++------------ SMTplugins/Calendar/calendar_plugin.py | 1 + calendar_state.json | 1 + static/js/plugins/calendar_script.js | 43 +++--- static/widgetCSS/calendar_widget.css | 23 ++++ templates/calendar_widget.html | 38 ++---- 6 files changed, 156 insertions(+), 130 deletions(-) create mode 100644 calendar_state.json diff --git a/SMTplugins/Calendar/calendarWidget.py b/SMTplugins/Calendar/calendarWidget.py index 2ef5de0..1de37d3 100644 --- a/SMTplugins/Calendar/calendarWidget.py +++ b/SMTplugins/Calendar/calendarWidget.py @@ -1,7 +1,3 @@ -# calendarWidget.py -# Displays a monthly calendar with Google Calendar event integration (optional) -# Falls back to a static calendar view if no credentials are provided - from widget import Widget from datetime import datetime, date import calendar @@ -9,18 +5,39 @@ import os import time + class calendarWidget(Widget): def __init__(self): - self._preferences = self.widgetDefaultPreferences - self.file = "calendar_events.json" - - if os.path.exists(self.file): - with open(self.file, "r") as f: - self._events = json.load(f) + # -------- FILES -------- + self.state_file = "calendar_state.json" + self.events_file = "calendar_events.json" + + # -------- DEFAULT STATE -------- + self.current_date = date.today() + + # -------- LOAD MONTH STATE -------- + if os.path.exists(self.state_file): + try: + with open(self.state_file, "r") as f: + state = json.load(f) + if "current_date" in state: + self.current_date = date.fromisoformat(state["current_date"]) + except: + print("State file corrupted, resetting") + + # -------- LOAD EVENTS -------- + if os.path.exists(self.events_file): + try: + with open(self.events_file, "r") as f: + self._events = json.load(f) + except: + print("Events file corrupted, resetting") + self._events = {} else: self._events = {} + # -------- METADATA -------- @property def widgetName(self): return "Calendar Widget" @@ -31,17 +48,16 @@ def widgetID(self): @property def widgetHTML(self): - """Returns the HTML template name; Flask renders it via Jinja.""" return "calendar_widget.html" + # -------- MAIN DATA -------- @property def widgetData(self): - """Returns current calendar data as a JSON-serialisable dict.""" today = date.today() - year = today.year - month = today.month + year = self.current_date.year + month = self.current_date.month - cal = calendar.Calendar(firstweekday=6) # week starts Sunday + cal = calendar.Calendar(firstweekday=6) weeks = cal.monthdatescalendar(year, month) weeks_data = [] @@ -58,12 +74,13 @@ def widgetData(self): weeks_data.append(days) return { - "month_name": today.strftime("%B"), + "month_name": self.current_date.strftime("%B"), "year": year, "weeks": weeks_data, "day_headers": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] } + # -------- PREFERENCES -------- @property def widgetPreferences(self): return self._preferences @@ -76,43 +93,39 @@ def widgetPreferences(self, value): def widgetDefaultPreferences(self): return { "show_week_numbers": False, - "use_google_cal": False, # Set True + provide creds to enable + "use_google_cal": False, "google_cal_id": "primary" } @property def updateTimer(self): - # Refresh every 10 minutes return 600_000 - def update(self): - """Called by the widget subsystem on a timer. Fetches events if enabled.""" - if self._preferences.get("use_google_cal"): - self._fetch_google_events() - else: - # Only add demo data if nothing exists yet - if not self._events: - today = date.today().isoformat() - self._events[today] = ["Class 4PM – LC 22", "Gym 5PM"] - - print(f"Calendar Widget updated – {date.today().strftime('%B %Y')}") - - def _fetch_google_events(self): - """ - Stub for Google Calendar API integration. - To enable: install google-auth + google-api-python-client, - create OAuth credentials, and fill in the logic below. - """ - try: - # TODO: implement OAuth flow and fetch events for current month - # from googleapiclient.discovery import build - # service = build("calendar", "v3", credentials=creds) - # events_result = service.events().list(...).execute() - raise NotImplementedError("Google Calendar auth not yet configured.") - except Exception as e: - print(f"[calendarWidget] Google Calendar fetch failed: {e}") + # -------- SAVE HELPERS -------- + def _save_state(self): + with open(self.state_file, "w") as f: + json.dump({ + "current_date": self.current_date.isoformat() + }, f) + + def _save_events(self): + with open(self.events_file, "w") as f: + json.dump(self._events, f) + # -------- OPTIONAL DEFAULT DATA -------- + def update(self): + if not self._events: + today = date.today().isoformat() + self._events[today] = [ + {"id": int(time.time()*1000), "title": "Class 4PM – LC 22"}, + {"id": int(time.time()*1000)+1, "title": "Gym 5PM"} + ] + self._save_events() + + # -------- EVENT HANDLER -------- def handle_event(self, event, args): + + # -------- ADD TASK -------- if event == "add_event": date_str = args.get("date") title = args.get("title", "Event") @@ -121,49 +134,46 @@ def handle_event(self, event, args): if date_str not in self._events: self._events[date_str] = [] - task = { - "id": int(time.time() * 1000), # unique ID + self._events[date_str].append({ + "id": int(time.time()*1000), "title": title - } - - self._events[date_str].append(task) - - with open(self.file, "w") as f: - json.dump(self._events, f) - - print(f"[calendarWidget] Added event '{title}' on {date_str}") - - - elif event == "delete_event": - date_str = args.get("date") - task_id = args.get("id") - - if date_str in self._events: - self._events[date_str] = [ - t for t in self._events[date_str] if t["id"] != task_id - ] - - with open(self.file, "w") as f: - json.dump(self._events, f) - - print(f"[calendarWidget] Deleted task {task_id}") - - - elif event == "edit_event": - date_str = args.get("date") - task_id = args.get("id") - new_title = args.get("title") - - if date_str in self._events: - for t in self._events[date_str]: - if t["id"] == task_id: - t["title"] = new_title - - with open(self.file, "w") as f: - json.dump(self._events, f) - - print(f"[calendarWidget] Edited task {task_id}") + }) + self._save_events() + + # -------- NEXT MONTH -------- + elif event == "next_month": + print("NEXT MONTH TRIGGERED") + + if self.current_date.month == 12: + self.current_date = self.current_date.replace( + year=self.current_date.year + 1, + month=1 + ) + else: + self.current_date = self.current_date.replace( + month=self.current_date.month + 1 + ) + + self._save_state() + print("NEW MONTH:", self.current_date) + + # -------- PREVIOUS MONTH -------- + elif event == "prev_month": + print("PREV MONTH TRIGGERED") + + if self.current_date.month == 1: + self.current_date = self.current_date.replace( + year=self.current_date.year - 1, + month=12 + ) + else: + self.current_date = self.current_date.replace( + month=self.current_date.month - 1 + ) + + self._save_state() + print("NEW MONTH:", self.current_date) else: print(f"[calendarWidget] Unhandled event: {event} args={args}") \ No newline at end of file diff --git a/SMTplugins/Calendar/calendar_plugin.py b/SMTplugins/Calendar/calendar_plugin.py index 1bf0d23..72e3322 100644 --- a/SMTplugins/Calendar/calendar_plugin.py +++ b/SMTplugins/Calendar/calendar_plugin.py @@ -10,6 +10,7 @@ @calendar_bp.route("/widget/calendar") def calendar_view(): + print("RENDERING MONTH:", _widget.current_date) # debug return render_template("calendar_widget.html", data=_widget.widgetData) @calendar_bp.route("/api/calendar/data") diff --git a/calendar_state.json b/calendar_state.json new file mode 100644 index 0000000..7593051 --- /dev/null +++ b/calendar_state.json @@ -0,0 +1 @@ +{"current_date": "2026-04-29"} \ No newline at end of file diff --git a/static/js/plugins/calendar_script.js b/static/js/plugins/calendar_script.js index c919a2e..ed333b2 100644 --- a/static/js/plugins/calendar_script.js +++ b/static/js/plugins/calendar_script.js @@ -95,20 +95,6 @@ function addTask() { input.value = ""; updateCalendar().then(() => { - setTimeout(() => { - const selected = document.querySelector( - `.cal-day[data-date="${selectedDate}"]` - ); - - if (selected) { - selected.click(); - - // optional safety re-click - setTimeout(() => { - selected.click(); - }, 50); - } - }, 50); }); }); } @@ -157,6 +143,28 @@ function editTask(id, date, oldTitle) { }); } +function changeMonth(direction) { + console.log("CLICKED:", direction); + + fetch("/api/calendar/event", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + event: direction === "next" ? "next_month" : "prev_month" + }) + }) + .then(res => res.json()) + .then(() => { + console.log("UPDATED MONTH"); + return updateCalendar(); + }) + .catch(err => console.error("ERROR:", err)); +} + +window.changeMonth = changeMonth; + // ONLY ONE FUNCTION export async function updateCalendar() { try { @@ -175,12 +183,7 @@ export async function updateCalendar() { attachCalendarListeners(); } - setTimeout(() => { - const today = document.querySelector(".cal-day.today"); - if (today) { - today.click(); - } - }, 50); + } catch (error) { console.error('Calendar update failed:', error); diff --git a/static/widgetCSS/calendar_widget.css b/static/widgetCSS/calendar_widget.css index 2c31d40..bf80d41 100644 --- a/static/widgetCSS/calendar_widget.css +++ b/static/widgetCSS/calendar_widget.css @@ -211,4 +211,27 @@ .cal-event-item button:hover { color: white; +} + +.cal-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.cal-nav-btn { + background: transparent; + border: none; + color: #c084fc; /* purple */ + font-size: 1.2rem; + cursor: pointer; + padding: 6px 10px; + border-radius: 6px; + transition: all 0.2s ease; +} + +.cal-nav-btn:hover { + background: rgba(192, 132, 252, 0.15); + color: white; + transform: scale(1.2); } \ No newline at end of file diff --git a/templates/calendar_widget.html b/templates/calendar_widget.html index 0dc8d76..7baf5d7 100644 --- a/templates/calendar_widget.html +++ b/templates/calendar_widget.html @@ -187,10 +187,21 @@
- {{ data.month_name }} + + + + + +
+ {{ data.month_name }}
{{ data.year }}
+ + + +
+
{% for header in data.day_headers %} @@ -214,30 +225,7 @@ {% endfor %}
- - +
Select a day
From e4d4e70f1f6a9f0e7354bf822f6bb8045c1527d3 Mon Sep 17 00:00:00 2001 From: bj322-design Date: Thu, 30 Apr 2026 01:09:41 -0400 Subject: [PATCH 4/6] added day names to date widget --- static/js/plugins/date_script.js | 4 ++++ static/widgetCSS/date_widget.css | 1 + 2 files changed, 5 insertions(+) diff --git a/static/js/plugins/date_script.js b/static/js/plugins/date_script.js index 22f256f..fb7117e 100644 --- a/static/js/plugins/date_script.js +++ b/static/js/plugins/date_script.js @@ -10,6 +10,9 @@ async function updateDate() { await fetch('/api/date'); const d = new Date(); + + const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + const dayName = days[new Date().getDay()]; const month = d.toLocaleString('en-US', { month: 'long' }); const day = d.getDate(); @@ -17,6 +20,7 @@ async function updateDate() { widget.innerHTML = `
+
${dayName}
${month}
${day}
${year}
diff --git a/static/widgetCSS/date_widget.css b/static/widgetCSS/date_widget.css index 31f66dd..9fdeb50 100644 --- a/static/widgetCSS/date_widget.css +++ b/static/widgetCSS/date_widget.css @@ -20,6 +20,7 @@ .date-month, .date-day, +.date-name, .date-year { /*font-size: 40px; /* SAME SIZE for all */ font-weight: bold; From 48b0856eeeeb6afbd3159b6562ba53ac60f9ad0c Mon Sep 17 00:00:00 2001 From: bj322-design Date: Thu, 30 Apr 2026 10:08:34 -0400 Subject: [PATCH 5/6] improved style / functionallity from other cal branch --- SMTplugins/Calendar/calendarWidget.py | 53 ++++++++++- calendar_events.json | 1 - static/js/plugins/calendar_script.js | 124 +++++++++++++++----------- static/widgetCSS/calendar_widget.css | 71 ++++++++------- 4 files changed, 158 insertions(+), 91 deletions(-) delete mode 100644 calendar_events.json diff --git a/SMTplugins/Calendar/calendarWidget.py b/SMTplugins/Calendar/calendarWidget.py index c53e8cc..29eee92 100644 --- a/SMTplugins/Calendar/calendarWidget.py +++ b/SMTplugins/Calendar/calendarWidget.py @@ -161,11 +161,56 @@ def updateTimer(self): return 60000 # Refresh every 1 minute to stay within API limits def handle_event(self, event, args): - # Keeps your existing add/delete/edit logic for local testing + + # -------- ADD TASK -------- if event == "add_event": date_str = args.get("date") title = args.get("title", "Event") + if date_str: - if date_str not in self._events: self._events[date_str] = [] - self._events[date_str].append({"id": int(time.time() * 1000), "title": title}) - with open(self.file, "w") as f: json.dump(self._events, f) \ No newline at end of file + if date_str not in self._events: + self._events[date_str] = [] + + self._events[date_str].append({ + "id": int(time.time()*1000), + "title": title + }) + + self._save_events() + + # -------- NEXT MONTH -------- + elif event == "next_month": + print("NEXT MONTH TRIGGERED") + + if self.current_date.month == 12: + self.current_date = self.current_date.replace( + year=self.current_date.year + 1, + month=1 + ) + else: + self.current_date = self.current_date.replace( + month=self.current_date.month + 1 + ) + + self._save_state() + print("NEW MONTH:", self.current_date) + + # -------- PREVIOUS MONTH -------- + elif event == "prev_month": + print("PREV MONTH TRIGGERED") + + if self.current_date.month == 1: + self.current_date = self.current_date.replace( + year=self.current_date.year - 1, + month=12 + ) + else: + self.current_date = self.current_date.replace( + month=self.current_date.month - 1 + ) + + self._save_state() + print("NEW MONTH:", self.current_date) + + else: + print(f"[calendarWidget] Unhandled event: {event} args={args}") \ No newline at end of file diff --git a/calendar_events.json b/calendar_events.json deleted file mode 100644 index 48cfc67..0000000 --- a/calendar_events.json +++ /dev/null @@ -1 +0,0 @@ -{"2026-04-09": [{"id": "52t16a56o13mo820mdu16lokq8", "title": "MT Tremblant Airbnb ", "google_event": true}], "2026-04-10": [{"id": "1le48utrdlm0a32pnc5s6dftmo", "title": "MT Trabant Trip", "google_event": true}], "2026-04-29": [{"id": "7hnt3ohcgu79o8co0o1g12bp21", "title": "IEEE Officer Debate", "google_event": true}, {"id": "3jc2bke6ic6cvnbuu1kvtn9s5p", "title": "Tell Easha I can Cook", "google_event": true}], "2026-04-30": [{"id": "nvqjiem54j2flbbh67vqkr9uq0", "title": "Showcase", "google_event": true}], "2026-05-01": [{"id": "6tj38cpm6grm2bb6c8o6cb9k6lhj8bb1cgoj4bb171hm2dpmcks3ip1gck", "title": "Ski formal", "google_event": true}], "2026-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20260522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20260522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2026-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20260715", "title": "Diane Cell's birthday", "google_event": true}], "2026-08-25": [{"id": "rhbtnkpd414p289606h6pddeds", "title": "Officers elections", "google_event": true}], "2026-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20260905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2026-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20260915", "title": "Jaden's birthday", "google_event": true}], "2026-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20260916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2026-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20261030", "title": "Rebecca's birthday", "google_event": true}], "2026-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20261221", "title": "Megan Quinn's birthday", "google_event": true}], "2027-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20270322", "title": "Happy birthday!", "google_event": true}], "2027-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20270522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20270522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2027-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20270715", "title": "Diane Cell's birthday", "google_event": true}], "2027-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20270905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2027-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20270915", "title": "Jaden's birthday", "google_event": true}], "2027-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20270916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2027-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20271030", "title": "Rebecca's birthday", "google_event": true}], "2027-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20271221", "title": "Megan Quinn's birthday", "google_event": true}], "2028-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20280322", "title": "Happy birthday!", "google_event": true}], "2028-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20280522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20280522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2028-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20280715", "title": "Diane Cell's birthday", "google_event": true}], "2028-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20280905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2028-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20280915", "title": "Jaden's birthday", "google_event": true}], "2028-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20280916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2028-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20281030", "title": "Rebecca's birthday", "google_event": true}], "2028-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20281221", "title": "Megan Quinn's birthday", "google_event": true}], "2029-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20290322", "title": "Happy birthday!", "google_event": true}], "2029-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20290522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20290522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2029-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20290715", "title": "Diane Cell's birthday", "google_event": true}], "2029-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20290905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2029-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20290915", "title": "Jaden's birthday", "google_event": true}], "2029-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20290916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2029-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20291030", "title": "Rebecca's birthday", "google_event": true}], "2029-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20291221", "title": "Megan Quinn's birthday", "google_event": true}], "2030-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20300322", "title": "Happy birthday!", "google_event": true}], "2030-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20300522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20300522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2030-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20300715", "title": "Diane Cell's birthday", "google_event": true}], "2030-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20300905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2030-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20300915", "title": "Jaden's birthday", "google_event": true}], "2030-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20300916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2030-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20301030", "title": "Rebecca's birthday", "google_event": true}], "2030-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20301221", "title": "Megan Quinn's birthday", "google_event": true}], "2031-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20310322", "title": "Happy birthday!", "google_event": true}], "2031-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20310522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20310522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2031-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20310715", "title": "Diane Cell's birthday", "google_event": true}], "2031-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20310905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2031-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20310915", "title": "Jaden's birthday", "google_event": true}], "2031-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20310916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2031-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20311030", "title": "Rebecca's birthday", "google_event": true}], "2031-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20311221", "title": "Megan Quinn's birthday", "google_event": true}], "2032-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20320322", "title": "Happy birthday!", "google_event": true}], "2032-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20320522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20320522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2032-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20320715", "title": "Diane Cell's birthday", "google_event": true}], "2032-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20320905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2032-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20320915", "title": "Jaden's birthday", "google_event": true}], "2032-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20320916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2032-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20321030", "title": "Rebecca's birthday", "google_event": true}], "2032-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20321221", "title": "Megan Quinn's birthday", "google_event": true}], "2033-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20330322", "title": "Happy birthday!", "google_event": true}], "2033-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20330522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20330522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2033-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20330715", "title": "Diane Cell's birthday", "google_event": true}], "2033-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20330905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2033-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20330915", "title": "Jaden's birthday", "google_event": true}], "2033-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20330916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2033-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20331030", "title": "Rebecca's birthday", "google_event": true}], "2033-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20331221", "title": "Megan Quinn's birthday", "google_event": true}], "2034-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20340322", "title": "Happy birthday!", "google_event": true}], "2034-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20340522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20340522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2034-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20340715", "title": "Diane Cell's birthday", "google_event": true}], "2034-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20340905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2034-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20340915", "title": "Jaden's birthday", "google_event": true}], "2034-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20340916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2034-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20341030", "title": "Rebecca's birthday", "google_event": true}], "2034-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20341221", "title": "Megan Quinn's birthday", "google_event": true}], "2035-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20350322", "title": "Happy birthday!", "google_event": true}], "2035-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20350522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20350522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2035-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20350715", "title": "Diane Cell's birthday", "google_event": true}], "2035-09-05": [{"id": "ilo00acvu4ut1qu6de762ids74_20350905", "title": "Lucas (Goose) Bryant's birthday", "google_event": true}], "2035-09-15": [{"id": "igni28c18ihcd1nr6n5mq8661m_20350915", "title": "Jaden's birthday", "google_event": true}], "2035-09-16": [{"id": "64sj0phncgrm2b9m61hm6b9k6pi6cb9pcksjcbb674r62eb564sjedb660_20350916T113000Z", "title": "Frankie Doria's Birthday", "google_event": true}], "2035-10-30": [{"id": "6gqr1kkhf64gveq6ja8ds4mtk4_20351030", "title": "Rebecca's birthday", "google_event": true}], "2035-12-21": [{"id": "1sff7ni6eu16ih5v29far0l27s_20351221", "title": "Megan Quinn's birthday", "google_event": true}], "2036-03-22": [{"id": "lho0rikn5mm3btte1v2ndcasio_20360322", "title": "Happy birthday!", "google_event": true}], "2036-05-22": [{"id": "evhou6bk9hh0bk5fst9eksnrhc_20360522", "title": "Chiara (Chairs)'s birthday", "google_event": true}, {"id": "q7q55tkon7ov1962nrf53rdj50_20360522", "title": "Gianna \u00b0\u2206\u00b0's birthday", "google_event": true}], "2036-07-15": [{"id": "909j5pjeebjhha6bi81e8l3ep0_20360715", "title": "Diane Cell's birthday", "google_event": true}]} \ No newline at end of file diff --git a/static/js/plugins/calendar_script.js b/static/js/plugins/calendar_script.js index a40d886..4f96e15 100644 --- a/static/js/plugins/calendar_script.js +++ b/static/js/plugins/calendar_script.js @@ -1,12 +1,23 @@ +/** + * PREREQUISITES FOR THIS FILE: + * 1. An HTML container with id="calendar". + * 2. An API endpoint at /widget/calendar that returns HTML. + * 3. An API endpoint at /api/calendar/event that handles POST requests. + * 4. A global window.calendarEvents array (usually provided by the server-side HTML). + */ + let selectedDate = null; let calendarInitialized = false; +/** + * Attaches event listeners to the calendar container. + * Uses delegation so we don't have to re-bind listeners to every single day. + */ function attachCalendarListeners() { const container = document.getElementById("calendar"); - if (!container) return; - calendarInitialized = true; // mark as initialized + calendarInitialized = true; container.addEventListener("click", (e) => { const day = e.target.closest(".cal-day"); @@ -14,14 +25,16 @@ function attachCalendarListeners() { selectedDate = day.dataset.date; - console.log("Calendar data:", window.calendarEvents); - // SHOW TASKS FOR SELECTED DATE + // UI Elements const eventsContainer = document.getElementById("selectedEventsContainer"); const title = document.getElementById("eventsTitle"); + const taskSection = document.getElementById("taskSection"); + const label = document.getElementById("selectedDateLabel"); if (eventsContainer && title && window.calendarEvents) { let events = []; + // Find events for the clicked date from the global data store window.calendarEvents.forEach(week => { week.forEach(dayData => { if (dayData.date_str === selectedDate) { @@ -37,13 +50,16 @@ function attachCalendarListeners() { events.forEach(e => { const div = document.createElement("div"); div.className = "cal-event-item"; + div.style.display = "flex"; + div.style.marginBottom = "5px"; div.innerHTML = ` ${e.title || e}
+
- `; + `; eventsContainer.appendChild(div); }); } else { @@ -51,16 +67,12 @@ function attachCalendarListeners() { } } - // highlight + // Visual Highlight: Clear others, highlight selected document.querySelectorAll(".cal-day").forEach(d => d.style.outline = "none"); day.style.outline = "2px solid #c084fc"; - // show input - const taskSection = document.getElementById("taskSection"); + // Toggle task input visibility if (taskSection) taskSection.style.display = "block"; - - // label - const label = document.getElementById("selectedDateLabel"); if (label) { label.innerText = "Editing tasks for: " + new Date(selectedDate).toDateString(); } @@ -69,6 +81,9 @@ function attachCalendarListeners() { }); } +/** + * Sends a new task to the server and refreshes the UI. + */ function addTask() { const input = document.getElementById("taskInput"); const task = input.value; @@ -80,58 +95,43 @@ function addTask() { fetch("/api/calendar/event", { method: "POST", - headers: { - "Content-Type": "application/json" - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event: "add_event", - args: { - date: selectedDate, - title: task - } + args: { date: selectedDate, title: task } }) }) .then(() => { input.value = ""; - updateCalendar().then(() => { + // File 1 logic: Re-click the date so the new task appears immediately setTimeout(() => { - const selected = document.querySelector( - `.cal-day[data-date="${selectedDate}"]` - ); - + const selected = document.querySelector(`.cal-day[data-date="${selectedDate}"]`); if (selected) { selected.click(); - - // optional safety re-click - setTimeout(() => { - selected.click(); - }, 50); + setTimeout(() => selected.click(), 50); // Safety re-click } }, 50); }); }); } - function deleteTask(id, date) { fetch("/api/calendar/event", { method: "POST", - headers: { - "Content-Type": "application/json" - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event: "delete_event", args: { id, date } }) }).then(() => { - updateCalendar().then(() => { - setTimeout(() => { - const day = document.querySelector(`[data-date="${date}"]`); - if (day) day.click(); - }, 50); + updateCalendar().then(() => { + setTimeout(() => { + const day = document.querySelector(`[data-date="${date}"]`); + if (day) day.click(); + }, 50); + }); }); -}); } function editTask(id, date, oldTitle) { @@ -140,24 +140,24 @@ function editTask(id, date, oldTitle) { fetch("/api/calendar/event", { method: "POST", - headers: { - "Content-Type": "application/json" - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event: "edit_event", args: { id, date, title: newTitle } }) }).then(() => { - updateCalendar().then(() => { - setTimeout(() => { - const day = document.querySelector(`[data-date="${date}"]`); - if (day) day.click(); - }, 50); + updateCalendar().then(() => { + setTimeout(() => { + const day = document.querySelector(`[data-date="${date}"]`); + if (day) day.click(); + }, 50); + }); }); -}); } -// ONLY ONE FUNCTION +/** + * Fetches the latest calendar HTML from the server and injects it. + */ export async function updateCalendar() { try { const response = await fetch('/widget/calendar'); @@ -167,19 +167,19 @@ export async function updateCalendar() { if (container) { container.innerHTML = html; - // IMPORTANT: re-read fresh data from HTML + // Execute the script embedded in the fetched HTML to update window.calendarEvents const scriptTag = container.querySelector("script"); if (scriptTag) { - eval(scriptTag.innerText); // updates window.calendarEvents + eval(scriptTag.innerText); } attachCalendarListeners(); } + + // File 1 logic: Default back to "Today" after background updates setTimeout(() => { const today = document.querySelector(".cal-day.today"); - if (today) { - today.click(); - } + if (today) today.click(); }, 50); } catch (error) { @@ -187,8 +187,23 @@ export async function updateCalendar() { } } +function changeMonth(direction) { + fetch("/api/calendar/event", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + event: direction === "next" ? "next_month" : "prev_month" + }) + }) + .then(res => res.json()) + .then(() => updateCalendar()) + .catch(err => console.error("ERROR:", err)); +} +window.changeMonth = changeMonth; + +// Initialize on Load updateCalendar().then(() => { setTimeout(() => { const today = document.querySelector(".cal-day.today"); @@ -196,8 +211,9 @@ updateCalendar().then(() => { }, 50); }); -// update every 60s +// Background Sync setInterval(updateCalendar, 60000); + // make button work globally window.addTask = addTask; window.deleteTask = deleteTask; diff --git a/static/widgetCSS/calendar_widget.css b/static/widgetCSS/calendar_widget.css index 0e700e7..bf80d41 100644 --- a/static/widgetCSS/calendar_widget.css +++ b/static/widgetCSS/calendar_widget.css @@ -1,26 +1,18 @@ -.widget-Calendar{ - align-items: center; - justify-content: center; - overflow: hidden; - width: 100%; - height: auto; -} .calendar-widget { display: flex; - flex-direction: column; /* Changed to column to allow sections to stack properly */ - width: fit-content; /* Fill parent width */ - height: fit-content; /* Allow height to grow with content */ + justify-content: center; + align-items: center; + width: fit-content; + height: fit-content; background: rgba(20, 20, 20, 0.8); border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); overflow: hidden; - box-sizing: border-box; - } #calendar { - width: 100%; - height: auto; + width: fit-content; + height: fit-content; padding: 16px; box-sizing: border-box; font-family: 'Geist Mono', monospace; @@ -32,7 +24,6 @@ justify-content: space-between; align-items: baseline; margin-bottom: 12px; - width: 100%; } .cal-month-name { @@ -51,9 +42,8 @@ .cal-grid { display: grid; - grid-template-columns: repeat(7, 1fr); /* 7 equal columns across the full width */ - gap: 4px; /* Slightly increased gap for a cleaner look on wide screens */ - width: 100%; + grid-template-columns: repeat(7, 1fr); + gap: 2px; } .cal-day-header { @@ -66,8 +56,7 @@ } .cal-day { - /* aspect-ratio: 1; -> Keep this to ensure days stay square as they get wider */ - aspect-ratio: 1; + aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; @@ -75,7 +64,6 @@ border-radius: 6px; cursor: default; transition: background 0.15s ease; - width: 100%; /* Ensure the div fills its grid cell */ } .cal-day:hover { @@ -116,9 +104,7 @@ .cal-events-section { margin-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.1); - padding: 12px 16px; /* Added horizontal padding to match #calendar */ - width: 100%; - box-sizing: border-box; + padding-top: 8px; } .cal-events-title { @@ -132,13 +118,12 @@ .cal-event-item { display: flex; align-items: center; - justify-content: space-between; - padding: 6px 10px; /* Slightly more padding for the wider view */ + padding: 4px 8px; background: rgba(0, 255, 153, 0.08); border-left: 2px solid #00ff99; border-radius: 0 4px 4px 0; margin-bottom: 4px; - font-size: 0.75rem; /* Slightly larger text for better readability on wide screens */ + font-size: 0.65rem; color: #eceff4; } @@ -149,11 +134,10 @@ } .task-section { - margin-top: 12px; - padding: 10px 16px; - border-top: 1px solid rgba(255,255,255,0.08); - width: 100%; - box-sizing: border-box; + margin-top: 12px; + display: none; + padding-top: 10px; + border-top: 1px solid rgba(255,255,255,0.08); } .task-label { @@ -227,4 +211,27 @@ .cal-event-item button:hover { color: white; +} + +.cal-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.cal-nav-btn { + background: transparent; + border: none; + color: #c084fc; /* purple */ + font-size: 1.2rem; + cursor: pointer; + padding: 6px 10px; + border-radius: 6px; + transition: all 0.2s ease; +} + +.cal-nav-btn:hover { + background: rgba(192, 132, 252, 0.15); + color: white; + transform: scale(1.2); } \ No newline at end of file From 1065b742ed99d9df3fde89c5ef4bbcc37cf74594 Mon Sep 17 00:00:00 2001 From: Easha Mashud Date: Thu, 30 Apr 2026 11:28:56 -0400 Subject: [PATCH 6/6] resolve stash conflict --- SMTplugins/Calendar/token_cal.json | 1 + calendar_events.json | 1 + flaskServer.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 SMTplugins/Calendar/token_cal.json create mode 100644 calendar_events.json diff --git a/SMTplugins/Calendar/token_cal.json b/SMTplugins/Calendar/token_cal.json new file mode 100644 index 0000000..acb34eb --- /dev/null +++ b/SMTplugins/Calendar/token_cal.json @@ -0,0 +1 @@ +{"token": "ya29.a0AQvPyINm3-Umt-poLbkfSpf3sLXjFOYawOQnHssKmcD6iWd6qI8Vxog2HqhWPjDc4-Gwib_qHJB0JgbnWlVCZeoMCrkxipNogHWpkAVsD4rjQxllKDdjlO7lcHQiFEODBc05EtXZHSdpdkqp3ah9cWzQeOHi2QksUPudqv8QNapkJpvElGqKDljOyvnuJMENZ8bTXnEaCgYKAaMSARQSFQHGX2Mi1bah18Yfs0QmPf9s0gUP6A0206", "refresh_token": "1//05NlvPY06LAVrCgYIARAAGAUSNwF-L9IrBh5LQKCmxDpM-lbVWh3B_Grots1N2s4FdVmAsdTZQ3AeBEwIUmtCI3uZRvfDXKTb4V4", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "772172144197-ldo19j0f7s5m7s55jd6ndkadoj4c4ehd.apps.googleusercontent.com", "client_secret": "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", "scopes": ["https://www.googleapis.com/auth/calendar.readonly"], "universe_domain": "googleapis.com", "account": "", "expiry": "2026-04-30T16:24:43Z"} \ No newline at end of file diff --git a/calendar_events.json b/calendar_events.json new file mode 100644 index 0000000..c5877dd --- /dev/null +++ b/calendar_events.json @@ -0,0 +1 @@ +{"2026-05-24": [{"id": "8ifd00fvn8hqjf73o7o24he5os", "title": "Photography Session (Easha Mashud)", "google_event": true}]} \ No newline at end of file diff --git a/flaskServer.py b/flaskServer.py index 77d379e..a27a1a0 100644 --- a/flaskServer.py +++ b/flaskServer.py @@ -29,7 +29,7 @@ #allows for easier starting of flask from start file def run_flask(): - app.run(debug=True, port=5000, use_reloader=False) + app.run(debug=True, port=8000, use_reloader=False)