From 48562c16ed5b242919da0fcc50a70e1984a41e3f Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Wed, 20 Aug 2025 16:24:26 -0600 Subject: [PATCH 01/47] Add calendar route to Views --- src/controls/router/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controls/router/views.py b/src/controls/router/views.py index 05d1809..ffa4dae 100644 --- a/src/controls/router/views.py +++ b/src/controls/router/views.py @@ -18,6 +18,7 @@ from views.charts_view import build_view_charts from views.test_view import TestView from assets.icons.friconix.frx_flet import frx_icon +from views.calendar_view import build_view_calendar position = { @@ -47,6 +48,7 @@ def __init__(self, page, forms): RouteView("Friconix", "/friconix", ft.Icons.NOW_WALLPAPER, lambda:build_view_friconix(page), position["side"]), RouteView("Charts", "/charts", ft.Icons.DONUT_SMALL_ROUNDED, lambda:build_view_charts(page), position["side"]), RouteView("Animations", "/animations", ft.Icons.SPORTS_HANDBALL_ROUNDED, lambda:build_view_animation(page), position["side"]), + RouteView("Calendar", "/calendar", ft.Icons.SPORTS_HANDBALL_ROUNDED, lambda: build_view_calendar(page),position["side"]), RouteView("Login", "/login", ft.Icons.LOGIN, lambda:build_view_login(page), position["top"]), RouteView("Profile", "/profile", ft.Icons.PERSON, lambda:build_view_profile(page), position["top"]), RouteView("Settings", "/settings", ft.Icons.SETTINGS, lambda:build_view_settings(page), position["top"]), From 468979013c3f8bf9cd1a94e0f643fbf1bc651701 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Wed, 20 Aug 2025 16:24:34 -0600 Subject: [PATCH 02/47] Add calendar view implementation with navigation and week header --- src/views/calendar_view.py | 160 +++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/views/calendar_view.py diff --git a/src/views/calendar_view.py b/src/views/calendar_view.py new file mode 100644 index 0000000..9b526a5 --- /dev/null +++ b/src/views/calendar_view.py @@ -0,0 +1,160 @@ +import flet as ft +import datetime + + +class CalendarHeader(ft.Column): + def __init__(self, today, week_days, on_day_change=None): + super().__init__() + self.today = today + self.week_days = week_days + self.day_selected = today + self.on_day_change = on_day_change + self.spacing = 0 + self.controls = [self.build_row()] + + def build_row(self): + return ft.Row( + spacing=0, + controls=[ + ft.Container( + width=60, + height=100, + border=ft.border.all(0.2, ft.Colors.GREY_400), + ), + *[ + ft.Container( + content=ft.Column( + [ + # Día abreviado (%a) + ft.Text( + day.strftime("%a"), + size=11, + weight=ft.FontWeight.W_700, + color="red" + if day == self.today + else "blue" + if day == self.day_selected + else "black", + ), + # Número de día (%d) + ft.Container( + content=ft.Text(day.strftime("%d"), size=21, + color="Red" + if day == self.today + else "White" + if day == self.day_selected + else "black", + ), + alignment=ft.alignment.center, + height=50, + width=50, + border_radius=100, + bgcolor="blue" + if self.day_selected == day + else "white", + on_click=lambda e, d=day: self.change_day(d), + ), + ], + alignment=ft.MainAxisAlignment.CENTER, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + spacing=4, + ), + border=ft.border.all(0.2, ft.Colors.GREY_400), + expand=True, + height=100, + ) + for day in self.week_days + ], + ], + ) + + def change_day(self, day): + self.day_selected = day + self.controls[0] = self.build_row() # reconstruir fila + self.update() + + def update_week(self, week_days): + self.week_days = week_days + self.controls[0] = self.build_row() + self.update() + + +def build_view_calendar(page: ft.Page) -> ft.Container: + today = datetime.date.today() + start_week = today - datetime.timedelta(days=today.weekday()) + week_days = [start_week + datetime.timedelta(days=i) for i in range(7)] + #Mes de la semana + first_month = week_days[0].strftime("%B %Y") + last_month = week_days[-1].strftime("%B %Y") + month_text = first_month if first_month == last_month else f"{first_month} - {last_month}" + month_label = ft.Text(month_text, size=16, weight=ft.FontWeight.W_700) + header = CalendarHeader(today, week_days,) + + # Grilla de horas + grid = ft.Column(spacing=0, scroll=True, expand=True) + for hour in range(0, 24): + row = ft.Row( + spacing=0, + controls=[ + # Columna con la hora + ft.Container(content=ft.Text(f"{hour:02d}:00", color="Black"), alignment= ft.alignment.center, width=60), + # Celdas de los 7 días + ft.Row( + controls=[ + ft.Container( + width=100, + height=40, + bgcolor=ft.Colors.GREY_200, + border=ft.border.all(0.2, ft.Colors.GREY_400), + expand=True, + ) + for _ in week_days + ], + expand=True, + spacing=0, + ), + ], + ) + grid.controls.append(row) + + #Navegacion entre semanas + + def next_week(e): + nonlocal start_week + start_week += datetime.timedelta(days=7) + new_week = [start_week + datetime.timedelta(days=i) for i in range(7)] + header.update_week(new_week) + update_month_label(new_week) + #update_grid(new_week) + + def back_week(e): + nonlocal start_week + start_week -= datetime.timedelta(days=7) + new_week = [start_week + datetime.timedelta(days=i) for i in range(7)] + header.update_week(new_week) + update_month_label(new_week) + #update_grid(new_week) + + def update_month_label(week_days): + # Tomamos el mes del primer día + first_month = week_days[0].strftime("%B %Y") + last_month = week_days[-1].strftime("%B %Y") + if first_month == last_month: + month_label.value = first_month + else: + month_label.value = f"{first_month} - {last_month}" + month_label.update() + + week_navigator = ft.Row([ + ft.IconButton(icon=ft.Icons.ARROW_BACK, icon_color=ft.Colors.BLACK, on_click=back_week), + month_label, + ft.IconButton(icon=ft.Icons.ARROW_FORWARD, icon_color=ft.Colors.BLACK, on_click=next_week) + ]) + + # Layout principal + + return ft.Container( + expand=True, + content = ft.Column([ week_navigator, header, grid], expand=True, spacing=0), + + ) From 17b75723ee85b0a9f8937e810072480c1cb76818 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 26 Aug 2025 12:32:11 -0600 Subject: [PATCH 03/47] Refactor calendar view --- src/views/calendar_view.py | 161 ++----------------------------------- 1 file changed, 6 insertions(+), 155 deletions(-) diff --git a/src/views/calendar_view.py b/src/views/calendar_view.py index 9b526a5..723f688 100644 --- a/src/views/calendar_view.py +++ b/src/views/calendar_view.py @@ -1,160 +1,11 @@ import flet as ft -import datetime - - -class CalendarHeader(ft.Column): - def __init__(self, today, week_days, on_day_change=None): - super().__init__() - self.today = today - self.week_days = week_days - self.day_selected = today - self.on_day_change = on_day_change - self.spacing = 0 - self.controls = [self.build_row()] - - def build_row(self): - return ft.Row( - spacing=0, - controls=[ - ft.Container( - width=60, - height=100, - border=ft.border.all(0.2, ft.Colors.GREY_400), - ), - *[ - ft.Container( - content=ft.Column( - [ - # Día abreviado (%a) - ft.Text( - day.strftime("%a"), - size=11, - weight=ft.FontWeight.W_700, - color="red" - if day == self.today - else "blue" - if day == self.day_selected - else "black", - ), - # Número de día (%d) - ft.Container( - content=ft.Text(day.strftime("%d"), size=21, - color="Red" - if day == self.today - else "White" - if day == self.day_selected - else "black", - ), - alignment=ft.alignment.center, - height=50, - width=50, - border_radius=100, - bgcolor="blue" - if self.day_selected == day - else "white", - on_click=lambda e, d=day: self.change_day(d), - ), - ], - alignment=ft.MainAxisAlignment.CENTER, - horizontal_alignment=ft.CrossAxisAlignment.CENTER, - spacing=4, - ), - border=ft.border.all(0.2, ft.Colors.GREY_400), - expand=True, - height=100, - ) - for day in self.week_days - ], - ], - ) - - def change_day(self, day): - self.day_selected = day - self.controls[0] = self.build_row() # reconstruir fila - self.update() - - def update_week(self, week_days): - self.week_days = week_days - self.controls[0] = self.build_row() - self.update() - - -def build_view_calendar(page: ft.Page) -> ft.Container: - today = datetime.date.today() - start_week = today - datetime.timedelta(days=today.weekday()) - week_days = [start_week + datetime.timedelta(days=i) for i in range(7)] - #Mes de la semana - first_month = week_days[0].strftime("%B %Y") - last_month = week_days[-1].strftime("%B %Y") - month_text = first_month if first_month == last_month else f"{first_month} - {last_month}" - month_label = ft.Text(month_text, size=16, weight=ft.FontWeight.W_700) - header = CalendarHeader(today, week_days,) - - # Grilla de horas - grid = ft.Column(spacing=0, scroll=True, expand=True) - for hour in range(0, 24): - row = ft.Row( - spacing=0, - controls=[ - # Columna con la hora - ft.Container(content=ft.Text(f"{hour:02d}:00", color="Black"), alignment= ft.alignment.center, width=60), - # Celdas de los 7 días - ft.Row( - controls=[ - ft.Container( - width=100, - height=40, - bgcolor=ft.Colors.GREY_200, - border=ft.border.all(0.2, ft.Colors.GREY_400), - expand=True, - ) - for _ in week_days - ], - expand=True, - spacing=0, - ), - ], - ) - grid.controls.append(row) - - #Navegacion entre semanas - - def next_week(e): - nonlocal start_week - start_week += datetime.timedelta(days=7) - new_week = [start_week + datetime.timedelta(days=i) for i in range(7)] - header.update_week(new_week) - update_month_label(new_week) - #update_grid(new_week) - - def back_week(e): - nonlocal start_week - start_week -= datetime.timedelta(days=7) - new_week = [start_week + datetime.timedelta(days=i) for i in range(7)] - header.update_week(new_week) - update_month_label(new_week) - #update_grid(new_week) - - def update_month_label(week_days): - # Tomamos el mes del primer día - first_month = week_days[0].strftime("%B %Y") - last_month = week_days[-1].strftime("%B %Y") - if first_month == last_month: - month_label.value = first_month - else: - month_label.value = f"{first_month} - {last_month}" - month_label.update() - - week_navigator = ft.Row([ - ft.IconButton(icon=ft.Icons.ARROW_BACK, icon_color=ft.Colors.BLACK, on_click=back_week), - month_label, - ft.IconButton(icon=ft.Icons.ARROW_FORWARD, icon_color=ft.Colors.BLACK, on_click=next_week) - ]) - - # Layout principal +from components.shared.calendar import Calendar +def build_view_calendar(page): return ft.Container( + content=Calendar(), expand=True, - content = ft.Column([ week_navigator, header, grid], expand=True, spacing=0), - ) + +if __name__ == "__main__": + ft.app(target=build_view_calendar) From b5b95d604d4430f45ac16f51c25c8a0a84590f42 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 26 Aug 2025 12:32:22 -0600 Subject: [PATCH 04/47] Implement calendar component with header, grid, and event management --- src/components/shared/calendar.py | 228 ++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/components/shared/calendar.py diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py new file mode 100644 index 0000000..5ce8365 --- /dev/null +++ b/src/components/shared/calendar.py @@ -0,0 +1,228 @@ +import flet as ft +import datetime +import uuid +from data.models import calendar_event +from data.manager.calendar_event_manager import EventManager + +# --------------------------- +# Cabecera del calendario +# --------------------------- +class CalendarHeader(ft.Column): + def __init__(self, today, week_days, on_day_change=None): + super().__init__() + self.today = today + self.week_days = week_days + self.day_selected = today + self.on_day_change = on_day_change + self.spacing = 0 + self.controls = [self.build_row()] + + def build_row(self): + return ft.Row( + spacing=0, + controls=[ + ft.Container(width=60, height=100, border=ft.border.all(0.2, ft.Colors.GREY_400)), + *[ + ft.Container( + content=ft.Column( + [ + ft.Text( + day.strftime("%a"), + size=11, + weight=ft.FontWeight.W_700, + color="red" if day == self.today else "blue" if day == self.day_selected else "black", + ), + ft.Container( + content=ft.Text( + day.strftime("%d"), + size=21, + color= "White" if day == self.day_selected else "red" if day == self.today else "black", + ), + alignment=ft.alignment.center, + height=50, + width=50, + border_radius=100, + bgcolor="blue" if self.day_selected == day else None, + on_click=lambda e, d=day: self.change_day(d), + ), + ], + alignment=ft.MainAxisAlignment.CENTER, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + spacing=4, + ), + border=ft.border.all(0.2, ft.Colors.GREY_400), + expand=True, + height=100, + ) + for day in self.week_days + ], + ], + ) + + def change_day(self, day): + self.day_selected = day + self.controls[0] = self.build_row() + self.update() + + def update_week(self, week_days): + self.week_days = week_days + self.controls[0] = self.build_row() + self.update() + +# --------------------------- +# Grilla del calendario +# --------------------------- +class CalendarGrid(ft.Column): + def __init__(self, week_days, events, hour_height=60): + super().__init__(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0) + self.week_days = week_days + self.events = events + self.hour_height = hour_height + self.build_grid() + + def build_grid(self): + self.controls = [] + + # Columna de horas + hour_column = ft.Column( + controls=[ + ft.Container( + content=ft.Text(f"{h:02d}:00"), + height=self.hour_height, + alignment=ft.alignment.center + ) for h in range(24) + ], + spacing=0 + ) + + # Fila principal: primera columna horas + días + main_row = ft.Row(expand=True, spacing=0) + main_row.controls.append(ft.Container(width=60, content=hour_column)) + + # Columnas de días + for day in self.week_days: + day_stack = ft.Stack(height=self.hour_height * 24, expand=True, controls=[]) + + # Celdas de fondo + for h in range(24): + day_stack.controls.append( + ft.Container( + top=h*self.hour_height, + left=0, + right=0, + height=self.hour_height, + bgcolor=ft.Colors.GREY_200, + border=ft.border.all(0.2, ft.Colors.GREY_400), + ) + ) + + # Eventos del día + day_events = [ev for ev in self.events if ev.date == day] + for ev in day_events: + start_hour = int(ev.start_time.split(":")[0]) + end_hour = int(ev.end_time.split(":")[0]) + duration = max(1, end_hour - start_hour) + + day_stack.controls.append( + ft.Container( + top=self.hour_height * start_hour, + left=0, + right=5, + height=self.hour_height * duration, + bgcolor=ev.color, + alignment=ft.alignment.center, + content=ft.Text(ev.title, size=10, color="white"), + ) + ) + main_row.controls.append(ft.Container(content=day_stack, expand=1)) + + self.controls.append(main_row) + + def render_events(self, events, week_days): + self.events = events + self.week_days = week_days + self.build_grid() + self.update() + +# --------------------------- +# Vista principal del calendario +# --------------------------- +class Calendar(ft.Container): + def __init__(self): + super().__init__(expand=True) + + # Estado inicial + self.today = datetime.date.today() + self.start_week = self.today - datetime.timedelta(days=self.today.weekday()) + self.week_days = [self.start_week + datetime.timedelta(days=i) for i in range(7)] + self.event_manager = EventManager() + + # Eventos iniciales + self._load_initial_events() + + #Label de el mes + self.month_label = ft.Text("", size=16, weight=ft.FontWeight.W_700) + first_month = self.week_days[0].strftime("%B %Y") + last_month = self.week_days[-1].strftime("%B %Y") + self.month_label.value = first_month if first_month == last_month else f"{first_month} - {last_month}" + + + self.header = CalendarHeader(self.today, self.week_days) + self.grid = CalendarGrid(self.week_days, self.event_manager.get_events_for_week(self.week_days)) + + # Botones de navegación + self.week_navigator = ft.Row([ + ft.IconButton(icon=ft.Icons.ARROW_BACK, icon_color=ft.Colors.BLACK, on_click=self.back_week), + self.month_label, + ft.IconButton(icon=ft.Icons.ARROW_FORWARD, icon_color=ft.Colors.BLACK, on_click=self.next_week), + ft.IconButton(icon=ft.Icons.ADD, icon_color=ft.Colors.BLACK, on_click=self.create_event), + ]) + + # Layout principal + self.content = ft.Column([self.week_navigator, self.header, self.grid], expand=True, spacing=0) + + #Se cargan eventos de prueba + def _load_initial_events(self): + self.event_manager = EventManager() + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Reunión equipo", self.today, "09:00", "11:00", "green")) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Clase inglés", self.today, "14:00", "15:00", "blue")) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Gym", self.today, "18:00", "19:00", "purple")) + day_1 = self.today + datetime.timedelta(days=1) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Proyecto X", day_1, "10:00", "12:00", "orange")) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Cita médica", day_1, "16:00", "17:00", "red")) + day_2 = datetime.date(self.today.year, 9, 8) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Reunión con clientes", day_2, "11:00", "12:30", "blue")) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Clase de yoga", day_2, "18:00", "19:00", "purple")) + + + def update_month_label(self, week_days): + first_month = week_days[0].strftime("%B %Y") + last_month = week_days[-1].strftime("%B %Y") + self.month_label.value = first_month if first_month == last_month else f"{first_month} - {last_month}" + self.month_label.update() + + def next_week(self, e): + self.start_week += datetime.timedelta(days=7) + self._update_week() + + def back_week(self, e): + self.start_week -= datetime.timedelta(days=7) + self._update_week() + + def create_event(self, e): + new_event = calendar_event.Event( + id=str(uuid.uuid4()), + title="Nuevo evento", + date=self.week_days[6], + start_time="10:00", + end_time="12:00", + color=ft.Colors.GREEN_800, + ) + self.event_manager.add_event(new_event) + self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) + + def _update_week(self): + self.week_days = [self.start_week + datetime.timedelta(days=i) for i in range(7)] + self.header.update_week(self.week_days) + self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) + self.update_month_label(self.week_days) \ No newline at end of file From c5a5b57e297524ddbd755ad19d0b562feae7bd0c Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 26 Aug 2025 12:32:31 -0600 Subject: [PATCH 05/47] Add Event class for calendar event management --- src/data/models/calendar_event.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/data/models/calendar_event.py diff --git a/src/data/models/calendar_event.py b/src/data/models/calendar_event.py new file mode 100644 index 0000000..93f9fd1 --- /dev/null +++ b/src/data/models/calendar_event.py @@ -0,0 +1,8 @@ +class Event: + def __init__(self, id, title, date, start_time, end_time, color="blue"): + self.id = id + self.title = title + self.date = date + self.start_time = start_time + self.end_time = end_time + self.color = color \ No newline at end of file From d0f88471a4ec9a765c3c96546d74735e2ebc2859 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 26 Aug 2025 12:32:36 -0600 Subject: [PATCH 06/47] Add EventManager class for managing calendar events --- src/data/manager/calendar_event_manager.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/data/manager/calendar_event_manager.py diff --git a/src/data/manager/calendar_event_manager.py b/src/data/manager/calendar_event_manager.py new file mode 100644 index 0000000..b589e09 --- /dev/null +++ b/src/data/manager/calendar_event_manager.py @@ -0,0 +1,18 @@ + +from data.models import calendar_event + +class EventManager: + def __init__(self): + self.events: list[calendar_event.Event] = [] + + def add_event(self, event: calendar_event.Event): + self.events.append(event) + + def remove_event(self, event_id: str): + self.events = [ev for ev in self.events if ev.id != event_id] + + def get_events_for_week(self, week_days): + return [ev for ev in self.events if ev.date in week_days] + + def get_events_for_day(self, day): + return [ev for ev in self.events if ev.date == day] From 7f0cf81b53f80263df07f1895af22ec216fcadf8 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 1 Sep 2025 15:40:39 -0600 Subject: [PATCH 07/47] Add DraggableModal class for creating movable modal dialogs --- src/components/shared/prueba.py | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/components/shared/prueba.py diff --git a/src/components/shared/prueba.py b/src/components/shared/prueba.py new file mode 100644 index 0000000..bf84ac4 --- /dev/null +++ b/src/components/shared/prueba.py @@ -0,0 +1,56 @@ +import flet as ft +import datetime + + +class DraggableModal: + def __init__(self, left=100, top=100, width=260, content=None): + self.left = left + self.top = top + self.width = width + self.visible = False + self.content = content + + # Contenido del modal con GestureDetector + self.modal_content = ft.GestureDetector( + content= self.content, + on_pan_update=self.move_modal, + ) + + # Container principal posicionado en el Stack + self.modal = ft.Container( + content=self.modal_content, + bgcolor=ft.Colors.WHITE, + border_radius=10, + shadow=ft.BoxShadow(blur_radius=10, color=ft.Colors.BLACK26), + padding=15, + width=self.width, + left=self.left, + top=self.top, + visible=self.visible, + ) + + + # Función para mover el modal + def move_modal(self, e: ft.DragUpdateEvent): + self.modal.left += e.delta_x + self.modal.top += e.delta_y + self.modal.update() + + # Abrir el modal + def open(self): + self.modal.visible = True + self.modal.update() + + # Cerrar el modal + def close(self): + self.modal.visible = False + self.modal.update() + + # Para usarlo en page.add + def get_control(self): + return self.modal + + + + + From 985c72c4e300a287d03b77fe7a912d9b0f94e30b Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 1 Sep 2025 15:41:07 -0600 Subject: [PATCH 08/47] Add FormCalendar class and integrate DraggableModal for event creation --- src/components/shared/calendar.py | 74 ++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 5ce8365..92a350e 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -3,6 +3,8 @@ import uuid from data.models import calendar_event from data.manager.calendar_event_manager import EventManager +from components.shared.prueba import DraggableModal + # --------------------------- # Cabecera del calendario @@ -21,7 +23,7 @@ def build_row(self): return ft.Row( spacing=0, controls=[ - ft.Container(width=60, height=100, border=ft.border.all(0.2, ft.Colors.GREY_400)), + ft.Container(width=60, height=100, border=ft.border.only( top=ft.BorderSide(0.2, ft.Colors.GREY_400), right=ft.BorderSide(0.2, ft.Colors.GREY_400), bottom=ft.BorderSide(0.2, ft.Colors.GREY_400)),), *[ ft.Container( content=ft.Column( @@ -50,7 +52,7 @@ def build_row(self): horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=4, ), - border=ft.border.all(0.2, ft.Colors.GREY_400), + border=ft.border.only( top=ft.BorderSide(0.2, ft.Colors.GREY_400), bottom=ft.BorderSide(0.2, ft.Colors.GREY_400)), expand=True, height=100, ) @@ -89,7 +91,7 @@ def build_grid(self): ft.Container( content=ft.Text(f"{h:02d}:00"), height=self.hour_height, - alignment=ft.alignment.center + alignment=ft.alignment.top_center ) for h in range(24) ], spacing=0 @@ -111,7 +113,6 @@ def build_grid(self): left=0, right=0, height=self.hour_height, - bgcolor=ft.Colors.GREY_200, border=ft.border.all(0.2, ft.Colors.GREY_400), ) ) @@ -144,6 +145,43 @@ def render_events(self, events, week_days): self.build_grid() self.update() + +class FormCalendar(ft.Column): + def __init__(self, save_event, close_callback): + super().__init__(expand=True, tight=True, spacing=10) + self.save_event = save_event + self.close_callback = close_callback + self.fechaInicio = str(datetime.date.today()) + self.fechaFin = "" + self.NombreEvento = "" + self.build_form() + + def build_form(self): + self.controls.append( + ft.TextField(label="Nombre del evento", width=200, on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) + ) + self.controls.append( + ft.TextField( + label="Fecha inicio", value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + ) + ) + self.controls.append( + ft.TextField( + label="Fecha fin", + value=str(datetime.date.today() + datetime.timedelta(days=1)), + width=200, + on_change=lambda e: setattr(self, 'fechaFin', e.control.value) + ) + ) + self.controls.append( + ft.Row( + [ + ft.ElevatedButton("Guardar", icon=ft.Icons.SAVE, on_click=self.save_event), + ft.ElevatedButton("Cerrar", icon=ft.Icons.CLOSE, on_click=self.close_callback), + ] + ) + ) + # --------------------------- # Vista principal del calendario # --------------------------- @@ -169,17 +207,29 @@ def __init__(self): self.header = CalendarHeader(self.today, self.week_days) self.grid = CalendarGrid(self.week_days, self.event_manager.get_events_for_week(self.week_days)) + self.formCalendar = FormCalendar( + save_event=lambda e: self.create_event(e), + close_callback=lambda e=None: self.modal.close() + ) + + self.modal = DraggableModal(left=150, top=150, content=self.formCalendar) # Botones de navegación self.week_navigator = ft.Row([ ft.IconButton(icon=ft.Icons.ARROW_BACK, icon_color=ft.Colors.BLACK, on_click=self.back_week), self.month_label, ft.IconButton(icon=ft.Icons.ARROW_FORWARD, icon_color=ft.Colors.BLACK, on_click=self.next_week), - ft.IconButton(icon=ft.Icons.ADD, icon_color=ft.Colors.BLACK, on_click=self.create_event), + ft.IconButton(icon=ft.Icons.ADD, icon_color=ft.Colors.BLACK, on_click=lambda e: self.modal.open()), ]) # Layout principal - self.content = ft.Column([self.week_navigator, self.header, self.grid], expand=True, spacing=0) + self.content = ft.Stack( + controls=[ + ft.Column([self.week_navigator, self.header, self.grid], expand=True, spacing=0, ), + self.modal.get_control(), + ] + ) + #Se cargan eventos de prueba def _load_initial_events(self): @@ -188,7 +238,7 @@ def _load_initial_events(self): self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Clase inglés", self.today, "14:00", "15:00", "blue")) self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Gym", self.today, "18:00", "19:00", "purple")) day_1 = self.today + datetime.timedelta(days=1) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Proyecto X", day_1, "10:00", "12:00", "orange")) + self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Proyecto X", day_1, "10:00", "12:15", "orange")) self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Cita médica", day_1, "16:00", "17:00", "red")) day_2 = datetime.date(self.today.year, 9, 8) self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Reunión con clientes", day_2, "11:00", "12:30", "blue")) @@ -210,15 +260,17 @@ def back_week(self, e): self._update_week() def create_event(self, e): + print(self.formCalendar.fechaInicio) new_event = calendar_event.Event( id=str(uuid.uuid4()), - title="Nuevo evento", - date=self.week_days[6], - start_time="10:00", - end_time="12:00", + title=self.formCalendar.NombreEvento, + date=datetime.datetime.strptime(self.formCalendar.fechaInicio, "%Y-%m-%d").date(), + start_time="12:00", + end_time="13:00", color=ft.Colors.GREEN_800, ) self.event_manager.add_event(new_event) + self.modal.close() self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) def _update_week(self): From b4a043cfaea849c84d1b1aaa5da1b1a641a0d6f5 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 1 Sep 2025 17:06:46 -0600 Subject: [PATCH 09/47] Refactor FormCalendar to include start and end time fields for event creation --- src/components/shared/calendar.py | 51 +++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 92a350e..2d3356a 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -3,7 +3,7 @@ import uuid from data.models import calendar_event from data.manager.calendar_event_manager import EventManager -from components.shared.prueba import DraggableModal +from components.shared.draggableModal import DraggableModal # --------------------------- @@ -152,27 +152,52 @@ def __init__(self, save_event, close_callback): self.save_event = save_event self.close_callback = close_callback self.fechaInicio = str(datetime.date.today()) - self.fechaFin = "" - self.NombreEvento = "" + self.horaInicio = "00:00" + self.fechaFin = str(datetime.date.today()) + self.horaFin = "00:00" + self.NombreEvento = "Nuevo evento" self.build_form() def build_form(self): + self.controls.append( - ft.TextField(label="Nombre del evento", width=200, on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) + ft.TextField(label="Nombre del evento", on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) ) self.controls.append( - ft.TextField( - label="Fecha inicio", value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + ft.Column( + [ + ft.Text("Inicio"), + ft.Row( + [ + ft.TextField( + value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + ), + ft.TextField( + value=self.horaInicio, width=200, on_change=lambda e: setattr(self, 'horaInicio', e.control.value) + ) + ] + ) + ] ) ) self.controls.append( - ft.TextField( - label="Fecha fin", - value=str(datetime.date.today() + datetime.timedelta(days=1)), - width=200, - on_change=lambda e: setattr(self, 'fechaFin', e.control.value) + ft.Column( + [ + ft.Text("Fin"), + ft.Row( + [ + ft.TextField( + value=self.fechaFin, width=200, on_change=lambda e: setattr(self, 'fechaFin', e.control.value) + ), + ft.TextField( + value=self.horaFin, width=200, on_change=lambda e: setattr(self, 'horaFin', e.control.value) + ) + ] + ) + ] ) ) + self.controls.append( ft.Row( [ @@ -265,8 +290,8 @@ def create_event(self, e): id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, date=datetime.datetime.strptime(self.formCalendar.fechaInicio, "%Y-%m-%d").date(), - start_time="12:00", - end_time="13:00", + start_time=self.formCalendar.horaInicio, + end_time=self.formCalendar.horaFin, color=ft.Colors.GREEN_800, ) self.event_manager.add_event(new_event) From 2d0c3166b3251aec8ba0f3048777b10a57b4629f Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 1 Sep 2025 17:07:28 -0600 Subject: [PATCH 10/47] rename file --- src/components/shared/{prueba.py => draggableModal.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/components/shared/{prueba.py => draggableModal.py} (90%) diff --git a/src/components/shared/prueba.py b/src/components/shared/draggableModal.py similarity index 90% rename from src/components/shared/prueba.py rename to src/components/shared/draggableModal.py index bf84ac4..c524506 100644 --- a/src/components/shared/prueba.py +++ b/src/components/shared/draggableModal.py @@ -3,10 +3,11 @@ class DraggableModal: - def __init__(self, left=100, top=100, width=260, content=None): + def __init__(self, left=100, top=100, content=None): self.left = left + self.expand = True self.top = top - self.width = width + self.visible = False self.content = content @@ -23,7 +24,6 @@ def __init__(self, left=100, top=100, width=260, content=None): border_radius=10, shadow=ft.BoxShadow(blur_radius=10, color=ft.Colors.BLACK26), padding=15, - width=self.width, left=self.left, top=self.top, visible=self.visible, From 672364983edc2bc6a6478e65b92ada2067573092 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 2 Sep 2025 11:24:42 -0600 Subject: [PATCH 11/47] Refactor Calendar classes to improve event handling and modal integration --- src/components/shared/calendar.py | 167 +++++++++++++++++++----------- 1 file changed, 104 insertions(+), 63 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 2d3356a..c9ae5d3 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -23,7 +23,13 @@ def build_row(self): return ft.Row( spacing=0, controls=[ - ft.Container(width=60, height=100, border=ft.border.only( top=ft.BorderSide(0.2, ft.Colors.GREY_400), right=ft.BorderSide(0.2, ft.Colors.GREY_400), bottom=ft.BorderSide(0.2, ft.Colors.GREY_400)),), + ft.Container(width=60, height=100, + border=ft.border.only( + top=ft.BorderSide(0.2, ft.Colors.GREY_400), + right=ft.BorderSide(0.2, ft.Colors.GREY_400), + bottom=ft.BorderSide(0.2, ft.Colors.GREY_400) + ), + ), *[ ft.Container( content=ft.Column( @@ -38,7 +44,7 @@ def build_row(self): content=ft.Text( day.strftime("%d"), size=21, - color= "White" if day == self.day_selected else "red" if day == self.today else "black", + color="White" if day == self.day_selected else "red" if day == self.today else "black", ), alignment=ft.alignment.center, height=50, @@ -52,7 +58,10 @@ def build_row(self): horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=4, ), - border=ft.border.only( top=ft.BorderSide(0.2, ft.Colors.GREY_400), bottom=ft.BorderSide(0.2, ft.Colors.GREY_400)), + border=ft.border.only( + top=ft.BorderSide(0.2, ft.Colors.GREY_400), + bottom=ft.BorderSide(0.2, ft.Colors.GREY_400) + ), expand=True, height=100, ) @@ -75,11 +84,12 @@ def update_week(self, week_days): # Grilla del calendario # --------------------------- class CalendarGrid(ft.Column): - def __init__(self, week_days, events, hour_height=60): + def __init__(self, week_days, events, hour_height=60, open_edit_event=None): # 🔹 EDITAR: callback super().__init__(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0) self.week_days = week_days self.events = events self.hour_height = hour_height + self.open_edit_event = open_edit_event # 🔹 EDITAR self.build_grid() def build_grid(self): @@ -96,7 +106,6 @@ def build_grid(self): ], spacing=0 ) - # Fila principal: primera columna horas + días main_row = ft.Row(expand=True, spacing=0) main_row.controls.append(ft.Container(width=60, content=hour_column)) @@ -115,8 +124,7 @@ def build_grid(self): height=self.hour_height, border=ft.border.all(0.2, ft.Colors.GREY_400), ) - ) - + ) # Eventos del día day_events = [ev for ev in self.events if ev.date == day] for ev in day_events: @@ -133,6 +141,7 @@ def build_grid(self): bgcolor=ev.color, alignment=ft.alignment.center, content=ft.Text(ev.title, size=10, color="white"), + on_click=lambda e, ev=ev: self.open_edit_event(ev) if self.open_edit_event else None # 🔹 EDITAR ) ) main_row.controls.append(ft.Container(content=day_stack, expand=1)) @@ -145,67 +154,68 @@ def render_events(self, events, week_days): self.build_grid() self.update() - +# --------------------------- +# Formulario de crear/editar +# --------------------------- class FormCalendar(ft.Column): - def __init__(self, save_event, close_callback): + def __init__(self, save_event, close_callback, event=None): # 🔹 EDITAR super().__init__(expand=True, tight=True, spacing=10) self.save_event = save_event self.close_callback = close_callback - self.fechaInicio = str(datetime.date.today()) - self.horaInicio = "00:00" - self.fechaFin = str(datetime.date.today()) - self.horaFin = "00:00" - self.NombreEvento = "Nuevo evento" - self.build_form() + self.event = event + # Inicializa los campos según si es edición o creación + if self.event: + self.fechaInicio = str(self.event.date) + self.horaInicio = self.event.start_time + self.fechaFin = str(self.event.date) + self.horaFin = self.event.end_time + self.NombreEvento = self.event.title + else: + self.fechaInicio = str(datetime.date.today()) + self.horaInicio = "00:00" + self.fechaFin = str(datetime.date.today()) + self.horaFin = "00:00" + self.NombreEvento = "Nuevo evento" + + self.build_form() def build_form(self): - + self.controls.clear() # 🔹 EDITAR + print("Build", self.NombreEvento) self.controls.append( - ft.TextField(label="Nombre del evento", on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) + ft.TextField(value=self.NombreEvento, label="Nombre del evento", + on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) ) self.controls.append( - ft.Column( - [ - ft.Text("Inicio"), - ft.Row( - [ - ft.TextField( - value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) - ), - ft.TextField( - value=self.horaInicio, width=200, on_change=lambda e: setattr(self, 'horaInicio', e.control.value) - ) - ] - ) - ] - ) + ft.Column([ + ft.Text("Inicio"), + ft.Row([ + ft.TextField(value=self.fechaInicio, width=200, + on_change=lambda e: setattr(self, 'fechaInicio', e.control.value)), + ft.TextField(value=self.horaInicio, width=200, + on_change=lambda e: setattr(self, 'horaInicio', e.control.value)) + ]) + ]) ) self.controls.append( - ft.Column( - [ - ft.Text("Fin"), - ft.Row( - [ - ft.TextField( - value=self.fechaFin, width=200, on_change=lambda e: setattr(self, 'fechaFin', e.control.value) - ), - ft.TextField( - value=self.horaFin, width=200, on_change=lambda e: setattr(self, 'horaFin', e.control.value) - ) - ] - ) - ] - ) + ft.Column([ + ft.Text("Fin"), + ft.Row([ + ft.TextField(value=self.fechaFin, width=200, + on_change=lambda e: setattr(self, 'fechaFin', e.control.value)), + ft.TextField(value=self.horaFin, width=200, + on_change=lambda e: setattr(self, 'horaFin', e.control.value)) + ]) + ]) ) - + self.controls.append( - ft.Row( - [ - ft.ElevatedButton("Guardar", icon=ft.Icons.SAVE, on_click=self.save_event), - ft.ElevatedButton("Cerrar", icon=ft.Icons.CLOSE, on_click=self.close_callback), - ] - ) + ft.Row([ + ft.ElevatedButton("Guardar", icon=ft.Icons.SAVE, on_click=self.save_event), + ft.ElevatedButton("Cerrar", icon=ft.Icons.CLOSE, on_click=self.close_callback), + ]) ) + # --------------------------- # Vista principal del calendario @@ -223,18 +233,21 @@ def __init__(self): # Eventos iniciales self._load_initial_events() - #Label de el mes + # Label del mes self.month_label = ft.Text("", size=16, weight=ft.FontWeight.W_700) first_month = self.week_days[0].strftime("%B %Y") last_month = self.week_days[-1].strftime("%B %Y") self.month_label.value = first_month if first_month == last_month else f"{first_month} - {last_month}" - self.header = CalendarHeader(self.today, self.week_days) - self.grid = CalendarGrid(self.week_days, self.event_manager.get_events_for_week(self.week_days)) + self.grid = CalendarGrid( + self.week_days, + self.event_manager.get_events_for_week(self.week_days), + open_edit_event=self.open_edit_event # 🔹 EDITAR + ) self.formCalendar = FormCalendar( - save_event=lambda e: self.create_event(e), - close_callback=lambda e=None: self.modal.close() + save_event=lambda e: self.create_event(e), + close_callback=lambda e=None: self.modal.close() ) self.modal = DraggableModal(left=150, top=150, content=self.formCalendar) @@ -244,7 +257,7 @@ def __init__(self): ft.IconButton(icon=ft.Icons.ARROW_BACK, icon_color=ft.Colors.BLACK, on_click=self.back_week), self.month_label, ft.IconButton(icon=ft.Icons.ARROW_FORWARD, icon_color=ft.Colors.BLACK, on_click=self.next_week), - ft.IconButton(icon=ft.Icons.ADD, icon_color=ft.Colors.BLACK, on_click=lambda e: self.modal.open()), + ft.IconButton(icon=ft.Icons.ADD, icon_color=ft.Colors.BLACK, on_click=lambda e: self.open_new_event()), ]) # Layout principal @@ -256,7 +269,7 @@ def __init__(self): ) - #Se cargan eventos de prueba + # Se cargan eventos de prueba def _load_initial_events(self): self.event_manager = EventManager() self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Reunión equipo", self.today, "09:00", "11:00", "green")) @@ -284,8 +297,17 @@ def back_week(self, e): self.start_week -= datetime.timedelta(days=7) self._update_week() + # 🔹 EDITAR: abrir modal vacío + def open_new_event(self): + self.formCalendar = FormCalendar( + save_event=lambda e: self.create_event(e), + close_callback=lambda e=None: self.modal.close() + ) + + self.modal.update_content(self.formCalendar) + self.modal.open() + def create_event(self, e): - print(self.formCalendar.fechaInicio) new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, @@ -298,8 +320,27 @@ def create_event(self, e): self.modal.close() self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) + # abrir modal en modo edición + def open_edit_event(self, event): + self.formCalendar = FormCalendar( + save_event=lambda e, ev=event: self.update_event(ev), + close_callback=lambda e=None: self.modal.close(), + event=event + ) + self.modal.update_content(self.formCalendar) + self.modal.open() + + # actualizar evento existente + def update_event(self, event): + event.title = self.formCalendar.NombreEvento + event.date = datetime.datetime.strptime(self.formCalendar.fechaInicio, "%Y-%m-%d").date() + event.start_time = self.formCalendar.horaInicio + event.end_time = self.formCalendar.horaFin + self.modal.close() + self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) + def _update_week(self): self.week_days = [self.start_week + datetime.timedelta(days=i) for i in range(7)] self.header.update_week(self.week_days) self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) - self.update_month_label(self.week_days) \ No newline at end of file + self.update_month_label(self.week_days) From 325f55e4510cd10ab72816e8a641a30e4a7b26fa Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 2 Sep 2025 11:24:51 -0600 Subject: [PATCH 12/47] Update Calendar route icon to use CALENDAR_MONTH --- src/controls/router/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controls/router/views.py b/src/controls/router/views.py index ffa4dae..6fe29c7 100644 --- a/src/controls/router/views.py +++ b/src/controls/router/views.py @@ -48,7 +48,7 @@ def __init__(self, page, forms): RouteView("Friconix", "/friconix", ft.Icons.NOW_WALLPAPER, lambda:build_view_friconix(page), position["side"]), RouteView("Charts", "/charts", ft.Icons.DONUT_SMALL_ROUNDED, lambda:build_view_charts(page), position["side"]), RouteView("Animations", "/animations", ft.Icons.SPORTS_HANDBALL_ROUNDED, lambda:build_view_animation(page), position["side"]), - RouteView("Calendar", "/calendar", ft.Icons.SPORTS_HANDBALL_ROUNDED, lambda: build_view_calendar(page),position["side"]), + RouteView("Calendar", "/calendar", ft.Icons.CALENDAR_MONTH, lambda: build_view_calendar(page),position["side"]), RouteView("Login", "/login", ft.Icons.LOGIN, lambda:build_view_login(page), position["top"]), RouteView("Profile", "/profile", ft.Icons.PERSON, lambda:build_view_profile(page), position["top"]), RouteView("Settings", "/settings", ft.Icons.SETTINGS, lambda:build_view_settings(page), position["top"]), From f72e3a403dd188d6244b69010c909575afd5709b Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 2 Sep 2025 11:24:59 -0600 Subject: [PATCH 13/47] Add update_content method to DraggableModal for dynamic content updates --- src/components/shared/draggableModal.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/shared/draggableModal.py b/src/components/shared/draggableModal.py index c524506..ea81819 100644 --- a/src/components/shared/draggableModal.py +++ b/src/components/shared/draggableModal.py @@ -17,6 +17,8 @@ def __init__(self, left=100, top=100, content=None): on_pan_update=self.move_modal, ) + + # Container principal posicionado en el Stack self.modal = ft.Container( content=self.modal_content, @@ -28,6 +30,11 @@ def __init__(self, left=100, top=100, content=None): top=self.top, visible=self.visible, ) + def update_content(self, new_content): + self.content = new_content + self.modal_content.content = new_content + self.modal_content.update() + self.modal.update() # Función para mover el modal From a1e87158b0b3a27e94873255a9562e237bee3b6a Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 2 Sep 2025 11:26:11 -0600 Subject: [PATCH 14/47] Clean up comments and formatting in CalendarGrid and FormCalendar classes --- src/components/shared/calendar.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index c9ae5d3..e0bccf8 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -84,12 +84,12 @@ def update_week(self, week_days): # Grilla del calendario # --------------------------- class CalendarGrid(ft.Column): - def __init__(self, week_days, events, hour_height=60, open_edit_event=None): # 🔹 EDITAR: callback + def __init__(self, week_days, events, hour_height=60, open_edit_event=None): super().__init__(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0) self.week_days = week_days self.events = events self.hour_height = hour_height - self.open_edit_event = open_edit_event # 🔹 EDITAR + self.open_edit_event = open_edit_event self.build_grid() def build_grid(self): @@ -158,7 +158,7 @@ def render_events(self, events, week_days): # Formulario de crear/editar # --------------------------- class FormCalendar(ft.Column): - def __init__(self, save_event, close_callback, event=None): # 🔹 EDITAR + def __init__(self, save_event, close_callback, event=None): super().__init__(expand=True, tight=True, spacing=10) self.save_event = save_event self.close_callback = close_callback @@ -180,7 +180,7 @@ def __init__(self, save_event, close_callback, event=None): # 🔹 EDITAR self.build_form() def build_form(self): - self.controls.clear() # 🔹 EDITAR + self.controls.clear() print("Build", self.NombreEvento) self.controls.append( ft.TextField(value=self.NombreEvento, label="Nombre del evento", @@ -297,7 +297,7 @@ def back_week(self, e): self.start_week -= datetime.timedelta(days=7) self._update_week() - # 🔹 EDITAR: abrir modal vacío + #abrir modal vacío def open_new_event(self): self.formCalendar = FormCalendar( save_event=lambda e: self.create_event(e), From 5a0cbe4a0c173bd58c5c9bac34b8fd9963df3aab Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 2 Sep 2025 12:51:40 -0600 Subject: [PATCH 15/47] Add time selection dropdowns to FormCalendar for event creation --- src/components/shared/calendar.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index e0bccf8..13d5af1 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -177,7 +177,15 @@ def __init__(self, save_event, close_callback, event=None): self.horaFin = "00:00" self.NombreEvento = "Nuevo evento" - self.build_form() + self.build_form() + + def get_time_options(self): + options = [] + for h in range(24): + value = f"{h:02d}:00" # valor real en 24h + label = datetime.time(h, 0).strftime("%I:%M %p").lstrip("0") # formato 12h + options.append(ft.dropdownm2.Option(text=label, key=value)) + return options def build_form(self): self.controls.clear() @@ -192,8 +200,8 @@ def build_form(self): ft.Row([ ft.TextField(value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value)), - ft.TextField(value=self.horaInicio, width=200, - on_change=lambda e: setattr(self, 'horaInicio', e.control.value)) + ft.DropdownM2(max_menu_height=250, width=200, options=self.get_time_options(), + value=self.horaInicio, on_change=lambda e: setattr(self, 'horaInicio', e.control.value)) ]) ]) ) @@ -203,8 +211,8 @@ def build_form(self): ft.Row([ ft.TextField(value=self.fechaFin, width=200, on_change=lambda e: setattr(self, 'fechaFin', e.control.value)), - ft.TextField(value=self.horaFin, width=200, - on_change=lambda e: setattr(self, 'horaFin', e.control.value)) + ft.DropdownM2(max_menu_height=250, width=200, options=self.get_time_options(), + value=self.horaFin, on_change=lambda e: setattr(self, 'horaFin', e.control.value)) ]) ]) ) From 8a374110d8ed7a6f64402cd3ae995831d2716249 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 2 Sep 2025 12:59:50 -0600 Subject: [PATCH 16/47] Replace DropdownM2 with Dropdown in FormCalendar for time selection, enabling filtering and editing options. --- src/components/shared/calendar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 13d5af1..75778aa 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -200,7 +200,7 @@ def build_form(self): ft.Row([ ft.TextField(value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value)), - ft.DropdownM2(max_menu_height=250, width=200, options=self.get_time_options(), + ft.Dropdown(menu_height=250, menu_width=150, options=self.get_time_options(), enable_filter=True,editable=True, value=self.horaInicio, on_change=lambda e: setattr(self, 'horaInicio', e.control.value)) ]) ]) @@ -211,7 +211,7 @@ def build_form(self): ft.Row([ ft.TextField(value=self.fechaFin, width=200, on_change=lambda e: setattr(self, 'fechaFin', e.control.value)), - ft.DropdownM2(max_menu_height=250, width=200, options=self.get_time_options(), + ft.Dropdown(menu_height=250, menu_width=150, options=self.get_time_options(), enable_filter=True,editable=True, value=self.horaFin, on_change=lambda e: setattr(self, 'horaFin', e.control.value)) ]) ]) From 5f5c1263d533efa4279cc4d9a91202981995148d Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Wed, 3 Sep 2025 12:55:02 -0600 Subject: [PATCH 17/47] Add delete event functionality to FormCalendar and update button layout --- src/components/shared/calendar.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 75778aa..6621cd4 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -158,9 +158,10 @@ def render_events(self, events, week_days): # Formulario de crear/editar # --------------------------- class FormCalendar(ft.Column): - def __init__(self, save_event, close_callback, event=None): + def __init__(self, save_event, close_callback, event=None, delete_event=None): super().__init__(expand=True, tight=True, spacing=10) self.save_event = save_event + self.delete_event = delete_event self.close_callback = close_callback self.event = event # Inicializa los campos según si es edición o creación @@ -189,7 +190,6 @@ def get_time_options(self): def build_form(self): self.controls.clear() - print("Build", self.NombreEvento) self.controls.append( ft.TextField(value=self.NombreEvento, label="Nombre del evento", on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) @@ -217,12 +217,17 @@ def build_form(self): ]) ) - self.controls.append( - ft.Row([ - ft.ElevatedButton("Guardar", icon=ft.Icons.SAVE, on_click=self.save_event), - ft.ElevatedButton("Cerrar", icon=ft.Icons.CLOSE, on_click=self.close_callback), - ]) + buttons = [ + ft.FilledButton("Guardar", on_click=self.save_event, bgcolor=ft.Colors.BLUE_400), + ft.FilledButton("Cancelar", on_click=self.close_callback, bgcolor=ft.Colors.GREY_400), + ] + + if self.event: + buttons.append( + ft.FilledButton("Eliminar", bgcolor=ft.Colors.GREY_400, on_click=lambda e: self.delete_event(self.event.id)) ) + + self.controls.append(ft.Row(buttons)) # --------------------------- @@ -251,8 +256,9 @@ def __init__(self): self.grid = CalendarGrid( self.week_days, self.event_manager.get_events_for_week(self.week_days), - open_edit_event=self.open_edit_event # 🔹 EDITAR + open_edit_event=self.open_edit_event ) + self.formCalendar = FormCalendar( save_event=lambda e: self.create_event(e), close_callback=lambda e=None: self.modal.close() @@ -333,7 +339,8 @@ def open_edit_event(self, event): self.formCalendar = FormCalendar( save_event=lambda e, ev=event: self.update_event(ev), close_callback=lambda e=None: self.modal.close(), - event=event + event=event, + delete_event=lambda e, ev_id=event.id: self.delete_event(ev_id) ) self.modal.update_content(self.formCalendar) self.modal.open() @@ -347,6 +354,11 @@ def update_event(self, event): self.modal.close() self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) + def delete_event(self, id): + self.event_manager.remove_event(id) + self.modal.close() + self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) + def _update_week(self): self.week_days = [self.start_week + datetime.timedelta(days=i) for i in range(7)] self.header.update_week(self.week_days) From 89a6607570049b390ea9a6df4748906673623da6 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 9 Sep 2025 11:59:26 -0600 Subject: [PATCH 18/47] Enhance CalendarGrid and FormCalendar to support new event creation with day and hour parameters --- src/components/shared/calendar.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 6621cd4..39e51f7 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -84,12 +84,13 @@ def update_week(self, week_days): # Grilla del calendario # --------------------------- class CalendarGrid(ft.Column): - def __init__(self, week_days, events, hour_height=60, open_edit_event=None): + def __init__(self, week_days, events, hour_height=60, open_edit_event=None, new_event=None): super().__init__(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0) self.week_days = week_days self.events = events self.hour_height = hour_height - self.open_edit_event = open_edit_event + self.open_edit_event = open_edit_event + self.new_event = new_event self.build_grid() def build_grid(self): @@ -123,6 +124,7 @@ def build_grid(self): right=0, height=self.hour_height, border=ft.border.all(0.2, ft.Colors.GREY_400), + on_click=lambda e, d=day, hr=h: self.new_event(day=d, hour=f"{hr:02d}:00") if self.new_event else None ) ) # Eventos del día @@ -141,7 +143,7 @@ def build_grid(self): bgcolor=ev.color, alignment=ft.alignment.center, content=ft.Text(ev.title, size=10, color="white"), - on_click=lambda e, ev=ev: self.open_edit_event(ev) if self.open_edit_event else None # 🔹 EDITAR + on_click=lambda e, ev=ev: self.open_edit_event(ev) if self.open_edit_event else None ) ) main_row.controls.append(ft.Container(content=day_stack, expand=1)) @@ -158,12 +160,16 @@ def render_events(self, events, week_days): # Formulario de crear/editar # --------------------------- class FormCalendar(ft.Column): - def __init__(self, save_event, close_callback, event=None, delete_event=None): + def __init__(self, save_event, close_callback, event=None, delete_event=None, day=None, hour=None): super().__init__(expand=True, tight=True, spacing=10) self.save_event = save_event self.delete_event = delete_event self.close_callback = close_callback self.event = event + self.day = day + self.hour = hour + print(f"FormCalendar initialized with day={day}, hour={hour}") + # Inicializa los campos según si es edición o creación if self.event: self.fechaInicio = str(self.event.date) @@ -172,10 +178,10 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None): self.horaFin = self.event.end_time self.NombreEvento = self.event.title else: - self.fechaInicio = str(datetime.date.today()) - self.horaInicio = "00:00" - self.fechaFin = str(datetime.date.today()) - self.horaFin = "00:00" + self.fechaInicio = self.day if self.day else str(datetime.date.today()) + self.horaInicio = self.hour if self.hour else datetime.datetime.now().strftime("%H:00") + self.fechaFin = self.day if self.day else str(datetime.date.today()) + self.horaFin = self.hour if self.hour else (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:00") self.NombreEvento = "Nuevo evento" self.build_form() @@ -256,7 +262,8 @@ def __init__(self): self.grid = CalendarGrid( self.week_days, self.event_manager.get_events_for_week(self.week_days), - open_edit_event=self.open_edit_event + open_edit_event=self.open_edit_event, + new_event=self.open_new_event ) self.formCalendar = FormCalendar( @@ -312,10 +319,12 @@ def back_week(self, e): self._update_week() #abrir modal vacío - def open_new_event(self): + def open_new_event(self, day=None, hour=None): self.formCalendar = FormCalendar( save_event=lambda e: self.create_event(e), - close_callback=lambda e=None: self.modal.close() + close_callback=lambda e=None: self.modal.close(), + day=day, + hour=hour ) self.modal.update_content(self.formCalendar) From abb274f567f9bff1b8b8a422a8b6f9d9b2b82b17 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 9 Sep 2025 15:36:46 -0600 Subject: [PATCH 19/47] Enhance CalendarGrid to format hour display and add hover effect; update event method signatures for clarity --- src/components/shared/calendar.py | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 39e51f7..4bbde6c 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -96,20 +96,26 @@ def __init__(self, week_days, events, hour_height=60, open_edit_event=None, new_ def build_grid(self): self.controls = [] + def format_hour(h: int) -> str: + t = datetime.time(h, 0) + # "%I" → hora 12h, "%p" → AM/PM + formatted = t.strftime("%I %p").lstrip("0") # "01 PM" → "1 PM" + return formatted.lower().replace("am", "a.m.").replace("pm", "p.m.") + # Columna de horas hour_column = ft.Column( controls=[ - ft.Container( - content=ft.Text(f"{h:02d}:00"), - height=self.hour_height, - alignment=ft.alignment.top_center - ) for h in range(24) + ft.Container( + content=ft.Text(format_hour(h)), + height=self.hour_height, + alignment=ft.alignment.top_center + ) for h in range(24) ], spacing=0 ) # Fila principal: primera columna horas + días main_row = ft.Row(expand=True, spacing=0) - main_row.controls.append(ft.Container(width=60, content=hour_column)) + main_row.controls.append(ft.Container(width=80, content=hour_column)) # Columnas de días for day in self.week_days: @@ -124,7 +130,8 @@ def build_grid(self): right=0, height=self.hour_height, border=ft.border.all(0.2, ft.Colors.GREY_400), - on_click=lambda e, d=day, hr=h: self.new_event(day=d, hour=f"{hr:02d}:00") if self.new_event else None + on_click=lambda e, d=day, hr=h: self.new_event(day=d, hour=f"{hr:02d}:00") if self.new_event else None, + on_hover= self.on_hover ) ) # Eventos del día @@ -155,7 +162,15 @@ def render_events(self, events, week_days): self.week_days = week_days self.build_grid() self.update() + + def on_hover(self, e: ft.HoverEvent): + if e.data == "true": + e.control.bgcolor = ft.Colors.LIGHT_BLUE_50 + else: + e.control.bgcolor = None + e.control.update() + # --------------------------- # Formulario de crear/editar # --------------------------- @@ -344,7 +359,7 @@ def create_event(self, e): self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) # abrir modal en modo edición - def open_edit_event(self, event): + def open_edit_event(self, event: calendar_event.Event): self.formCalendar = FormCalendar( save_event=lambda e, ev=event: self.update_event(ev), close_callback=lambda e=None: self.modal.close(), @@ -355,7 +370,7 @@ def open_edit_event(self, event): self.modal.open() # actualizar evento existente - def update_event(self, event): + def update_event(self, event: calendar_event.Event): event.title = self.formCalendar.NombreEvento event.date = datetime.datetime.strptime(self.formCalendar.fechaInicio, "%Y-%m-%d").date() event.start_time = self.formCalendar.horaInicio From 8a055b8895a088679588d7ead8a7636354021efc Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 9 Sep 2025 15:57:48 -0600 Subject: [PATCH 20/47] Fix date and time handling in FormCalendar for new events; ensure proper formatting and default values --- src/components/shared/calendar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 4bbde6c..4580287 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -193,10 +193,10 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.horaFin = self.event.end_time self.NombreEvento = self.event.title else: - self.fechaInicio = self.day if self.day else str(datetime.date.today()) + self.fechaInicio = str(self.day) if self.day else str(datetime.date.today()) self.horaInicio = self.hour if self.hour else datetime.datetime.now().strftime("%H:00") - self.fechaFin = self.day if self.day else str(datetime.date.today()) - self.horaFin = self.hour if self.hour else (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:00") + self.fechaFin = str(self.day) if self.day else str(datetime.date.today()) + self.horaFin = (datetime.datetime.strptime(self.horaInicio, "%H:%M") + datetime.timedelta(hours=1)).strftime("%H:%M") self.NombreEvento = "Nuevo evento" self.build_form() From bfef7571fcb9430f93d0eb6d4a2cf4ee4e581218 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 15 Sep 2025 22:22:43 -0600 Subject: [PATCH 21/47] Remove debug print statement from FormCalendar initialization --- src/components/shared/calendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 4580287..692ff27 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -183,7 +183,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.event = event self.day = day self.hour = hour - print(f"FormCalendar initialized with day={day}, hour={hour}") + # Inicializa los campos según si es edición o creación if self.event: From 03be696e53094850efd47a0ec6d1aacaa69fafaa Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 16 Sep 2025 12:57:00 -0600 Subject: [PATCH 22/47] Fix event date retrieval in EventManager to use start_time for week and day queries --- src/data/manager/calendar_event_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/manager/calendar_event_manager.py b/src/data/manager/calendar_event_manager.py index b589e09..8737dd6 100644 --- a/src/data/manager/calendar_event_manager.py +++ b/src/data/manager/calendar_event_manager.py @@ -12,7 +12,7 @@ def remove_event(self, event_id: str): self.events = [ev for ev in self.events if ev.id != event_id] def get_events_for_week(self, week_days): - return [ev for ev in self.events if ev.date in week_days] + return [ev for ev in self.events if ev.start_time.date() in week_days] def get_events_for_day(self, day): - return [ev for ev in self.events if ev.date == day] + return [ev for ev in self.events if ev.start_time.date() == day] From f4322b05d8e6f616ad5391fdc21349e1b13c52d4 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 16 Sep 2025 12:57:14 -0600 Subject: [PATCH 23/47] Refactor Event class constructor to remove unused date parameter and ensure type annotations for clarity --- src/data/models/calendar_event.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/data/models/calendar_event.py b/src/data/models/calendar_event.py index 93f9fd1..5b030f4 100644 --- a/src/data/models/calendar_event.py +++ b/src/data/models/calendar_event.py @@ -1,8 +1,19 @@ +import datetime class Event: - def __init__(self, id, title, date, start_time, end_time, color="blue"): + def __init__(self, id: str, title: str, start_time: datetime.datetime, end_time: datetime.datetime, color: str): self.id = id self.title = title - self.date = date self.start_time = start_time self.end_time = end_time - self.color = color \ No newline at end of file + self.color = color + + @classmethod + def from_json(cls, data: dict): + """Crea un Event a partir de un diccionario JSON""" + return cls( + id=str(data["id"]), + title=data["title"], + start_time=datetime.datetime.fromisoformat(data["start_time"]), + end_time=datetime.datetime.fromisoformat(data["end_time"]), + color=data.get("color", "green") + ) \ No newline at end of file From e620f917d6794bbf55532ce6b74a85cd7c1c7d61 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 16 Sep 2025 12:57:26 -0600 Subject: [PATCH 24/47] Fix event date handling in CalendarGrid and FormCalendar; update to use start_time and end_time with proper formatting --- src/components/shared/calendar.py | 50 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 692ff27..49e8205 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -1,5 +1,6 @@ import flet as ft import datetime +from datetime import timezone import uuid from data.models import calendar_event from data.manager.calendar_event_manager import EventManager @@ -135,10 +136,10 @@ def format_hour(h: int) -> str: ) ) # Eventos del día - day_events = [ev for ev in self.events if ev.date == day] + day_events = [ev for ev in self.events if ev.start_time.date() == day] for ev in day_events: - start_hour = int(ev.start_time.split(":")[0]) - end_hour = int(ev.end_time.split(":")[0]) + start_hour = int(ev.start_time.strftime("%H")) + end_hour = int(ev.end_time.strftime("%H")) duration = max(1, end_hour - start_hour) day_stack.controls.append( @@ -187,10 +188,10 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da # Inicializa los campos según si es edición o creación if self.event: - self.fechaInicio = str(self.event.date) - self.horaInicio = self.event.start_time - self.fechaFin = str(self.event.date) - self.horaFin = self.event.end_time + self.fechaInicio = str(self.event.start_time.date()) + self.horaInicio = self.event.start_time.time().strftime("%H:%M") + self.fechaFin = str(self.event.end_time.date()) + self.horaFin = self.event.end_time.time().strftime("%H:%M") self.NombreEvento = self.event.title else: self.fechaInicio = str(self.day) if self.day else str(datetime.date.today()) @@ -255,10 +256,11 @@ def build_form(self): # Vista principal del calendario # --------------------------- class Calendar(ft.Container): - def __init__(self): + def __init__(self, data = None): super().__init__(expand=True) # Estado inicial + self.data = data self.today = datetime.date.today() self.start_week = self.today - datetime.timedelta(days=self.today.weekday()) self.week_days = [self.start_week + datetime.timedelta(days=i) for i in range(7)] @@ -308,15 +310,8 @@ def __init__(self): # Se cargan eventos de prueba def _load_initial_events(self): self.event_manager = EventManager() - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Reunión equipo", self.today, "09:00", "11:00", "green")) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Clase inglés", self.today, "14:00", "15:00", "blue")) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Gym", self.today, "18:00", "19:00", "purple")) - day_1 = self.today + datetime.timedelta(days=1) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Proyecto X", day_1, "10:00", "12:15", "orange")) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Cita médica", day_1, "16:00", "17:00", "red")) - day_2 = datetime.date(self.today.year, 9, 8) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Reunión con clientes", day_2, "11:00", "12:30", "blue")) - self.event_manager.add_event(calendar_event.Event(str(uuid.uuid4()), "Clase de yoga", day_2, "18:00", "19:00", "purple")) + for ev_data in self.data: + self.event_manager.add_event(calendar_event.Event.from_json(ev_data)) def update_month_label(self, week_days): @@ -349,9 +344,8 @@ def create_event(self, e): new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, - date=datetime.datetime.strptime(self.formCalendar.fechaInicio, "%Y-%m-%d").date(), - start_time=self.formCalendar.horaInicio, - end_time=self.formCalendar.horaFin, + start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio), + end_time=self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin), color=ft.Colors.GREEN_800, ) self.event_manager.add_event(new_event) @@ -372,9 +366,8 @@ def open_edit_event(self, event: calendar_event.Event): # actualizar evento existente def update_event(self, event: calendar_event.Event): event.title = self.formCalendar.NombreEvento - event.date = datetime.datetime.strptime(self.formCalendar.fechaInicio, "%Y-%m-%d").date() - event.start_time = self.formCalendar.horaInicio - event.end_time = self.formCalendar.horaFin + event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio) + event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin) self.modal.close() self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) @@ -388,3 +381,14 @@ def _update_week(self): self.header.update_week(self.week_days) self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) self.update_month_label(self.week_days) + + def to_utc_datetime(self, fecha: str, hora: str) -> datetime: + """ + Convierte fecha + hora a datetime con tzinfo=UTC. + Acepta hora en formato HH:MM o HH:MM:SS + """ + try: + dt = datetime.datetime.strptime(f"{fecha} {hora}", "%Y-%m-%d %H:%M:%S") + except ValueError: + dt = datetime.datetime.strptime(f"{fecha} {hora}", "%Y-%m-%d %H:%M") + return dt.replace(tzinfo=timezone.utc) From 3853deea2cd8f7e37b3be7d94aa14ca69d33c245 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 16 Sep 2025 12:57:33 -0600 Subject: [PATCH 25/47] Integrate Supabase client to fetch event data for Calendar; update Calendar component to display events --- src/views/calendar_view.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/views/calendar_view.py b/src/views/calendar_view.py index 723f688..899e3e1 100644 --- a/src/views/calendar_view.py +++ b/src/views/calendar_view.py @@ -1,9 +1,22 @@ import flet as ft from components.shared.calendar import Calendar +import os +from dotenv import load_dotenv +load_dotenv() +from supabase import create_client, Client +url: str = os.environ.get("SUPABASE_URL") +key: str = os.environ.get("SUPABASE_KEY") +supabase: Client = create_client(url, key) def build_view_calendar(page): + + response = ( + supabase.table("Events") + .select("*") + .execute() +) return ft.Container( - content=Calendar(), + content=Calendar(response.data), expand=True, ) From c9c3ef91fa0db5f3722daa920acaae02d440dfe3 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 16 Sep 2025 14:06:15 -0600 Subject: [PATCH 26/47] Enhance Calendar component to support event API integration; add methods for adding, updating, and removing events --- src/components/shared/calendar.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 49e8205..3bb618b 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -256,11 +256,14 @@ def build_form(self): # Vista principal del calendario # --------------------------- class Calendar(ft.Container): - def __init__(self, data = None): + def __init__(self, data = None, add_event=None, remove_event=None, update_event=None): super().__init__(expand=True) # Estado inicial - self.data = data + self.api_add_event = add_event + self.api_update_event = update_event + self.api_remove_event = remove_event + self.data = data if data else [] self.today = datetime.date.today() self.start_week = self.today - datetime.timedelta(days=self.today.weekday()) self.week_days = [self.start_week + datetime.timedelta(days=i) for i in range(7)] @@ -348,6 +351,9 @@ def create_event(self, e): end_time=self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin), color=ft.Colors.GREEN_800, ) + response = self.api_add_event(new_event) if self.api_add_event else None + if response: + new_event.id = response.data[0]["id"] self.event_manager.add_event(new_event) self.modal.close() self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) @@ -369,11 +375,13 @@ def update_event(self, event: calendar_event.Event): event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio) event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin) self.modal.close() + self.api_update_event(event) self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) def delete_event(self, id): self.event_manager.remove_event(id) self.modal.close() + self.api_remove_event(id) self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) def _update_week(self): From c72ccbf3f4ef583a0fffc742aba275a5eb37a71a Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Tue, 16 Sep 2025 14:06:23 -0600 Subject: [PATCH 27/47] Add event management functions to Calendar component; implement add, remove, and update event capabilities --- src/views/calendar_view.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/views/calendar_view.py b/src/views/calendar_view.py index 899e3e1..ecf290d 100644 --- a/src/views/calendar_view.py +++ b/src/views/calendar_view.py @@ -10,13 +10,28 @@ def build_view_calendar(page): - response = ( - supabase.table("Events") - .select("*") - .execute() -) + response = (supabase.table("Events").select("*").execute()) + + def event_to_dict(event): + return { + "title": event.title, + "start_time": event.start_time.isoformat(), + "end_time": event.end_time.isoformat(), + "color": event.color + } + + def add_event(event): + response = supabase.table("Events").insert(event_to_dict(event)).execute() + return response + + def remove_event(event_id): + supabase.table("Events").delete().eq("id", event_id).execute() + + def update_event(event): + response = supabase.table("Events").update(event_to_dict(event)).eq("id", event.id).execute() + return ft.Container( - content=Calendar(response.data), + content=Calendar(response.data, add_event=add_event, remove_event=remove_event, update_event=update_event), expand=True, ) From bb866973e2049219a7cb11df8c2cafc26f3e4725 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Wed, 17 Sep 2025 11:33:54 -0600 Subject: [PATCH 28/47] Add conditional loading for initial events and safeguard API calls in Calendar component --- src/components/shared/calendar.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 3bb618b..7733445 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -270,7 +270,9 @@ def __init__(self, data = None, add_event=None, remove_event=None, update_event= self.event_manager = EventManager() # Eventos iniciales - self._load_initial_events() + if self.data: + print("Cargando eventos iniciales...") + self._load_initial_events() # Label del mes self.month_label = ft.Text("", size=16, weight=ft.FontWeight.W_700) @@ -375,13 +377,13 @@ def update_event(self, event: calendar_event.Event): event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio) event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin) self.modal.close() - self.api_update_event(event) + self.api_update_event(event) if self.api_update_event else None self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) def delete_event(self, id): self.event_manager.remove_event(id) self.modal.close() - self.api_remove_event(id) + self.api_remove_event(id) if self.api_remove_event else None self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) def _update_week(self): From 583303aba372a0be08116740f7883bf6716ddcab Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Thu, 18 Sep 2025 14:45:26 -0600 Subject: [PATCH 29/47] Refactor FormCalendar to improve event creation and validation; enhance date and time handling, and streamline UI controls --- src/components/shared/calendar.py | 198 +++++++++++++++++++++++------- 1 file changed, 152 insertions(+), 46 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 7733445..483c1d6 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -5,7 +5,7 @@ from data.models import calendar_event from data.manager.calendar_event_manager import EventManager from components.shared.draggableModal import DraggableModal - +import re # --------------------------- # Cabecera del calendario @@ -185,72 +185,177 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.day = day self.hour = hour - - # Inicializa los campos según si es edición o creación + # valores iniciales if self.event: - self.fechaInicio = str(self.event.start_time.date()) - self.horaInicio = self.event.start_time.time().strftime("%H:%M") - self.fechaFin = str(self.event.end_time.date()) - self.horaFin = self.event.end_time.time().strftime("%H:%M") - self.NombreEvento = self.event.title + self.fechaInicio = self.event.start_time.strftime("%d/%m/%Y") + self.horaInicio = self.event.start_time.strftime("%H:%M") + self.fechaFin = self.event.end_time.strftime("%d/%m/%Y") + self.horaFin = self.event.end_time.strftime("%H:%M") + self.NombreEvento = self.event.title or "Evento" else: - self.fechaInicio = str(self.day) if self.day else str(datetime.date.today()) - self.horaInicio = self.hour if self.hour else datetime.datetime.now().strftime("%H:00") - self.fechaFin = str(self.day) if self.day else str(datetime.date.today()) - self.horaFin = (datetime.datetime.strptime(self.horaInicio, "%H:%M") + datetime.timedelta(hours=1)).strftime("%H:%M") + hoy = datetime.date.today() + self.fechaInicio = (self.day.strftime("%d/%m/%Y") if isinstance(self.day, datetime.date) else hoy.strftime("%d/%m/%Y")) + self.horaInicio = (self.hour if self.hour else datetime.datetime.now().strftime("%H:00")) + self.fechaFin = self.fechaInicio + try: + hh = datetime.datetime.strptime(self.horaInicio, "%H:%M") + self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%H:%M") + except Exception: + self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") self.NombreEvento = "Nuevo evento" + # referencias a controles + self.tf_nombre = None + self.tf_fechaInicio = None + self.dd_horaInicio = None + self.tf_fechaFin = None + self.dd_horaFin = None + self.build_form() def get_time_options(self): options = [] for h in range(24): - value = f"{h:02d}:00" # valor real en 24h - label = datetime.time(h, 0).strftime("%I:%M %p").lstrip("0") # formato 12h + value = f"{h:02d}:00" + label = datetime.time(h, 0).strftime("%I:%M %p").lstrip("0") options.append(ft.dropdownm2.Option(text=label, key=value)) return options def build_form(self): - self.controls.clear() - self.controls.append( - ft.TextField(value=self.NombreEvento, label="Nombre del evento", - on_change=lambda e: setattr(self, 'NombreEvento', e.control.value)) + self.controls.clear() + + # Titulo + self.tf_nombre = ft.TextField( + label="Nombre del evento", + value=self.NombreEvento, + on_change=lambda e: setattr(self, 'NombreEvento', e.control.value) + ) + self.controls.append(self.tf_nombre) + + # Inicio + self.tf_fechaInicio = ft.TextField( + label="Fecha inicio (dd/mm/YYYY)", + hint_text="dd/mm/YYYY", + value=self.fechaInicio, + width=200, + on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + ) + self.dd_horaInicio = ft.Dropdown( + menu_height=250, + menu_width=150, + options=self.get_time_options(), + enable_filter=True, + editable=True, + value=self.horaInicio, + on_change=lambda e: setattr(self, 'horaInicio', e.control.value) ) self.controls.append( ft.Column([ ft.Text("Inicio"), - ft.Row([ - ft.TextField(value=self.fechaInicio, width=200, - on_change=lambda e: setattr(self, 'fechaInicio', e.control.value)), - ft.Dropdown(menu_height=250, menu_width=150, options=self.get_time_options(), enable_filter=True,editable=True, - value=self.horaInicio, on_change=lambda e: setattr(self, 'horaInicio', e.control.value)) - ]) + ft.Row([ self.tf_fechaInicio, self.dd_horaInicio ]) ]) ) + + # Fin + self.tf_fechaFin = ft.TextField( + label="Fecha fin (dd/mm/YYYY)", + hint_text="dd/mm/YYYY", + value=self.fechaFin, + width=200, + on_change=lambda e: setattr(self, 'fechaFin', e.control.value) + ) + self.dd_horaFin = ft.Dropdown( + menu_height=250, + menu_width=150, + options=self.get_time_options(), + enable_filter=True, + editable=True, + value=self.horaFin, + on_change=lambda e: setattr(self, 'horaFin', e.control.value) + ) self.controls.append( ft.Column([ ft.Text("Fin"), - ft.Row([ - ft.TextField(value=self.fechaFin, width=200, - on_change=lambda e: setattr(self, 'fechaFin', e.control.value)), - ft.Dropdown(menu_height=250, menu_width=150, options=self.get_time_options(), enable_filter=True,editable=True, - value=self.horaFin, on_change=lambda e: setattr(self, 'horaFin', e.control.value)) - ]) + ft.Row([ self.tf_fechaFin, self.dd_horaFin ]) ]) ) + # botones buttons = [ - ft.FilledButton("Guardar", on_click=self.save_event, bgcolor=ft.Colors.BLUE_400), + ft.FilledButton("Guardar", on_click=self.on_save_click, bgcolor=ft.Colors.BLUE_400), ft.FilledButton("Cancelar", on_click=self.close_callback, bgcolor=ft.Colors.GREY_400), - ] + ] - if self.event: - buttons.append( - ft.FilledButton("Eliminar", bgcolor=ft.Colors.GREY_400, on_click=lambda e: self.delete_event(self.event.id)) - ) + if self.event: + # cuidado: empacamos id en default arg para evitar captura late-binding + buttons.append(ft.FilledButton("Eliminar", bgcolor=ft.Colors.GREY_400, + on_click=lambda e, id=self.event.id: self.delete_event(id))) self.controls.append(ft.Row(buttons)) - + + def verifyForm(self): + # Limpia espacios + self.fechaInicio = (self.fechaInicio or "").strip() + self.fechaFin = (self.fechaFin or "").strip() + self.horaInicio = (self.horaInicio or "").strip() + self.horaFin = (self.horaFin or "").strip() + self.NombreEvento = (self.NombreEvento or "").strip() + + # Validadores: dd/mm/YYYY y HH:MM (24h) + date_pattern = r"^\d{2}/\d{2}/\d{4}$" + time_pattern = r"^(?:[01]?\d|2[0-3]):[0-5]\d$" + + # Fecha inicio + if not re.match(date_pattern, self.fechaInicio): + self.fechaInicio = datetime.date.today().strftime("%d/%m/%Y") + + # Fecha fin + if not re.match(date_pattern, self.fechaFin): + # si fecha fin inválida, por defecto la misma fecha inicio + self.fechaFin = self.fechaInicio + + # Hora inicio + if not re.match(time_pattern, self.horaInicio): + self.horaInicio = datetime.datetime.now().strftime("%H:00") + + # Hora fin + if not re.match(time_pattern, self.horaFin): + try: + inicio = datetime.datetime.strptime(self.horaInicio, "%H:%M") + self.horaFin = (inicio + datetime.timedelta(hours=1)).strftime("%H:%M") + except Exception: + self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") + + # Nombre + if not self.NombreEvento: + self.NombreEvento = "Nuevo evento" + + return True + + def on_save_click(self, e): + # 1) validar / corregir campos + istrue = self.verifyForm() + + # 2) actualizar controles + if self.tf_nombre: self.tf_nombre.value = self.NombreEvento + if self.tf_fechaInicio: self.tf_fechaInicio.value = self.fechaInicio + if self.dd_horaInicio: self.dd_horaInicio.value = self.horaInicio + if self.tf_fechaFin: self.tf_fechaFin.value = self.fechaFin + if self.dd_horaFin: self.dd_horaFin.value = self.horaFin + + try: + self.update() + except Exception as exc: + + print("Warning: update() falló en on_save_click:", exc) + + # 3) llamar a save_event (intento compatible con firmas que reciben 'e' o no) + if istrue: + try: + self.save_event(e) + except TypeError: + self.save_event() + # --------------------------- # Vista principal del calendario @@ -392,13 +497,14 @@ def _update_week(self): self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) self.update_month_label(self.week_days) - def to_utc_datetime(self, fecha: str, hora: str) -> datetime: - """ - Convierte fecha + hora a datetime con tzinfo=UTC. - Acepta hora en formato HH:MM o HH:MM:SS - """ - try: - dt = datetime.datetime.strptime(f"{fecha} {hora}", "%Y-%m-%d %H:%M:%S") - except ValueError: - dt = datetime.datetime.strptime(f"{fecha} {hora}", "%Y-%m-%d %H:%M") + def to_utc_datetime(self, fecha: str, hora: str) -> datetime.datetime: + # Intentar varios formatos comunes + dt_str = f"{fecha} {hora}" + for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%d/%m/%Y %H:%M"): + try: + dt = datetime.datetime.strptime(dt_str, fmt) + return dt.replace(tzinfo=timezone.utc) + except ValueError: + continue + dt = datetime.datetime.now() return dt.replace(tzinfo=timezone.utc) From 4bb673ed857dbfa2479ceb264115073d0acb1708 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Thu, 18 Sep 2025 16:05:19 -0600 Subject: [PATCH 30/47] Enhance FormCalendar UI; adjust container sizes, improve event input layout, and add labels for better user experience --- src/components/shared/calendar.py | 70 ++++++++++++++++--------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 483c1d6..2724c72 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -24,7 +24,7 @@ def build_row(self): return ft.Row( spacing=0, controls=[ - ft.Container(width=60, height=100, + ft.Container(width=80, height=100, border=ft.border.only( top=ft.BorderSide(0.2, ft.Colors.GREY_400), right=ft.BorderSide(0.2, ft.Colors.GREY_400), @@ -98,9 +98,9 @@ def build_grid(self): self.controls = [] def format_hour(h: int) -> str: + # Se le da formato 12 horas t = datetime.time(h, 0) - # "%I" → hora 12h, "%p" → AM/PM - formatted = t.strftime("%I %p").lstrip("0") # "01 PM" → "1 PM" + formatted = t.strftime("%I %p").lstrip("0") return formatted.lower().replace("am", "a.m.").replace("pm", "p.m.") # Columna de horas @@ -210,6 +210,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.dd_horaInicio = None self.tf_fechaFin = None self.dd_horaFin = None + self.text_label = None self.build_form() @@ -223,23 +224,33 @@ def get_time_options(self): def build_form(self): self.controls.clear() - + + + self.text_label = ft.Container( + content=ft.Text("Editar evento" if self.event else "Nuevo evento", size=18, weight=ft.FontWeight.W_400, text_align=ft.TextAlign.CENTER, color=ft.Colors.WHITE), + alignment=ft.alignment.center_left, + expand=True, + padding=ft.padding.only(top=10, bottom=10, left=10), + bgcolor=ft.Colors.GREY_400, + width=400, + border=ft.border.only(bottom=ft.BorderSide(0.5, ft.Colors.GREY_400)), + ) + # Titulo self.tf_nombre = ft.TextField( - label="Nombre del evento", - value=self.NombreEvento, + label="Agregar título", on_change=lambda e: setattr(self, 'NombreEvento', e.control.value) ) - self.controls.append(self.tf_nombre) - # Inicio + # Fecha self.tf_fechaInicio = ft.TextField( - label="Fecha inicio (dd/mm/YYYY)", + hint_text="dd/mm/YYYY", value=self.fechaInicio, width=200, on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) ) + # Hora inicio self.dd_horaInicio = ft.Dropdown( menu_height=250, menu_width=150, @@ -249,21 +260,8 @@ def build_form(self): value=self.horaInicio, on_change=lambda e: setattr(self, 'horaInicio', e.control.value) ) - self.controls.append( - ft.Column([ - ft.Text("Inicio"), - ft.Row([ self.tf_fechaInicio, self.dd_horaInicio ]) - ]) - ) - - # Fin - self.tf_fechaFin = ft.TextField( - label="Fecha fin (dd/mm/YYYY)", - hint_text="dd/mm/YYYY", - value=self.fechaFin, - width=200, - on_change=lambda e: setattr(self, 'fechaFin', e.control.value) - ) + + # Hora Fin self.dd_horaFin = ft.Dropdown( menu_height=250, menu_width=150, @@ -273,13 +271,6 @@ def build_form(self): value=self.horaFin, on_change=lambda e: setattr(self, 'horaFin', e.control.value) ) - self.controls.append( - ft.Column([ - ft.Text("Fin"), - ft.Row([ self.tf_fechaFin, self.dd_horaFin ]) - ]) - ) - # botones buttons = [ ft.FilledButton("Guardar", on_click=self.on_save_click, bgcolor=ft.Colors.BLUE_400), @@ -287,11 +278,24 @@ def build_form(self): ] if self.event: - # cuidado: empacamos id en default arg para evitar captura late-binding buttons.append(ft.FilledButton("Eliminar", bgcolor=ft.Colors.GREY_400, on_click=lambda e, id=self.event.id: self.delete_event(id))) - self.controls.append(ft.Row(buttons)) + + self.controls.extend( + [self.text_label, + ft.Container( padding=ft.padding.all(10), + content=ft.Column([ + self.tf_nombre, + ft.Text("Fecha de evento"), + self.tf_fechaInicio, + ft.Text("Incio y fin de evento"), + ft.Row([self.dd_horaInicio, ft.Container(content=ft.Icon(name=ft.Icons.ARROW_FORWARD)), self.dd_horaFin]), + ft.Row(buttons) + ]) + )] + ) + def verifyForm(self): # Limpia espacios From 490a130d3f842ba0a2aaa88c671b5ea8f73ede99 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Fri, 19 Sep 2025 12:01:29 -0600 Subject: [PATCH 31/47] Refactor FormCalendar header; replace text label with a structured header containing event title and close button for improved UI layout --- src/components/shared/calendar.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 2724c72..d29f266 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -210,7 +210,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.dd_horaInicio = None self.tf_fechaFin = None self.dd_horaFin = None - self.text_label = None + self.header = None self.build_form() @@ -226,12 +226,17 @@ def build_form(self): self.controls.clear() - self.text_label = ft.Container( - content=ft.Text("Editar evento" if self.event else "Nuevo evento", size=18, weight=ft.FontWeight.W_400, text_align=ft.TextAlign.CENTER, color=ft.Colors.WHITE), + self.header = ft.Container( + content=ft.Row(controls=[ + ft.Text("Editar evento" if self.event else "Nuevo evento", size=18, weight=ft.FontWeight.W_400, text_align=ft.TextAlign.CENTER, color=ft.Colors.WHITE), + ft.CupertinoButton(icon=ft.Icons.CLOSE, on_click=self.close_callback, icon_color=ft.Colors.WHITE) + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN + ), alignment=ft.alignment.center_left, expand=True, padding=ft.padding.only(top=10, bottom=10, left=10), - bgcolor=ft.Colors.GREY_400, + bgcolor=ft.Colors.BLUE_GREY_800, width=400, border=ft.border.only(bottom=ft.BorderSide(0.5, ft.Colors.GREY_400)), ) @@ -283,14 +288,15 @@ def build_form(self): self.controls.extend( - [self.text_label, - ft.Container( padding=ft.padding.all(10), + [self.header, + ft.Container( padding=ft.padding.all(20), content=ft.Column([ self.tf_nombre, ft.Text("Fecha de evento"), self.tf_fechaInicio, ft.Text("Incio y fin de evento"), ft.Row([self.dd_horaInicio, ft.Container(content=ft.Icon(name=ft.Icons.ARROW_FORWARD)), self.dd_horaFin]), + ft.Divider(height=1, color=ft.Colors.GREY_400), ft.Row(buttons) ]) )] From ae995d089a1f76978cbfbab52592072fd9501225 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Fri, 19 Sep 2025 18:53:34 -0600 Subject: [PATCH 32/47] Enhance FormCalendar; add fields for event description, location, and color, and improve layout with icons for better usability --- src/components/shared/calendar.py | 133 ++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 33 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index d29f266..77f9109 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -192,17 +192,24 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.fechaFin = self.event.end_time.strftime("%d/%m/%Y") self.horaFin = self.event.end_time.strftime("%H:%M") self.NombreEvento = self.event.title or "Evento" + self.descripcion = self.event.description or "" + self.ubicacion = self.event.location or "" + self.color = self.event.color or ft.Colors.BLUE_400 else: hoy = datetime.date.today() self.fechaInicio = (self.day.strftime("%d/%m/%Y") if isinstance(self.day, datetime.date) else hoy.strftime("%d/%m/%Y")) self.horaInicio = (self.hour if self.hour else datetime.datetime.now().strftime("%H:00")) self.fechaFin = self.fechaInicio + try: hh = datetime.datetime.strptime(self.horaInicio, "%H:%M") self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%H:%M") except Exception: self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") self.NombreEvento = "Nuevo evento" + self.descripcion = "" + self.ubicacion = "" + self.color = ft.Colors.BLUE_400 # referencias a controles self.tf_nombre = None @@ -235,47 +242,105 @@ def build_form(self): ), alignment=ft.alignment.center_left, expand=True, - padding=ft.padding.only(top=10, bottom=10, left=10), + padding=ft.padding.only(top=10, bottom=10, left=20, right=20), bgcolor=ft.Colors.BLUE_GREY_800, - width=400, + width=500, border=ft.border.only(bottom=ft.BorderSide(0.5, ft.Colors.GREY_400)), ) # Titulo - self.tf_nombre = ft.TextField( - label="Agregar título", - on_change=lambda e: setattr(self, 'NombreEvento', e.control.value) + self.tf_nombre = ft.Container( + content=ft.TextField( + border=ft.InputBorder.NONE, + hint_text="Agregar título", + on_change=lambda e: setattr(self, 'NombreEvento', e.control.value), + value=self.NombreEvento + ), + bgcolor=ft.Colors.BLUE_GREY_100, + border_radius=5, + padding=ft.padding.only(left=10, right=10), + expand=True, ) - # Fecha - self.tf_fechaInicio = ft.TextField( + # Descripción + self.tf_descripcion = ft.Container( + content=ft.TextField( + border=ft.InputBorder.NONE, + hint_text="Agregar descripción", + on_change=lambda e: setattr(self, 'descripcion', e.control.value), + max_lines=3, + min_lines=3, + multiline=True, + ), + bgcolor=ft.Colors.BLUE_GREY_100, + border_radius=5, + padding=ft.padding.only(left=10, right=10), + expand=True, + ) + + # Ubicación + self.tf_ubicacion = ft.Container( + content=ft.TextField( + border=ft.InputBorder.NONE, + hint_text="Agregar ubicación", + on_change=lambda e: setattr(self, 'ubicacion', e.control.value) + ), + bgcolor=ft.Colors.BLUE_GREY_100, + border_radius=5, + padding=ft.padding.only(left=10, right=10), + expand=True, + ) - hint_text="dd/mm/YYYY", - value=self.fechaInicio, - width=200, - on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + # Fecha + self.tf_fechaInicio = ft.Container( + content=ft.TextField( + border=ft.InputBorder.NONE, + label="Fecha inicio", + hint_text="dd/mm/YYYY", + value=self.fechaInicio, + width=200, + on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + ), + border_radius=5, + padding=ft.padding.only(left=10, right=10) ) # Hora inicio - self.dd_horaInicio = ft.Dropdown( - menu_height=250, - menu_width=150, - options=self.get_time_options(), - enable_filter=True, - editable=True, - value=self.horaInicio, - on_change=lambda e: setattr(self, 'horaInicio', e.control.value) + self.dd_horaInicio = ft.Container( + content=ft.Dropdown( + label="Hora inicio", + menu_height=250, + menu_width=150, + border=ft.InputBorder.NONE, + options=self.get_time_options(), + enable_filter=True, + editable=True, + value=self.horaInicio, + on_change=lambda e: setattr(self, 'horaInicio', e.control.value) + ), + bgcolor=ft.Colors.BLUE_GREY_100, + border_radius=5, + padding=ft.padding.only(left=10, right=10), ) # Hora Fin - self.dd_horaFin = ft.Dropdown( - menu_height=250, - menu_width=150, - options=self.get_time_options(), - enable_filter=True, - editable=True, - value=self.horaFin, - on_change=lambda e: setattr(self, 'horaFin', e.control.value) + self.dd_horaFin = ft.Container( + content=ft.Dropdown( + label="Hora fin", + menu_height=250, + menu_width=150, + border=ft.InputBorder.NONE, + options=self.get_time_options(), + enable_filter=True, + editable=True, + value=self.horaFin, + on_change=lambda e: setattr(self, 'horaFin', e.control.value) + ), + bgcolor=ft.Colors.BLUE_GREY_100, + border_radius=5, + padding=ft.padding.only(left=10, right=10), ) + + # botones buttons = [ ft.FilledButton("Guardar", on_click=self.on_save_click, bgcolor=ft.Colors.BLUE_400), @@ -291,14 +356,14 @@ def build_form(self): [self.header, ft.Container( padding=ft.padding.all(20), content=ft.Column([ - self.tf_nombre, - ft.Text("Fecha de evento"), - self.tf_fechaInicio, - ft.Text("Incio y fin de evento"), - ft.Row([self.dd_horaInicio, ft.Container(content=ft.Icon(name=ft.Icons.ARROW_FORWARD)), self.dd_horaFin]), + ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.CALENDAR_MONTH_OUTLINED, color=ft.Colors.BLACK)),self.tf_fechaInicio]), + ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.ACCESS_TIME, color=ft.Colors.BLACK)), ft.Row([self.dd_horaInicio, ft.Container(content=ft.Icon(name=ft.Icons.ARROW_FORWARD, color=ft.Colors.BLACK)), self.dd_horaFin],alignment=ft.MainAxisAlignment.SPACE_AROUND, expand=1)], width=460), + ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.EDIT_SHARP, color=ft.Colors.BLACK)),self.tf_nombre],expand=1, width=460), + ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.LOCATION_ON, color=ft.Colors.BLACK)),self.tf_ubicacion],expand=1, width=460), + ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.DESCRIPTION, color=ft.Colors.BLACK)),self.tf_descripcion],expand=1, width=460), ft.Divider(height=1, color=ft.Colors.GREY_400), ft.Row(buttons) - ]) + ],spacing=20) )] ) @@ -466,6 +531,8 @@ def create_event(self, e): title=self.formCalendar.NombreEvento, start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio), end_time=self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin), + description=self.formCalendar.descripcion, + location=self.formCalendar.ubicacion, color=ft.Colors.GREEN_800, ) response = self.api_add_event(new_event) if self.api_add_event else None From c54e7538de652f6f06c304f5caa073b6d035f883 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Fri, 19 Sep 2025 18:53:47 -0600 Subject: [PATCH 33/47] Remove padding from DraggableModal container for improved layout --- src/components/shared/draggableModal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/shared/draggableModal.py b/src/components/shared/draggableModal.py index ea81819..7f29394 100644 --- a/src/components/shared/draggableModal.py +++ b/src/components/shared/draggableModal.py @@ -25,7 +25,6 @@ def __init__(self, left=100, top=100, content=None): bgcolor=ft.Colors.WHITE, border_radius=10, shadow=ft.BoxShadow(blur_radius=10, color=ft.Colors.BLACK26), - padding=15, left=self.left, top=self.top, visible=self.visible, From 96aa058aa88ceda70b126d37f742319328210772 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Fri, 19 Sep 2025 18:53:54 -0600 Subject: [PATCH 34/47] Add description and location fields to Event class for enhanced event details --- src/data/models/calendar_event.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/data/models/calendar_event.py b/src/data/models/calendar_event.py index 5b030f4..881608d 100644 --- a/src/data/models/calendar_event.py +++ b/src/data/models/calendar_event.py @@ -1,11 +1,13 @@ import datetime class Event: - def __init__(self, id: str, title: str, start_time: datetime.datetime, end_time: datetime.datetime, color: str): + def __init__(self, id: str, title: str, start_time: datetime.datetime, end_time: datetime.datetime, color: str, description: str , location: str, ): self.id = id self.title = title self.start_time = start_time self.end_time = end_time self.color = color + self.description = description + self.location = location @classmethod def from_json(cls, data: dict): @@ -15,5 +17,7 @@ def from_json(cls, data: dict): title=data["title"], start_time=datetime.datetime.fromisoformat(data["start_time"]), end_time=datetime.datetime.fromisoformat(data["end_time"]), - color=data.get("color", "green") + color=data.get("color", "green"), + description=data.get("description", ""), + location=data.get("location", "") ) \ No newline at end of file From e453bb16ad2008e3b3873d0fc4ea7911646028a6 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Fri, 19 Sep 2025 18:59:10 -0600 Subject: [PATCH 35/47] Add description and location to event dictionary in calendar view for improved event details --- src/components/shared/calendar.py | 4 +++- src/views/calendar_view.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 77f9109..65ab1af 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -268,6 +268,7 @@ def build_form(self): border=ft.InputBorder.NONE, hint_text="Agregar descripción", on_change=lambda e: setattr(self, 'descripcion', e.control.value), + value=self.descripcion, max_lines=3, min_lines=3, multiline=True, @@ -283,7 +284,8 @@ def build_form(self): content=ft.TextField( border=ft.InputBorder.NONE, hint_text="Agregar ubicación", - on_change=lambda e: setattr(self, 'ubicacion', e.control.value) + on_change=lambda e: setattr(self, 'ubicacion', e.control.value), + value=self.ubicacion ), bgcolor=ft.Colors.BLUE_GREY_100, border_radius=5, diff --git a/src/views/calendar_view.py b/src/views/calendar_view.py index ecf290d..7140d46 100644 --- a/src/views/calendar_view.py +++ b/src/views/calendar_view.py @@ -17,6 +17,8 @@ def event_to_dict(event): "title": event.title, "start_time": event.start_time.isoformat(), "end_time": event.end_time.isoformat(), + "description": event.description, + "location": event.location, "color": event.color } From 20f694b6573133826dc24462a97670427968676f Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Fri, 19 Sep 2025 23:08:23 -0600 Subject: [PATCH 36/47] Add delete button to FormCalendar and improve form validation logic --- src/components/shared/calendar.py | 56 +++++++++++++------------------ 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 65ab1af..2665923 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -218,7 +218,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.tf_fechaFin = None self.dd_horaFin = None self.header = None - + self.delete_button = None self.build_form() def get_time_options(self): @@ -301,7 +301,7 @@ def build_form(self): hint_text="dd/mm/YYYY", value=self.fechaInicio, width=200, - on_change=lambda e: setattr(self, 'fechaInicio', e.control.value) + on_blur=self.verifyForm, ), border_radius=5, padding=ft.padding.only(left=10, right=10) @@ -344,33 +344,29 @@ def build_form(self): # botones - buttons = [ - ft.FilledButton("Guardar", on_click=self.on_save_click, bgcolor=ft.Colors.BLUE_400), - ft.FilledButton("Cancelar", on_click=self.close_callback, bgcolor=ft.Colors.GREY_400), - ] + self.saveButton = ft.FilledButton("Guardar", on_click=self.on_save_click, bgcolor=ft.Colors.BLUE_500, width=460, height=40) if self.event: - buttons.append(ft.FilledButton("Eliminar", bgcolor=ft.Colors.GREY_400, - on_click=lambda e, id=self.event.id: self.delete_event(id))) + self.delete_button = ft.CupertinoButton(icon=ft.Icons.DELETE, icon_color=ft.Colors.RED, on_click=lambda e, id=self.event.id: self.delete_event(id)) + else: + self.delete_button = ft.Container(height=40) - self.controls.extend( [self.header, - ft.Container( padding=ft.padding.all(20), + ft.Container( padding=ft.padding.all(20), content=ft.Column([ - ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.CALENDAR_MONTH_OUTLINED, color=ft.Colors.BLACK)),self.tf_fechaInicio]), - ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.ACCESS_TIME, color=ft.Colors.BLACK)), ft.Row([self.dd_horaInicio, ft.Container(content=ft.Icon(name=ft.Icons.ARROW_FORWARD, color=ft.Colors.BLACK)), self.dd_horaFin],alignment=ft.MainAxisAlignment.SPACE_AROUND, expand=1)], width=460), - ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.EDIT_SHARP, color=ft.Colors.BLACK)),self.tf_nombre],expand=1, width=460), - ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.LOCATION_ON, color=ft.Colors.BLACK)),self.tf_ubicacion],expand=1, width=460), - ft.Row([ft.Container(content=ft.Icon(name=ft.Icons.DESCRIPTION, color=ft.Colors.BLACK)),self.tf_descripcion],expand=1, width=460), - ft.Divider(height=1, color=ft.Colors.GREY_400), - ft.Row(buttons) + ft.Row([ft.Row([ft.Icon(name=ft.Icons.CALENDAR_MONTH_OUTLINED, color=ft.Colors.BLACK),self.tf_fechaInicio]), self.delete_button], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, width=460), + ft.Row([ft.Icon(name=ft.Icons.ACCESS_TIME, color=ft.Colors.BLACK), ft.Row([self.dd_horaInicio, ft.Icon(name=ft.Icons.ARROW_FORWARD, color=ft.Colors.BLACK), self.dd_horaFin],alignment=ft.MainAxisAlignment.SPACE_AROUND, expand=1)], width=460), + ft.Row([ft.Icon(name=ft.Icons.EDIT_SHARP, color=ft.Colors.BLACK),self.tf_nombre],expand=1, width=460), + ft.Row([ft.Icon(name=ft.Icons.LOCATION_ON, color=ft.Colors.BLACK),self.tf_ubicacion],expand=1, width=460), + ft.Row([ft.Icon(name=ft.Icons.DESCRIPTION, color=ft.Colors.BLACK),self.tf_descripcion],expand=1, width=460), + self.saveButton, ],spacing=20) )] ) - def verifyForm(self): + def verifyForm(self, e=None): # Limpia espacios self.fechaInicio = (self.fechaInicio or "").strip() self.fechaFin = (self.fechaFin or "").strip() @@ -383,17 +379,16 @@ def verifyForm(self): time_pattern = r"^(?:[01]?\d|2[0-3]):[0-5]\d$" # Fecha inicio - if not re.match(date_pattern, self.fechaInicio): - self.fechaInicio = datetime.date.today().strftime("%d/%m/%Y") - - # Fecha fin - if not re.match(date_pattern, self.fechaFin): - # si fecha fin inválida, por defecto la misma fecha inicio - self.fechaFin = self.fechaInicio + if not re.match(date_pattern, self.tf_fechaInicio.content.value): + self.fechaInicio = self.event.start_time.strftime("%d/%m/%Y") if self.event else self.fechaInicio + if self.tf_fechaInicio: self.tf_fechaInicio.content.value = self.fechaInicio + self.tf_fechaInicio.update() + return False # Hora inicio if not re.match(time_pattern, self.horaInicio): self.horaInicio = datetime.datetime.now().strftime("%H:00") + return False # Hora fin if not re.match(time_pattern, self.horaFin): @@ -402,7 +397,7 @@ def verifyForm(self): self.horaFin = (inicio + datetime.timedelta(hours=1)).strftime("%H:%M") except Exception: self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") - + return False # Nombre if not self.NombreEvento: self.NombreEvento = "Nuevo evento" @@ -412,13 +407,10 @@ def verifyForm(self): def on_save_click(self, e): # 1) validar / corregir campos istrue = self.verifyForm() - # 2) actualizar controles - if self.tf_nombre: self.tf_nombre.value = self.NombreEvento - if self.tf_fechaInicio: self.tf_fechaInicio.value = self.fechaInicio - if self.dd_horaInicio: self.dd_horaInicio.value = self.horaInicio - if self.tf_fechaFin: self.tf_fechaFin.value = self.fechaFin - if self.dd_horaFin: self.dd_horaFin.value = self.horaFin + if self.tf_nombre: self.tf_nombre.content.value = self.NombreEvento + if self.dd_horaInicio: self.dd_horaInicio.content.value = self.horaInicio + if self.dd_horaFin: self.dd_horaFin.content.value = self.horaFin try: self.update() From a0b288d12499584f058738f77dc9a2ec6adb1a51 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 13:09:21 -0600 Subject: [PATCH 37/47] Refactor FormCalendar to use 12-hour format for time inputs and improve validation logic --- src/components/shared/calendar.py | 77 ++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 2665923..6c056a8 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -188,7 +188,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da # valores iniciales if self.event: self.fechaInicio = self.event.start_time.strftime("%d/%m/%Y") - self.horaInicio = self.event.start_time.strftime("%H:%M") + self.horaInicio = self.event.start_time.strftime("%I:%M %p").lstrip("0") self.fechaFin = self.event.end_time.strftime("%d/%m/%Y") self.horaFin = self.event.end_time.strftime("%H:%M") self.NombreEvento = self.event.title or "Evento" @@ -198,7 +198,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da else: hoy = datetime.date.today() self.fechaInicio = (self.day.strftime("%d/%m/%Y") if isinstance(self.day, datetime.date) else hoy.strftime("%d/%m/%Y")) - self.horaInicio = (self.hour if self.hour else datetime.datetime.now().strftime("%H:00")) + self.horaInicio = (datetime.time(int(self.hour.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") if self.hour else datetime.datetime.now().strftime("%I:%M %p").lstrip("0")) self.fechaFin = self.fechaInicio try: @@ -307,17 +307,31 @@ def build_form(self): padding=ft.padding.only(left=10, right=10) ) # Hora inicio + + self.tf_horaInicio = ft.TextField( + hint_text="HH:MM", + value=self.horaInicio, + label="Hora inicio", + width=100, + border=ft.InputBorder.NONE, + on_blur=self.verifyForm, + ) + self.dd_horaInicio = ft.Container( - content=ft.Dropdown( - label="Hora inicio", - menu_height=250, - menu_width=150, - border=ft.InputBorder.NONE, - options=self.get_time_options(), - enable_filter=True, - editable=True, - value=self.horaInicio, - on_change=lambda e: setattr(self, 'horaInicio', e.control.value) + content=ft.Row( + [ + self.tf_horaInicio, + ft.Dropdown( + width=50, + menu_height=250, + menu_width=150, + border=ft.InputBorder.NONE, + options=self.get_time_options(), + on_change=lambda e: self.change_hour(e) + + ) + + ] ), bgcolor=ft.Colors.BLUE_GREY_100, border_radius=5, @@ -342,7 +356,7 @@ def build_form(self): padding=ft.padding.only(left=10, right=10), ) - + # botones self.saveButton = ft.FilledButton("Guardar", on_click=self.on_save_click, bgcolor=ft.Colors.BLUE_500, width=460, height=40) @@ -365,6 +379,12 @@ def build_form(self): )] ) + def change_hour(self, e): + self.horaInicio = e.control.value + self.tf_horaInicio.value = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") + + self.tf_horaInicio.update() + def verifyForm(self, e=None): # Limpia espacios @@ -376,7 +396,8 @@ def verifyForm(self, e=None): # Validadores: dd/mm/YYYY y HH:MM (24h) date_pattern = r"^\d{2}/\d{2}/\d{4}$" - time_pattern = r"^(?:[01]?\d|2[0-3]):[0-5]\d$" + #validar formato 12 horas con AM/PM + time_pattern = r"^(0?[1-9]|1[0-2]):[0-5]\d (AM|PM)$" # Fecha inicio if not re.match(date_pattern, self.tf_fechaInicio.content.value): @@ -384,20 +405,29 @@ def verifyForm(self, e=None): if self.tf_fechaInicio: self.tf_fechaInicio.content.value = self.fechaInicio self.tf_fechaInicio.update() return False - + else: + self.fechaInicio = self.tf_fechaInicio.content.value + # Hora inicio - if not re.match(time_pattern, self.horaInicio): - self.horaInicio = datetime.datetime.now().strftime("%H:00") + + if not re.match(time_pattern, self.tf_horaInicio.value): + self.horaInicio = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") + if self.tf_horaInicio: self.tf_horaInicio.value = self.horaInicio + self.tf_horaInicio.update() return False + else: + self.horaInicio = self.tf_horaInicio.value + + # Hora fin - if not re.match(time_pattern, self.horaFin): + """ if not re.match(time_pattern, self.horaFin): try: - inicio = datetime.datetime.strptime(self.horaInicio, "%H:%M") + inicio = datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H:%M") self.horaFin = (inicio + datetime.timedelta(hours=1)).strftime("%H:%M") except Exception: self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") - return False + return False """ # Nombre if not self.NombreEvento: self.NombreEvento = "Nuevo evento" @@ -520,15 +550,17 @@ def open_new_event(self, day=None, hour=None): self.modal.open() def create_event(self, e): + print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, - start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio), + start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M").strftime("%H:%M")), end_time=self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin), description=self.formCalendar.descripcion, location=self.formCalendar.ubicacion, color=ft.Colors.GREEN_800, ) + print(new_event.start_time, new_event.end_time, new_event.title) response = self.api_add_event(new_event) if self.api_add_event else None if response: new_event.id = response.data[0]["id"] @@ -549,8 +581,9 @@ def open_edit_event(self, event: calendar_event.Event): # actualizar evento existente def update_event(self, event: calendar_event.Event): + print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) event.title = self.formCalendar.NombreEvento - event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, self.formCalendar.horaInicio) + event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")) event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin) self.modal.close() self.api_update_event(event) if self.api_update_event else None From 350adbea1b004e4ab018251e7494c7f777a303ca Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 17:51:03 -0600 Subject: [PATCH 38/47] Fix time handling in FormCalendar to ensure correct parsing and formatting of start time --- src/components/shared/calendar.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 6c056a8..6c31a2f 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -327,8 +327,7 @@ def build_form(self): menu_width=150, border=ft.InputBorder.NONE, options=self.get_time_options(), - on_change=lambda e: self.change_hour(e) - + on_change=lambda e: self.change_hour(e), ) ] @@ -411,7 +410,7 @@ def verifyForm(self, e=None): # Hora inicio if not re.match(time_pattern, self.tf_horaInicio.value): - self.horaInicio = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") + self.horaInicio = datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%I:%M %p").lstrip("0") if self.tf_horaInicio: self.tf_horaInicio.value = self.horaInicio self.tf_horaInicio.update() return False @@ -554,7 +553,7 @@ def create_event(self, e): new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, - start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M").strftime("%H:%M")), + start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")), end_time=self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin), description=self.formCalendar.descripcion, location=self.formCalendar.ubicacion, From db330819f48c43822e498d3c76ecc8fcec2e13ca Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 18:30:01 -0600 Subject: [PATCH 39/47] Refactor FormCalendar to use 12-hour format for end time and improve hour change handling --- src/components/shared/calendar.py | 61 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 6c31a2f..3fc005a 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -190,7 +190,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.fechaInicio = self.event.start_time.strftime("%d/%m/%Y") self.horaInicio = self.event.start_time.strftime("%I:%M %p").lstrip("0") self.fechaFin = self.event.end_time.strftime("%d/%m/%Y") - self.horaFin = self.event.end_time.strftime("%H:%M") + self.horaFin = self.event.end_time.strftime("%I:%M %p").lstrip("0") self.NombreEvento = self.event.title or "Evento" self.descripcion = self.event.description or "" self.ubicacion = self.event.location or "" @@ -200,12 +200,11 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.fechaInicio = (self.day.strftime("%d/%m/%Y") if isinstance(self.day, datetime.date) else hoy.strftime("%d/%m/%Y")) self.horaInicio = (datetime.time(int(self.hour.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") if self.hour else datetime.datetime.now().strftime("%I:%M %p").lstrip("0")) self.fechaFin = self.fechaInicio - try: - hh = datetime.datetime.strptime(self.horaInicio, "%H:%M") - self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%H:%M") + hh = datetime.datetime.strptime(self.horaInicio, "%I:%M %p") + self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") except Exception: - self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") + self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") self.NombreEvento = "Nuevo evento" self.descripcion = "" self.ubicacion = "" @@ -327,7 +326,7 @@ def build_form(self): menu_width=150, border=ft.InputBorder.NONE, options=self.get_time_options(), - on_change=lambda e: self.change_hour(e), + on_change=lambda e: self.change_hour(e, is_horaInicio=True ), ) ] @@ -338,17 +337,29 @@ def build_form(self): ) # Hora Fin + self.tf_horaFin = ft.TextField( + hint_text="HH:MM", + value=self.horaFin, + label="Hora fin", + width=100, + border=ft.InputBorder.NONE, + on_blur=self.verifyForm, + ) + self.dd_horaFin = ft.Container( - content=ft.Dropdown( - label="Hora fin", - menu_height=250, - menu_width=150, - border=ft.InputBorder.NONE, - options=self.get_time_options(), - enable_filter=True, - editable=True, - value=self.horaFin, - on_change=lambda e: setattr(self, 'horaFin', e.control.value) + content=ft.Row( + [ + self.tf_horaFin, + ft.Dropdown( + width=50, + menu_height=250, + menu_width=150, + border=ft.InputBorder.NONE, + options=self.get_time_options(), + on_change=lambda e: self.change_hour(e, is_horaInicio=False), + ) + + ] ), bgcolor=ft.Colors.BLUE_GREY_100, border_radius=5, @@ -378,11 +389,15 @@ def build_form(self): )] ) - def change_hour(self, e): - self.horaInicio = e.control.value - self.tf_horaInicio.value = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") - - self.tf_horaInicio.update() + def change_hour(self, e, is_horaInicio=True): + if is_horaInicio: + self.horaInicio = e.control.value + self.tf_horaInicio.value = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") + self.tf_horaInicio.update() + else: + self.horaFin = e.control.value + self.tf_horaFin.value = datetime.time(int(self.horaFin.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") + self.tf_horaFin.update() def verifyForm(self, e=None): @@ -438,7 +453,6 @@ def on_save_click(self, e): istrue = self.verifyForm() # 2) actualizar controles if self.tf_nombre: self.tf_nombre.content.value = self.NombreEvento - if self.dd_horaInicio: self.dd_horaInicio.content.value = self.horaInicio if self.dd_horaFin: self.dd_horaFin.content.value = self.horaFin try: @@ -549,12 +563,11 @@ def open_new_event(self, day=None, hour=None): self.modal.open() def create_event(self, e): - print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")), - end_time=self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin), + end_time=self.to_utc_datetime(self.formCalendar.fechaFin, datetime.datetime.strptime(self.formCalendar.horaFin, "%I:%M %p").strftime("%H:%M")), description=self.formCalendar.descripcion, location=self.formCalendar.ubicacion, color=ft.Colors.GREEN_800, From d125b7d84c2b9e86aaedd653cafd018bdf349a8e Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 19:30:29 -0600 Subject: [PATCH 40/47] Fix time validation in FormCalendar to ensure correct end time parsing and formatting --- src/components/shared/calendar.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 3fc005a..4f34187 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -431,17 +431,18 @@ def verifyForm(self, e=None): return False else: self.horaInicio = self.tf_horaInicio.value + # Hora fin - """ if not re.match(time_pattern, self.horaFin): - try: - inicio = datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H:%M") - self.horaFin = (inicio + datetime.timedelta(hours=1)).strftime("%H:%M") - except Exception: - self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%H:%M") - return False """ + if not re.match(time_pattern, self.tf_horaFin.value): + self.horaFin = datetime.datetime.strptime(self.horaFin, "%I:%M %p").strftime("%I:%M %p").lstrip("0") + if self.tf_horaFin: self.tf_horaFin.value = self.horaFin + self.tf_horaFin.update() + return False + else: + self.horaFin = self.tf_horaFin.value # Nombre if not self.NombreEvento: self.NombreEvento = "Nuevo evento" @@ -563,6 +564,7 @@ def open_new_event(self, day=None, hour=None): self.modal.open() def create_event(self, e): + print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, @@ -596,7 +598,7 @@ def update_event(self, event: calendar_event.Event): print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) event.title = self.formCalendar.NombreEvento event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")) - event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, self.formCalendar.horaFin) + event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, datetime.datetime.strptime(self.formCalendar.horaFin, "%I:%M %p").strftime("%H:%M")) self.modal.close() self.api_update_event(event) if self.api_update_event else None self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) From 7806b1b6f9c5c3dfd7e4a3bf6d149a0b4aa4acb2 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 22:28:43 -0600 Subject: [PATCH 41/47] Enhance FormCalendar time options to dynamically adjust end time based on start time selection and improve validation logic for time inputs --- src/components/shared/calendar.py | 81 ++++++++++++++++--------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 4f34187..1d9841a 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -220,12 +220,21 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.delete_button = None self.build_form() - def get_time_options(self): + def get_time_options(self, is_fechaInicio=True): options = [] - for h in range(24): - value = f"{h:02d}:00" - label = datetime.time(h, 0).strftime("%I:%M %p").lstrip("0") - options.append(ft.dropdownm2.Option(text=label, key=value)) + if is_fechaInicio: + for h in range(24): + value = f"{h:02d}:00" + label = datetime.time(h, 0).strftime("%I:%M %p").lstrip("0") + options.append(ft.dropdownm2.Option(text=label, key=value)) + else: + #opciones de hora fin a partir de hora inicio + start_hour = int(self.horaInicio.split(":")[0]) if self.horaInicio else 0 + for h in range(start_hour , 24): + value = f"{h%24:02d}:00" + label = datetime.time(h%24, 0).strftime("%I:%M %p").lstrip("0") + options.append(ft.dropdownm2.Option(text=label, key=value)) + return options def build_form(self): @@ -346,21 +355,17 @@ def build_form(self): on_blur=self.verifyForm, ) - self.dd_horaFin = ft.Container( - content=ft.Row( - [ - self.tf_horaFin, - ft.Dropdown( - width=50, - menu_height=250, - menu_width=150, - border=ft.InputBorder.NONE, - options=self.get_time_options(), - on_change=lambda e: self.change_hour(e, is_horaInicio=False), - ) + self.dropdown_horaFin = ft.Dropdown( + width=50, + menu_height=250, + menu_width=150, + border=ft.InputBorder.NONE, + options=self.get_time_options(is_fechaInicio=False), + on_change=lambda e: self.change_hour(e, is_horaInicio=False), + ) - ] - ), + self.dd_horaFin = ft.Container( + content=ft.Row([self.tf_horaFin, self.dropdown_horaFin]), bgcolor=ft.Colors.BLUE_GREY_100, border_radius=5, padding=ft.padding.only(left=10, right=10), @@ -393,6 +398,9 @@ def change_hour(self, e, is_horaInicio=True): if is_horaInicio: self.horaInicio = e.control.value self.tf_horaInicio.value = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") + self.dropdown_horaFin.options = self.get_time_options(is_fechaInicio=False) + self.dropdown_horaFin.update() + self.verifyForm() self.tf_horaInicio.update() else: self.horaFin = e.control.value @@ -423,7 +431,6 @@ def verifyForm(self, e=None): self.fechaInicio = self.tf_fechaInicio.content.value # Hora inicio - if not re.match(time_pattern, self.tf_horaInicio.value): self.horaInicio = datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%I:%M %p").lstrip("0") if self.tf_horaInicio: self.tf_horaInicio.value = self.horaInicio @@ -431,11 +438,9 @@ def verifyForm(self, e=None): return False else: self.horaInicio = self.tf_horaInicio.value - - - - + # Hora fin + #validar formato if not re.match(time_pattern, self.tf_horaFin.value): self.horaFin = datetime.datetime.strptime(self.horaFin, "%I:%M %p").strftime("%I:%M %p").lstrip("0") if self.tf_horaFin: self.tf_horaFin.value = self.horaFin @@ -443,7 +448,17 @@ def verifyForm(self, e=None): return False else: self.horaFin = self.tf_horaFin.value - # Nombre + + #hora fin no puede ser menor que hora inicio convierte a formato 24 horas para comparar + horaFin_24 = datetime.datetime.strptime(self.tf_horaFin.value, "%I:%M %p") + horaInicio_24 = datetime.datetime.strptime(self.tf_horaInicio.value, "%I:%M %p") + + if horaFin_24 < horaInicio_24: + self.horaFin = (datetime.datetime.strptime(self.horaInicio, "%I:%M %p") + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") + if self.tf_horaFin: self.tf_horaFin.value = self.horaFin + self.tf_horaFin.update() + + if not self.NombreEvento: self.NombreEvento = "Nuevo evento" @@ -452,17 +467,7 @@ def verifyForm(self, e=None): def on_save_click(self, e): # 1) validar / corregir campos istrue = self.verifyForm() - # 2) actualizar controles - if self.tf_nombre: self.tf_nombre.content.value = self.NombreEvento - if self.dd_horaFin: self.dd_horaFin.content.value = self.horaFin - - try: - self.update() - except Exception as exc: - - print("Warning: update() falló en on_save_click:", exc) - - # 3) llamar a save_event (intento compatible con firmas que reciben 'e' o no) + if istrue: try: self.save_event(e) @@ -489,7 +494,6 @@ def __init__(self, data = None, add_event=None, remove_event=None, update_event= # Eventos iniciales if self.data: - print("Cargando eventos iniciales...") self._load_initial_events() # Label del mes @@ -564,7 +568,6 @@ def open_new_event(self, day=None, hour=None): self.modal.open() def create_event(self, e): - print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, @@ -574,7 +577,6 @@ def create_event(self, e): location=self.formCalendar.ubicacion, color=ft.Colors.GREEN_800, ) - print(new_event.start_time, new_event.end_time, new_event.title) response = self.api_add_event(new_event) if self.api_add_event else None if response: new_event.id = response.data[0]["id"] @@ -595,7 +597,6 @@ def open_edit_event(self, event: calendar_event.Event): # actualizar evento existente def update_event(self, event: calendar_event.Event): - print(self.formCalendar.horaInicio, self.formCalendar.horaFin, self.formCalendar.fechaInicio) event.title = self.formCalendar.NombreEvento event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")) event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, datetime.datetime.strptime(self.formCalendar.horaFin, "%I:%M %p").strftime("%H:%M")) From 4b4000db1db5b4b8d2e04d6febc10ef54ae61125 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 22:42:51 -0600 Subject: [PATCH 42/47] Fix end time calculation in FormCalendar to ensure it caps at 11:59 PM and improve validation logic for time inputs --- src/components/shared/calendar.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 1d9841a..fe13ae4 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -202,7 +202,10 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.fechaFin = self.fechaInicio try: hh = datetime.datetime.strptime(self.horaInicio, "%I:%M %p") - self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") + if hh.hour >= 23: + self.horaFin = "11:59 PM" + else: + self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") except Exception: self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") self.NombreEvento = "Nuevo evento" @@ -314,8 +317,8 @@ def build_form(self): border_radius=5, padding=ft.padding.only(left=10, right=10) ) + # Hora inicio - self.tf_horaInicio = ft.TextField( hint_text="HH:MM", value=self.horaInicio, @@ -454,11 +457,15 @@ def verifyForm(self, e=None): horaInicio_24 = datetime.datetime.strptime(self.tf_horaInicio.value, "%I:%M %p") if horaFin_24 < horaInicio_24: - self.horaFin = (datetime.datetime.strptime(self.horaInicio, "%I:%M %p") + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") + #si hora inicio es mayor a 11:00PM pone hora fin a 11:59PM + if horaInicio_24.hour >= 23: + self.horaFin = "11:59 PM" + else: + self.horaFin = (datetime.datetime.strptime(self.horaInicio, "%I:%M %p") + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") if self.tf_horaFin: self.tf_horaFin.value = self.horaFin self.tf_horaFin.update() - - + return False + if not self.NombreEvento: self.NombreEvento = "Nuevo evento" From 13f23bb60e9a4b847e61e652480a879243a59464 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 22:55:29 -0600 Subject: [PATCH 43/47] Convert start hour to 24-hour format in FormCalendar for accurate end time options --- src/components/shared/calendar.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index fe13ae4..8b8b318 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -232,7 +232,10 @@ def get_time_options(self, is_fechaInicio=True): options.append(ft.dropdownm2.Option(text=label, key=value)) else: #opciones de hora fin a partir de hora inicio - start_hour = int(self.horaInicio.split(":")[0]) if self.horaInicio else 0 + #convertir hora inicio a formato 24 horas + + start_hour = int(datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H")) if self.horaInicio else 0 + print(start_hour) for h in range(start_hour , 24): value = f"{h%24:02d}:00" label = datetime.time(h%24, 0).strftime("%I:%M %p").lstrip("0") @@ -243,7 +246,7 @@ def get_time_options(self, is_fechaInicio=True): def build_form(self): self.controls.clear() - + # Header self.header = ft.Container( content=ft.Row(controls=[ ft.Text("Editar evento" if self.event else "Nuevo evento", size=18, weight=ft.FontWeight.W_400, text_align=ft.TextAlign.CENTER, color=ft.Colors.WHITE), From 166a443b4d6068ef6dbae0379de95e5498aa0151 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Sat, 20 Sep 2025 23:31:32 -0600 Subject: [PATCH 44/47] Refactor time calculations in CalendarGrid and FormCalendar to use minutes for improved accuracy and handle edge cases in time parsing --- src/components/shared/calendar.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 8b8b318..64effee 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -138,16 +138,19 @@ def format_hour(h: int) -> str: # Eventos del día day_events = [ev for ev in self.events if ev.start_time.date() == day] for ev in day_events: - start_hour = int(ev.start_time.strftime("%H")) - end_hour = int(ev.end_time.strftime("%H")) - duration = max(1, end_hour - start_hour) + start_minutes = ev.start_time.hour * 60 + ev.start_time.minute + end_minutes = ev.end_time.hour * 60 + ev.end_time.minute + duration = max(1, end_minutes - start_minutes) + #altura minima de 25 para eventos muy cortos + if duration < 25: + duration = 25 day_stack.controls.append( ft.Container( - top=self.hour_height * start_hour, + top=self.hour_height * start_minutes / 60, left=0, right=5, - height=self.hour_height * duration, + height=self.hour_height * duration / 60, bgcolor=ev.color, alignment=ft.alignment.center, content=ft.Text(ev.title, size=10, color="white"), @@ -233,9 +236,13 @@ def get_time_options(self, is_fechaInicio=True): else: #opciones de hora fin a partir de hora inicio #convertir hora inicio a formato 24 horas - - start_hour = int(datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H")) if self.horaInicio else 0 - print(start_hour) + if self.horaInicio: + try: + start_hour = int(datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H")) + except ValueError: + start_hour = int(datetime.datetime.strptime(self.horaInicio, "%H:%M").strftime("%H")) + else: + start_hour = 0 for h in range(start_hour , 24): value = f"{h%24:02d}:00" label = datetime.time(h%24, 0).strftime("%I:%M %p").lstrip("0") From fc84d00f02e6d9451554ab646fb7ebe523ae3598 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Thu, 25 Sep 2025 15:56:41 -0600 Subject: [PATCH 45/47] Refactor FormCalendar to improve time handling and validation; update event creation and editing to use consistent datetime parsing --- src/components/shared/calendar.py | 56 ++++++++++++++----------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index 64effee..bc53d67 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -153,7 +153,7 @@ def format_hour(h: int) -> str: height=self.hour_height * duration / 60, bgcolor=ev.color, alignment=ft.alignment.center, - content=ft.Text(ev.title, size=10, color="white"), + content=ft.Text(ev.title, size=14, color="white"), on_click=lambda e, ev=ev: self.open_edit_event(ev) if self.open_edit_event else None ) ) @@ -211,7 +211,7 @@ def __init__(self, save_event, close_callback, event=None, delete_event=None, da self.horaFin = (hh + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") except Exception: self.horaFin = (datetime.datetime.now() + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") - self.NombreEvento = "Nuevo evento" + self.NombreEvento = "" self.descripcion = "" self.ubicacion = "" self.color = ft.Colors.BLUE_400 @@ -231,7 +231,7 @@ def get_time_options(self, is_fechaInicio=True): if is_fechaInicio: for h in range(24): value = f"{h:02d}:00" - label = datetime.time(h, 0).strftime("%I:%M %p").lstrip("0") + label = datetime.time(h, 0).strftime("%I:%M %p") options.append(ft.dropdownm2.Option(text=label, key=value)) else: #opciones de hora fin a partir de hora inicio @@ -245,7 +245,7 @@ def get_time_options(self, is_fechaInicio=True): start_hour = 0 for h in range(start_hour , 24): value = f"{h%24:02d}:00" - label = datetime.time(h%24, 0).strftime("%I:%M %p").lstrip("0") + label = datetime.time(h%24, 0).strftime("%I:%M %p") options.append(ft.dropdownm2.Option(text=label, key=value)) return options @@ -349,6 +349,7 @@ def build_form(self): border=ft.InputBorder.NONE, options=self.get_time_options(), on_change=lambda e: self.change_hour(e, is_horaInicio=True ), + value=datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H:%M"), ) ] @@ -357,7 +358,6 @@ def build_form(self): border_radius=5, padding=ft.padding.only(left=10, right=10), ) - # Hora Fin self.tf_horaFin = ft.TextField( hint_text="HH:MM", @@ -375,6 +375,7 @@ def build_form(self): border=ft.InputBorder.NONE, options=self.get_time_options(is_fechaInicio=False), on_change=lambda e: self.change_hour(e, is_horaInicio=False), + value=datetime.datetime.strptime(self.horaFin, "%I:%M %p").strftime("%H:%M") ) self.dd_horaFin = ft.Container( @@ -411,18 +412,16 @@ def change_hour(self, e, is_horaInicio=True): if is_horaInicio: self.horaInicio = e.control.value self.tf_horaInicio.value = datetime.time(int(self.horaInicio.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") - self.dropdown_horaFin.options = self.get_time_options(is_fechaInicio=False) - self.dropdown_horaFin.update() self.verifyForm() - self.tf_horaInicio.update() else: self.horaFin = e.control.value self.tf_horaFin.value = datetime.time(int(self.horaFin.split(":")[0]), 0).strftime("%I:%M %p").lstrip("0") - self.tf_horaFin.update() - + self.tf_horaFin.update() def verifyForm(self, e=None): # Limpia espacios + + print("Verificando formulario...") self.fechaInicio = (self.fechaInicio or "").strip() self.fechaFin = (self.fechaFin or "").strip() self.horaInicio = (self.horaInicio or "").strip() @@ -451,16 +450,20 @@ def verifyForm(self, e=None): return False else: self.horaInicio = self.tf_horaInicio.value - + self.dd_horaFin.options = self.get_time_options(is_fechaInicio=False) + self.dd_horaInicio.value = datetime.datetime.strptime(self.horaInicio, "%I:%M %p").strftime("%H:00") + self.dd_horaInicio.update() + # Hora fin #validar formato if not re.match(time_pattern, self.tf_horaFin.value): self.horaFin = datetime.datetime.strptime(self.horaFin, "%I:%M %p").strftime("%I:%M %p").lstrip("0") - if self.tf_horaFin: self.tf_horaFin.value = self.horaFin + self.tf_horaFin.value = self.horaFin self.tf_horaFin.update() return False else: self.horaFin = self.tf_horaFin.value + #hora fin no puede ser menor que hora inicio convierte a formato 24 horas para comparar horaFin_24 = datetime.datetime.strptime(self.tf_horaFin.value, "%I:%M %p") @@ -473,17 +476,19 @@ def verifyForm(self, e=None): else: self.horaFin = (datetime.datetime.strptime(self.horaInicio, "%I:%M %p") + datetime.timedelta(hours=1)).strftime("%I:%M %p").lstrip("0") if self.tf_horaFin: self.tf_horaFin.value = self.horaFin + if self.dropdown_horaFin: self.dropdown_horaFin.value = datetime.datetime.strptime(self.horaFin, "%I:%M %p").strftime("%H:00") self.tf_horaFin.update() + self.dropdown_horaFin.update() return False - if not self.NombreEvento: self.NombreEvento = "Nuevo evento" return True def on_save_click(self, e): - # 1) validar / corregir campos + # validar / corregir campos istrue = self.verifyForm() + if istrue: try: @@ -588,12 +593,13 @@ def create_event(self, e): new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, - start_time=self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")), - end_time=self.to_utc_datetime(self.formCalendar.fechaFin, datetime.datetime.strptime(self.formCalendar.horaFin, "%I:%M %p").strftime("%H:%M")), + start_time= datetime.datetime.strptime(f"{self.formCalendar.fechaInicio} {self.formCalendar.horaInicio}", "%d/%m/%Y %I:%M %p"), + end_time= datetime.datetime.strptime(f"{self.formCalendar.fechaFin} {self.formCalendar.horaFin}", "%d/%m/%Y %I:%M %p"), description=self.formCalendar.descripcion, location=self.formCalendar.ubicacion, color=ft.Colors.GREEN_800, ) + # Agregar evento a la API response = self.api_add_event(new_event) if self.api_add_event else None if response: new_event.id = response.data[0]["id"] @@ -615,15 +621,17 @@ def open_edit_event(self, event: calendar_event.Event): # actualizar evento existente def update_event(self, event: calendar_event.Event): event.title = self.formCalendar.NombreEvento - event.start_time = self.to_utc_datetime(self.formCalendar.fechaInicio, datetime.datetime.strptime(self.formCalendar.horaInicio, "%I:%M %p").strftime("%H:%M")) - event.end_time = self.to_utc_datetime(self.formCalendar.fechaFin, datetime.datetime.strptime(self.formCalendar.horaFin, "%I:%M %p").strftime("%H:%M")) + event.start_time = datetime.datetime.strptime(f"{self.formCalendar.fechaInicio} {self.formCalendar.horaInicio}", "%d/%m/%Y %I:%M %p") + event.end_time = datetime.datetime.strptime(f"{self.formCalendar.fechaFin} {self.formCalendar.horaFin}", "%d/%m/%Y %I:%M %p") self.modal.close() + # Actualizar evento en la API self.api_update_event(event) if self.api_update_event else None self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) def delete_event(self, id): self.event_manager.remove_event(id) self.modal.close() + # Eliminar evento en la API self.api_remove_event(id) if self.api_remove_event else None self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) @@ -632,15 +640,3 @@ def _update_week(self): self.header.update_week(self.week_days) self.grid.render_events(self.event_manager.get_events_for_week(self.week_days), self.week_days) self.update_month_label(self.week_days) - - def to_utc_datetime(self, fecha: str, hora: str) -> datetime.datetime: - # Intentar varios formatos comunes - dt_str = f"{fecha} {hora}" - for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%d/%m/%Y %H:%M"): - try: - dt = datetime.datetime.strptime(dt_str, fmt) - return dt.replace(tzinfo=timezone.utc) - except ValueError: - continue - dt = datetime.datetime.now() - return dt.replace(tzinfo=timezone.utc) From b56df9bf234f783caebaaf0e97e845d2e4d65f11 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 20 Oct 2025 17:25:19 -0600 Subject: [PATCH 46/47] Add error handling for event fetching in build_view_calendar --- src/views/calendar_view.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/views/calendar_view.py b/src/views/calendar_view.py index 7140d46..d73bd1a 100644 --- a/src/views/calendar_view.py +++ b/src/views/calendar_view.py @@ -10,7 +10,11 @@ def build_view_calendar(page): - response = (supabase.table("Events").select("*").execute()) + try: + response = supabase.table("Events").select("*").execute() + except Exception as e: + print("Error fetching Events from Supabase:", e) + response = None def event_to_dict(event): return { @@ -33,7 +37,7 @@ def update_event(event): response = supabase.table("Events").update(event_to_dict(event)).eq("id", event.id).execute() return ft.Container( - content=Calendar(response.data, add_event=add_event, remove_event=remove_event, update_event=update_event), + content=Calendar(), expand=True, ) From d20341e8bc1d8666e6b022c54f715c53178333b4 Mon Sep 17 00:00:00 2001 From: Luisda1828 Date: Mon, 20 Oct 2025 17:25:31 -0600 Subject: [PATCH 47/47] Add random color selection for new events in create_event method --- src/components/shared/calendar.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/shared/calendar.py b/src/components/shared/calendar.py index bc53d67..b96945d 100644 --- a/src/components/shared/calendar.py +++ b/src/components/shared/calendar.py @@ -6,6 +6,7 @@ from data.manager.calendar_event_manager import EventManager from components.shared.draggableModal import DraggableModal import re +import random # --------------------------- # Cabecera del calendario @@ -590,6 +591,13 @@ def open_new_event(self, day=None, hour=None): self.modal.open() def create_event(self, e): + colors = [ + ft.Colors.RED_400, ft.Colors.PINK_400, ft.Colors.PURPLE_400, + ft.Colors.INDIGO_400, ft.Colors.BLUE_400, ft.Colors.CYAN_400, + ft.Colors.TEAL_400, ft.Colors.GREEN_400, ft.Colors.AMBER_700, + ft.Colors.ORANGE_700 + ] + new_event = calendar_event.Event( id=str(uuid.uuid4()), title=self.formCalendar.NombreEvento, @@ -597,7 +605,7 @@ def create_event(self, e): end_time= datetime.datetime.strptime(f"{self.formCalendar.fechaFin} {self.formCalendar.horaFin}", "%d/%m/%Y %I:%M %p"), description=self.formCalendar.descripcion, location=self.formCalendar.ubicacion, - color=ft.Colors.GREEN_800, + color=random.choice(colors), ) # Agregar evento a la API response = self.api_add_event(new_event) if self.api_add_event else None