diff --git a/crates/apps/tahuantinsuyu/src/shell.rs b/crates/apps/tahuantinsuyu/src/shell.rs index c803645..0cd033e 100644 --- a/crates/apps/tahuantinsuyu/src/shell.rs +++ b/crates/apps/tahuantinsuyu/src/shell.rs @@ -249,6 +249,7 @@ impl Shell { entries.push(FreeChartEntry { id: FreeChartId::sky_now(), label: c.label.clone(), + birth_data: c.birth_data.clone(), }); } let mut others: Vec<(&FreeChartId, &Chart)> = self @@ -261,6 +262,7 @@ impl Shell { entries.push(FreeChartEntry { id: id.clone(), label: c.label.clone(), + birth_data: c.birth_data.clone(), }); } self.tree @@ -502,6 +504,32 @@ impl Shell { ); return; } + TreeEvent::FreeChartEditConfirmed { + source_id, + birth_data, + label, + } => { + if let Some(chart) = self.free_charts.get_mut(source_id) { + chart.birth_data = birth_data.clone(); + chart.label = label.clone(); + } + self.push_free_charts_to_tree(cx); + // Si la carta editada era la activa, re-render. + if let Some(current) = self.current_chart.as_mut() { + // Heurística: comparamos por label (ya cambiado al + // que pidió el usuario). Si el label de la activa + // coincide, era esta carta. + if current.label == label.clone() + || current.birth_data.subject_name.as_deref() == Some("Cielo") + { + if let Some(updated) = self.free_charts.get(source_id) { + *current = updated.clone(); + self.render_current(cx); + } + } + } + return; + } TreeEvent::DeleteFreeChartRequested(id) => { if id.is_sky_now() { return; // no se borra el Cielo diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-tree/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-tree/src/lib.rs index 821b8b6..086d03d 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-tree/src/lib.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-tree/src/lib.rs @@ -79,6 +79,13 @@ pub enum TreeEvent { contact: Option, new_contact_name: Option, }, + /// Submit del modal "Editar datos" para una carta libre. El + /// shell aplica al mapa `free_charts` y re-renderea el wheel. + FreeChartEditConfirmed { + source_id: FreeChartId, + birth_data: StoredBirthData, + label: String, + }, } // ===================================================================== @@ -157,6 +164,15 @@ enum Modal { form: ChartForm, error: Option, }, + /// Editar los datos (fecha/hora/lugar) de una carta libre. + /// Reusa el mismo `ChartForm` que `EditChart`. El submit emite + /// `FreeChartEditConfirmed` que el shell aplica al mapa + /// `free_charts` y re-renderea el wheel. + EditFreeChart { + source_id: FreeChartId, + form: ChartForm, + error: Option, + }, /// Guardar una carta libre como `Chart` persistido. El usuario /// elige nombre + contacto destino (existente de la lista o /// uno nuevo creado al vuelo). El shell escucha @@ -227,13 +243,15 @@ pub struct TahuantinsuyuTree { free_charts: Vec, } -/// Entrada de la sección "Cartas libres" — id + label visible. La -/// estructura del Chart en sí vive en el shell (`free_charts` de -/// `Shell`), no en el tree. +/// Entrada de la sección "Cartas libres" — id + label visible + +/// birth_data clonado (para pre-poblar el modal "Editar datos…"). +/// El Chart completo vive en el shell; el tree mantiene esta +/// proyección compacta para no depender del shell en cada operación. #[derive(Clone, Debug)] pub struct FreeChartEntry { pub id: FreeChartId, pub label: String, + pub birth_data: StoredBirthData, } /// Preset de ciudad con datos canónicos para autocompletar lat/lon/tz @@ -777,6 +795,7 @@ impl TahuantinsuyuTree { let form = match self.modal.as_mut() { Some(Modal::CreateChart { form, .. }) => form, Some(Modal::EditChart { form, .. }) => form, + Some(Modal::EditFreeChart { form, .. }) => form, _ => { self.city_picker_open = false; cx.notify(); @@ -878,6 +897,58 @@ impl TahuantinsuyuTree { /// contactos es un snapshot recursivo de toda la jerarquía /// (no solo el nivel raíz). El usuario elige uno existente o /// deja en "Nuevo contacto" para que se cree uno al confirmar. + /// Abre el modal "Editar datos" para una carta libre. Pre-puebla + /// `ChartForm` con `birth_data` actual de la entry. Submit emite + /// `FreeChartEditConfirmed` que el shell aplica al mapa de + /// `free_charts` y re-renderea. + fn open_edit_free_chart_modal( + &mut self, + source_id: FreeChartId, + window: &mut Window, + cx: &mut Context<'_, Self>, + ) { + let entry = match self.free_charts.iter().find(|e| e.id == source_id) { + Some(e) => e.clone(), + None => return, + }; + let bd = &entry.birth_data; + let form = ChartForm { + name: self.make_input("Etiqueta de la carta", &entry.label, window, cx), + place: self.make_input( + "Lugar (ciudad, país)", + bd.birthplace_label.as_deref().unwrap_or(""), + window, + cx, + ), + year: self.make_input("Año", &bd.year.to_string(), window, cx), + month: self.make_input("Mes", &bd.month.to_string(), window, cx), + day: self.make_input("Día", &bd.day.to_string(), window, cx), + hour: self.make_input("Hora (0-23)", &bd.hour.to_string(), window, cx), + minute: self.make_input("Minuto", &bd.minute.to_string(), window, cx), + tz_offset_min: self.make_input( + "TZ offset (min)", + &bd.tz_offset_minutes.to_string(), + window, + cx, + ), + lat: self.make_input("Latitud (°)", &format!("{}", bd.latitude_deg), window, cx), + lon: self.make_input( + "Longitud (°)", + &format!("{}", bd.longitude_deg), + window, + cx, + ), + alt: self.make_input("Altitud (m)", &format!("{}", bd.altitude_m), window, cx), + }; + form.name.update(cx, |i, _| i.request_focus(window)); + self.modal = Some(Modal::EditFreeChart { + source_id, + form, + error: None, + }); + self.close_menu(cx); + } + /// Cambia `selected_contact` del modal `SaveFreeChart` activo /// sin recrear los inputs. Permite alternar entre los botones /// radio "contacto existente" y "Nuevo contacto…". @@ -1230,6 +1301,32 @@ impl TahuantinsuyuTree { } } } + Modal::EditFreeChart { + source_id, + form, + error: _, + } => { + let _ = value; + match build_chart_from_form(&form, cx) { + Ok((birth, label)) => { + cx.emit(TreeEvent::FreeChartEditConfirmed { + source_id, + birth_data: birth, + label, + }); + self.modal = None; + cx.notify(); + } + Err(msg) => { + self.modal = Some(Modal::EditFreeChart { + source_id, + form, + error: Some(SharedString::from(msg)), + }); + cx.notify(); + } + } + } Modal::SaveFreeChart { source_id, name, @@ -1617,6 +1714,14 @@ impl TahuantinsuyuTree { } MenuTarget::FreeChart(fid) => { let is_sky = fid.is_sky_now(); + let fid_edit = fid.clone(); + items = items.child( + menu_item("tts-menu-edit-free", "Editar datos…", theme).on_click( + cx.listener(move |this, _: &ClickEvent, w, cx| { + this.open_edit_free_chart_modal(fid_edit.clone(), w, cx); + }), + ), + ); let fid_save = fid.clone(); items = items.child( menu_item("tts-menu-save-free", "Guardar como…", theme).on_click( @@ -1708,6 +1813,15 @@ impl TahuantinsuyuTree { &self.city_atlas, cx, ), + Modal::EditFreeChart { form, error, .. } => render_chart_form( + theme, + "Editar carta libre", + form, + error.clone(), + self.city_picker_open, + &self.city_atlas, + cx, + ), Modal::SaveFreeChart { source_id, name,