From 64dbf65bebb2865d3daf5b3ed47e487c1b6868b1 Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Fri, 12 Sep 2025 14:10:43 -0400 Subject: [PATCH] Fix event positioning in print copy with dynamic base-unit recalculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Measure actual print copy div height after aspect-ratio scaling - Recalculate base-unit based on measured height vs original 720px assumption - Apply position scaling to .week-event elements in print copy only - Parse and recalculate top/height pixel values using scale factor - Add landscape orientation and fit-to-page CSS hints for better printing - Preserve hour filtering with proper data attributes and CSS variables This ensures events display correctly when print copy is scaled to fit page while maintaining proper aspect ratio and hour range filtering. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/print-preview.css | 19 +++- .../src/components/print_preview_modal.rs | 92 +++++++++++++++---- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/frontend/print-preview.css b/frontend/print-preview.css index 1884695..5da387d 100644 --- a/frontend/print-preview.css +++ b/frontend/print-preview.css @@ -1128,7 +1128,7 @@ .print-preview-paper[data-start-hour="7"][data-end-hour="19"]:has(.time-slot.quarter-mode) .time-labels, .print-preview-paper[data-start-hour="7"][data-end-hour="19"]:has(.time-slot.quarter-mode) .week-days-grid { min-height: 1530px !important; } -/* Print Page Setup - Force landscape orientation */ +/* Print Page Setup - Force landscape orientation and fit to page */ @page { size: landscape; margin: 0.5in; @@ -1136,6 +1136,11 @@ /* Print Media Rules - Show only the print-preview-copy when printing */ @media print { + html { + print-color-adjust: exact; /* Preserve colors when printing */ + -webkit-print-color-adjust: exact; + } + /* Hide all top-level app content */ .app { display: none !important; @@ -1147,11 +1152,19 @@ position: absolute !important; left: 0 !important; top: 0 !important; - width: 960px !important; - height: 720px !important; + /* width: 960px !important; */ + /* height: 720px !important; */ + aspect-ratio: 1.2941176470588236 !important; margin: 0 !important; padding: 0 !important; box-sizing: border-box !important; + /* Properties to help browsers scale to fit page */ + max-width: 100vw; + max-height: 100vh; + object-fit: contain; + page-break-inside: avoid; + orphans: 1; + widows: 1; } /* Ensure print content uses the full page */ diff --git a/frontend/src/components/print_preview_modal.rs b/frontend/src/components/print_preview_modal.rs index b5255c1..2e72988 100644 --- a/frontend/src/components/print_preview_modal.rs +++ b/frontend/src/components/print_preview_modal.rs @@ -106,8 +106,8 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html { { let start_hour = *start_hour; let end_hour = *end_hour; - let base_unit_value = base_unit; - let pixels_per_hour_value = pixels_per_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() { @@ -115,8 +115,6 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html { 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-base-unit", &format!("{:.2}", base_unit_value)); - let _ = style.set_property("--print-pixels-per-hour", &format!("{:.2}", pixels_per_hour_value)); let _ = style.set_property("--print-start-hour", &start_hour.to_string()); let _ = style.set_property("--print-end-hour", &end_hour.to_string()); } @@ -132,22 +130,80 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html { print_copy.set_inner_html(""); let _ = print_copy.append_child(&content_clone); - // Copy the CSS variables and data attributes from the print-preview-paper - if let Some(preview_paper) = document.query_selector(".print-preview-paper").ok().flatten() { - if let Some(_paper_element) = preview_paper.dyn_ref::() { - // Copy CSS custom properties (variables) - if let Some(print_copy_html) = print_copy.dyn_ref::() { - let style = print_copy_html.style(); - let _ = style.set_property("--print-start-hour", &start_hour.to_string()); - let _ = style.set_property("--print-end-hour", &end_hour.to_string()); - let _ = style.set_property("--print-base-unit", &format!("{:.2}", base_unit_value)); - let _ = style.set_property("--print-pixels-per-hour", &format!("{:.2}", pixels_per_hour_value)); + // 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)); + } + } + } + } } - - // 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()); } + + 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()); } } }