use crate::components::{ViewMode, WeekView, MonthView}; use crate::models::ical::VEvent; use crate::services::calendar_service::{UserInfo, ExternalCalendar}; use chrono::NaiveDate; use std::collections::HashMap; use wasm_bindgen::{closure::Closure, JsCast}; use yew::prelude::*; #[derive(Properties, PartialEq)] pub struct PrintPreviewModalProps { pub on_close: Callback<()>, pub view_mode: ViewMode, pub current_date: NaiveDate, pub selected_date: NaiveDate, pub events: HashMap>, pub user_info: Option, pub external_calendars: Vec, pub time_increment: u32, pub today: NaiveDate, } #[function_component] pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html { let start_hour = use_state(|| 6u32); let end_hour = use_state(|| 22u32); let zoom_level = use_state(|| 0.4f64); // Default 40% zoom let close_modal = { let on_close = props.on_close.clone(); Callback::from(move |_| { on_close.emit(()); }) }; let backdrop_click = { let on_close = props.on_close.clone(); Callback::from(move |e: MouseEvent| { if e.target() == e.current_target() { on_close.emit(()); } }) }; let on_start_hour_change = { let start_hour = start_hour.clone(); let end_hour = end_hour.clone(); Callback::from(move |e: Event| { let target = e.target_dyn_into::(); if let Some(select) = target { if let Ok(hour) = select.value().parse::() { if hour < *end_hour { start_hour.set(hour); } } } }) }; let on_end_hour_change = { let start_hour = start_hour.clone(); let end_hour = end_hour.clone(); Callback::from(move |e: Event| { let target = e.target_dyn_into::(); if let Some(select) = target { if let Ok(hour) = select.value().parse::() { if hour > *start_hour && hour <= 24 { end_hour.set(hour); } } } }) }; let format_hour = |hour: u32| -> String { if hour == 0 { "12 AM".to_string() } else if hour < 12 { format!("{} AM", hour) } else if hour == 12 { "12 PM".to_string() } else { format!("{} PM", hour - 12) } }; // Calculate dynamic base unit for print preview let calculate_print_dimensions = |start_hour: u32, end_hour: u32, time_increment: u32| -> (f64, f64, f64) { let visible_hours = (end_hour - start_hour) as f64; let slots_per_hour = if time_increment == 15 { 4.0 } else { 2.0 }; let header_height = 50.0; // Fixed week header height in print preview let header_border = 2.0; // Week header bottom border (2px solid) let container_spacing = 8.0; // Additional container spacing/margins let total_overhead = header_height + header_border + container_spacing; let available_height = 720.0 - total_overhead; // Available for time content let base_unit = available_height / (visible_hours * slots_per_hour); let pixels_per_hour = base_unit * slots_per_hour; (base_unit, pixels_per_hour, available_height) }; // Calculate print dimensions for the current hour range let (base_unit, pixels_per_hour, _available_height) = calculate_print_dimensions(*start_hour, *end_hour, props.time_increment); // Effect to update print copy whenever modal renders or content changes { let start_hour = *start_hour; let end_hour = *end_hour; let time_increment = props.time_increment; let original_base_unit = base_unit; use_effect(move || { if let Some(window) = web_sys::window() { if let Some(document) = window.document() { // Set CSS variables on document root if let Some(document_element) = document.document_element() { if let Some(html_element) = document_element.dyn_ref::() { let style = html_element.style(); let _ = style.set_property("--print-start-hour", &start_hour.to_string()); let _ = style.set_property("--print-end-hour", &end_hour.to_string()); } } // Copy content from print-preview-content to the hidden print-preview-copy div let copy_content = move || { if let Some(preview_content) = document.query_selector(".print-preview-content").ok().flatten() { if let Some(print_copy) = document.get_element_by_id("print-preview-copy") { // Clone the preview content if let Some(content_clone) = preview_content.clone_node_with_deep(true).ok() { // Clear the print copy div and add the cloned content print_copy.set_inner_html(""); let _ = print_copy.append_child(&content_clone); // Get the actual rendered height of the print copy div and recalculate base-unit if let Some(print_copy_html) = print_copy.dyn_ref::() { // Temporarily make visible to measure height, then hide again let original_display = print_copy_html.style().get_property_value("display").unwrap_or_default(); let _ = print_copy_html.style().set_property("display", "block"); let _ = print_copy_html.style().set_property("visibility", "hidden"); let _ = print_copy_html.style().set_property("position", "absolute"); let _ = print_copy_html.style().set_property("top", "-9999px"); // Now measure the height let actual_height = print_copy_html.client_height() as f64; // Restore original display let _ = print_copy_html.style().set_property("display", &original_display); let _ = print_copy_html.style().remove_property("visibility"); let _ = print_copy_html.style().remove_property("position"); let _ = print_copy_html.style().remove_property("top"); // Recalculate base-unit and pixels-per-hour based on actual height let visible_hours = (end_hour - start_hour) as f64; let slots_per_hour = if time_increment == 15 { 4.0 } else { 2.0 }; let header_height = 50.0; let header_border = 2.0; let container_spacing = 8.0; let total_overhead = header_height + header_border + container_spacing; let available_height = actual_height - total_overhead; let actual_base_unit = available_height / (visible_hours * slots_per_hour); let actual_pixels_per_hour = actual_base_unit * slots_per_hour; // Set CSS variables with recalculated values let style = print_copy_html.style(); let _ = style.set_property("--print-base-unit", &format!("{:.2}", actual_base_unit)); let _ = style.set_property("--print-pixels-per-hour", &format!("{:.2}", actual_pixels_per_hour)); let _ = style.set_property("--print-start-hour", &start_hour.to_string()); let _ = style.set_property("--print-end-hour", &end_hour.to_string()); // Copy data attributes let _ = print_copy.set_attribute("data-start-hour", &start_hour.to_string()); let _ = print_copy.set_attribute("data-end-hour", &end_hour.to_string()); // Recalculate event positions using the new base-unit let events = print_copy.query_selector_all(".week-event").unwrap(); let scale_factor = actual_base_unit / original_base_unit; for i in 0..events.length() { if let Some(event_element) = events.get(i) { if let Some(event_html) = event_element.dyn_ref::() { let event_style = event_html.style(); // Get current positioning values and recalculate if let Ok(current_top) = event_style.get_property_value("top") { if current_top.ends_with("px") { if let Ok(top_px) = current_top[..current_top.len()-2].parse::() { let new_top = top_px * scale_factor; let _ = event_style.set_property("top", &format!("{:.2}px", new_top)); } } } if let Ok(current_height) = event_style.get_property_value("height") { if current_height.ends_with("px") { if let Ok(height_px) = current_height[..current_height.len()-2].parse::() { let new_height = height_px * scale_factor; let _ = event_style.set_property("height", &format!("{:.2}px", new_height)); } } } } } } web_sys::console::log_1(&format!("Height: {:.2}, Original base-unit: {:.2}, New base-unit: {:.2}, Scale factor: {:.2}", actual_height, original_base_unit, actual_base_unit, scale_factor).into()); } } } } }; // Copy content immediately copy_content(); // Also set up a small delay to catch any async rendering let copy_callback = Closure::wrap(Box::new(copy_content) as Box); let _ = window.set_timeout_with_callback_and_timeout_and_arguments_0( copy_callback.as_ref().unchecked_ref(), 100 ); copy_callback.forget(); } } || () }); } let on_print = { Callback::from(move |_: MouseEvent| { if let Some(window) = web_sys::window() { // Print copy is already updated by the use_effect, just trigger print let _ = window.print(); } }) }; html! { } }