Compare commits
1 Commits
4cbc495c48
...
bugfix/ext
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91be4436a9 |
@@ -78,16 +78,74 @@ pub async fn fetch_external_calendar_events(
|
|||||||
|
|
||||||
// If not fetched from cache, get from external URL
|
// If not fetched from cache, get from external URL
|
||||||
if !fetched_from_cache {
|
if !fetched_from_cache {
|
||||||
let client = Client::new();
|
// Log the URL being fetched for debugging
|
||||||
let response = client
|
println!("🌍 Fetching calendar URL: {}", calendar.url);
|
||||||
.get(&calendar.url)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| ApiError::Internal(format!("Failed to fetch calendar: {}", e)))?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
let user_agents = vec![
|
||||||
return Err(ApiError::Internal(format!("Calendar server returned: {}", response.status())));
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (compatible; Runway Calendar/1.0)",
|
||||||
|
"Outlook-iOS/709.2226530.prod.iphone (3.24.1)"
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut response = None;
|
||||||
|
let mut last_error = None;
|
||||||
|
|
||||||
|
// Try different user agents
|
||||||
|
for (i, ua) in user_agents.iter().enumerate() {
|
||||||
|
println!("🔄 Attempt {} with User-Agent: {}", i + 1, ua);
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.redirect(reqwest::redirect::Policy::limited(10))
|
||||||
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
|
.user_agent(*ua)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| ApiError::Internal(format!("Failed to create HTTP client: {}", e)))?;
|
||||||
|
|
||||||
|
let result = client
|
||||||
|
.get(&calendar.url)
|
||||||
|
.header("Accept", "text/calendar,application/calendar+xml,text/plain,*/*")
|
||||||
|
.header("Accept-Charset", "utf-8")
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(resp) => {
|
||||||
|
let status = resp.status();
|
||||||
|
println!("📡 Response status: {}", status);
|
||||||
|
if status.is_success() {
|
||||||
|
response = Some(resp);
|
||||||
|
break;
|
||||||
|
} else if status == 400 {
|
||||||
|
// Check if this is an Outlook auth error
|
||||||
|
let error_body = resp.text().await.unwrap_or_default();
|
||||||
|
if error_body.contains("OwaPage") || error_body.contains("Outlook") {
|
||||||
|
println!("🚫 Outlook authentication error detected, trying next approach...");
|
||||||
|
last_error = Some(format!("Outlook auth error: {}", error_body.chars().take(100).collect::<String>()));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
last_error = Some(format!("Bad Request: {}", error_body.chars().take(100).collect::<String>()));
|
||||||
|
} else {
|
||||||
|
last_error = Some(format!("HTTP {}", status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("❌ Request failed: {}", e);
|
||||||
|
last_error = Some(format!("Request error: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.ok_or_else(|| {
|
||||||
|
ApiError::Internal(format!(
|
||||||
|
"Failed to fetch calendar after trying {} different approaches. Last error: {}",
|
||||||
|
user_agents.len(),
|
||||||
|
last_error.unwrap_or("Unknown error".to_string())
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Response is guaranteed to be successful here since we checked in the loop
|
||||||
|
println!("✅ Successfully fetched calendar data");
|
||||||
|
|
||||||
ics_content = response
|
ics_content = response
|
||||||
.text()
|
.text()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ dist = "dist"
|
|||||||
BACKEND_API_URL = "http://localhost:3000/api"
|
BACKEND_API_URL = "http://localhost:3000/api"
|
||||||
|
|
||||||
[watch]
|
[watch]
|
||||||
watch = ["src", "Cargo.toml", "../calendar-models/src", "styles.css", "print.css", "print-preview.css", "index.html"]
|
watch = ["src", "Cargo.toml", "../calendar-models/src", "styles.css", "index.html"]
|
||||||
ignore = ["../backend/", "../target/"]
|
ignore = ["../backend/", "../target/"]
|
||||||
|
|
||||||
[serve]
|
[serve]
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<base data-trunk-public-url />
|
<base data-trunk-public-url />
|
||||||
<link data-trunk rel="css" href="styles.css">
|
<link data-trunk rel="css" href="styles.css">
|
||||||
<link data-trunk rel="css" href="print.css">
|
|
||||||
<link data-trunk rel="css" href="print-preview.css">
|
|
||||||
<link data-trunk rel="copy-file" href="styles/google.css">
|
<link data-trunk rel="copy-file" href="styles/google.css">
|
||||||
<link data-trunk rel="icon" href="favicon.ico">
|
<link data-trunk rel="icon" href="favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,302 +0,0 @@
|
|||||||
/* Print-specific styles for calendar printing */
|
|
||||||
@media print {
|
|
||||||
/* Hide UI elements that shouldn't be printed */
|
|
||||||
.app-sidebar,
|
|
||||||
.current-time-indicator-container,
|
|
||||||
.current-time-indicator,
|
|
||||||
.print-button,
|
|
||||||
.nav-button,
|
|
||||||
.today-button,
|
|
||||||
.time-increment-button,
|
|
||||||
.modal-backdrop,
|
|
||||||
.create-event-modal,
|
|
||||||
.event-modal,
|
|
||||||
.calendar-management-modal,
|
|
||||||
.context-menu {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove today highlighting from calendar elements */
|
|
||||||
.calendar-day.today,
|
|
||||||
.week-day-header.today,
|
|
||||||
.week-day-column.today {
|
|
||||||
background-color: transparent !important;
|
|
||||||
border-color: var(--border-color) !important;
|
|
||||||
color: var(--text-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove today-specific styling from day numbers */
|
|
||||||
.calendar-day.today .day-number {
|
|
||||||
background-color: transparent !important;
|
|
||||||
color: var(--text-color) !important;
|
|
||||||
font-weight: normal !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove today indicator from week day headers */
|
|
||||||
.week-day-header.today .weekday-name {
|
|
||||||
color: var(--text-color) !important;
|
|
||||||
font-weight: normal !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page setup */
|
|
||||||
@page {
|
|
||||||
size: letter landscape;
|
|
||||||
margin: 0.5in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make app and main container fill full page width */
|
|
||||||
.app,
|
|
||||||
.app-main,
|
|
||||||
.calendar-view {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: none !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove any flexbox constraints that might limit width */
|
|
||||||
.app {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-main {
|
|
||||||
margin-left: 0 !important; /* Remove sidebar margin */
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure calendar uses full available width */
|
|
||||||
.calendar-container,
|
|
||||||
.calendar {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: none !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adjust calendar header for printing */
|
|
||||||
.calendar-header {
|
|
||||||
margin-bottom: 0.5rem !important;
|
|
||||||
padding-bottom: 0.5rem !important;
|
|
||||||
border-bottom: 1px solid var(--border-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure text is readable in print */
|
|
||||||
body, html {
|
|
||||||
color: black !important;
|
|
||||||
background: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure event text is readable */
|
|
||||||
.week-event,
|
|
||||||
.calendar-event,
|
|
||||||
.month-event {
|
|
||||||
border: 1px solid #333 !important;
|
|
||||||
color: black !important;
|
|
||||||
font-size: 0.8rem !important;
|
|
||||||
line-height: 1.2 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure month view events are visible */
|
|
||||||
.calendar-day .event-list .event-item {
|
|
||||||
background-color: #f5f5f5 !important;
|
|
||||||
border: 1px solid #333 !important;
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Week view specific adjustments - force full day view */
|
|
||||||
.week-view-container {
|
|
||||||
width: 100% !important;
|
|
||||||
height: auto !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.week-day-column {
|
|
||||||
border-right: 1px solid #333 !important;
|
|
||||||
height: auto !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-column {
|
|
||||||
border-right: 2px solid #333 !important;
|
|
||||||
width: 60px !important;
|
|
||||||
min-width: 60px !important;
|
|
||||||
height: auto !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Default time slot sizes - will be adjusted by hour range */
|
|
||||||
.time-slot {
|
|
||||||
height: 20px !important;
|
|
||||||
min-height: 20px !important;
|
|
||||||
max-height: 20px !important;
|
|
||||||
border-bottom: 1px solid #ddd !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Time slot quarters for 15-minute mode */
|
|
||||||
.time-slot.quarter-mode .time-slot-quarter {
|
|
||||||
height: 5px !important;
|
|
||||||
min-height: 5px !important;
|
|
||||||
max-height: 5px !important;
|
|
||||||
border-bottom: 1px solid #eee !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Time slot halves for 30-minute mode */
|
|
||||||
.time-slot .time-slot-half {
|
|
||||||
height: 10px !important;
|
|
||||||
min-height: 10px !important;
|
|
||||||
max-height: 10px !important;
|
|
||||||
border-bottom: 1px solid #eee !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make hour boundaries more visible */
|
|
||||||
.time-slot:nth-child(4n) {
|
|
||||||
border-bottom: 1px solid #333 !important;
|
|
||||||
height: 20px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dynamic hour range hiding for print mode */
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="1"] .week-view .time-slot:nth-child(1) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="2"] .week-view .time-slot:nth-child(-n+2) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="3"] .week-view .time-slot:nth-child(-n+3) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="4"] .week-view .time-slot:nth-child(-n+4) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="5"] .week-view .time-slot:nth-child(-n+5) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="6"] .week-view .time-slot:nth-child(-n+6) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="7"] .week-view .time-slot:nth-child(-n+7) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="8"] .week-view .time-slot:nth-child(-n+8) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="9"] .week-view .time-slot:nth-child(-n+9) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="10"] .week-view .time-slot:nth-child(-n+10) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="11"] .week-view .time-slot:nth-child(-n+11) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="12"] .week-view .time-slot:nth-child(-n+12) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="13"] .week-view .time-slot:nth-child(-n+13) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="14"] .week-view .time-slot:nth-child(-n+14) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="15"] .week-view .time-slot:nth-child(-n+15) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="16"] .week-view .time-slot:nth-child(-n+16) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="17"] .week-view .time-slot:nth-child(-n+17) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="18"] .week-view .time-slot:nth-child(-n+18) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="19"] .week-view .time-slot:nth-child(-n+19) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="20"] .week-view .time-slot:nth-child(-n+20) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="21"] .week-view .time-slot:nth-child(-n+21) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="22"] .week-view .time-slot:nth-child(-n+22) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-start-hour="23"] .week-view .time-slot:nth-child(-n+23) { display: none !important; }
|
|
||||||
|
|
||||||
/* Hide hours after end-hour */
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="1"] .week-view .time-slot:nth-child(n+2) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="2"] .week-view .time-slot:nth-child(n+3) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="3"] .week-view .time-slot:nth-child(n+4) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="4"] .week-view .time-slot:nth-child(n+5) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="5"] .week-view .time-slot:nth-child(n+6) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="6"] .week-view .time-slot:nth-child(n+7) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="7"] .week-view .time-slot:nth-child(n+8) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="8"] .week-view .time-slot:nth-child(n+9) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="9"] .week-view .time-slot:nth-child(n+10) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="10"] .week-view .time-slot:nth-child(n+11) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="11"] .week-view .time-slot:nth-child(n+12) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="12"] .week-view .time-slot:nth-child(n+13) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="13"] .week-view .time-slot:nth-child(n+14) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="14"] .week-view .time-slot:nth-child(n+15) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="15"] .week-view .time-slot:nth-child(n+16) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="16"] .week-view .time-slot:nth-child(n+17) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="17"] .week-view .time-slot:nth-child(n+18) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="18"] .week-view .time-slot:nth-child(n+19) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="19"] .week-view .time-slot:nth-child(n+20) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="20"] .week-view .time-slot:nth-child(n+21) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="21"] .week-view .time-slot:nth-child(n+22) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="22"] .week-view .time-slot:nth-child(n+23) { display: none !important; }
|
|
||||||
body[data-print-mode="true"][data-print-end-hour="23"] .week-view .time-slot:nth-child(n+24) { display: none !important; }
|
|
||||||
|
|
||||||
/* Force the week grid to show full height */
|
|
||||||
.week-grid,
|
|
||||||
.week-content,
|
|
||||||
.time-grid {
|
|
||||||
height: auto !important;
|
|
||||||
max-height: none !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure all hours are visible by removing any height constraints */
|
|
||||||
.week-view-container,
|
|
||||||
.week-view-container > div {
|
|
||||||
height: auto !important;
|
|
||||||
max-height: none !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Month view specific adjustments */
|
|
||||||
.calendar-grid {
|
|
||||||
border: 1px solid #333 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-day {
|
|
||||||
border: 1px solid #333 !important;
|
|
||||||
min-height: 80px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure grid lines are visible - handled above in main time-slot rules */
|
|
||||||
|
|
||||||
/* Make sure header text is visible */
|
|
||||||
.calendar-header h2,
|
|
||||||
.calendar-header .month-year {
|
|
||||||
color: black !important;
|
|
||||||
font-size: 1.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.week-day-header .weekday-name,
|
|
||||||
.week-day-header .date-number {
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Time labels in week view */
|
|
||||||
.time-label {
|
|
||||||
color: #666 !important;
|
|
||||||
font-size: 0.75rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove any shadows or fancy effects */
|
|
||||||
* {
|
|
||||||
box-shadow: none !important;
|
|
||||||
text-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure proper spacing */
|
|
||||||
.calendar-day .day-number {
|
|
||||||
margin-bottom: 0.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure events don't overlap text in month view */
|
|
||||||
.calendar-day .event-list {
|
|
||||||
margin-top: 0.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Force page break before calendar if needed */
|
|
||||||
.calendar {
|
|
||||||
page-break-inside: avoid !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Additional print button styling for screen display */
|
|
||||||
.print-button {
|
|
||||||
background: rgba(255,255,255,0.2);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-button:hover {
|
|
||||||
background: rgba(255,255,255,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-button:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::components::{
|
use crate::components::{
|
||||||
CalendarHeader, CreateEventModal, EventCreationData, EventModal, MonthView, PrintPreviewModal, ViewMode, WeekView,
|
CalendarHeader, CreateEventModal, EventCreationData, EventModal, MonthView, ViewMode, WeekView,
|
||||||
};
|
};
|
||||||
use crate::models::ical::VEvent;
|
use crate::models::ical::VEvent;
|
||||||
use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService};
|
use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService};
|
||||||
@@ -389,15 +389,6 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle print calendar preview
|
|
||||||
let show_print_preview = use_state(|| false);
|
|
||||||
let on_print = {
|
|
||||||
let show_print_preview = show_print_preview.clone();
|
|
||||||
Callback::from(move |_: MouseEvent| {
|
|
||||||
show_print_preview.set(true);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle drag-to-create event
|
// Handle drag-to-create event
|
||||||
let on_create_event = {
|
let on_create_event = {
|
||||||
let show_create_modal = show_create_modal.clone();
|
let show_create_modal = show_create_modal.clone();
|
||||||
@@ -466,7 +457,6 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
|||||||
on_today={on_today}
|
on_today={on_today}
|
||||||
time_increment={Some(*time_increment)}
|
time_increment={Some(*time_increment)}
|
||||||
on_time_increment_toggle={Some(on_time_increment_toggle)}
|
on_time_increment_toggle={Some(on_time_increment_toggle)}
|
||||||
on_print={Some(on_print)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -573,32 +563,6 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
// Print preview modal
|
|
||||||
{
|
|
||||||
if *show_print_preview {
|
|
||||||
html! {
|
|
||||||
<PrintPreviewModal
|
|
||||||
on_close={{
|
|
||||||
let show_print_preview = show_print_preview.clone();
|
|
||||||
Callback::from(move |_| {
|
|
||||||
show_print_preview.set(false);
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
view_mode={props.view.clone()}
|
|
||||||
current_date={*current_date}
|
|
||||||
selected_date={*selected_date}
|
|
||||||
events={(*events).clone()}
|
|
||||||
user_info={props.user_info.clone()}
|
|
||||||
external_calendars={props.external_calendars.clone()}
|
|
||||||
time_increment={*time_increment}
|
|
||||||
today={today}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ pub struct CalendarHeaderProps {
|
|||||||
pub time_increment: Option<u32>,
|
pub time_increment: Option<u32>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub on_time_increment_toggle: Option<Callback<MouseEvent>>,
|
pub on_time_increment_toggle: Option<Callback<MouseEvent>>,
|
||||||
#[prop_or_default]
|
|
||||||
pub on_print: Option<Callback<MouseEvent>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component(CalendarHeader)]
|
#[function_component(CalendarHeader)]
|
||||||
@@ -41,17 +39,6 @@ pub fn calendar_header(props: &CalendarHeaderProps) -> Html {
|
|||||||
html! {}
|
html! {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
if let Some(print_callback) = &props.on_print {
|
|
||||||
html! {
|
|
||||||
<button class="print-button" onclick={print_callback.clone()} title="Print Calendar">
|
|
||||||
{"🖨️"}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<h2 class="month-year">{title}</h2>
|
<h2 class="month-year">{title}</h2>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
let is_creating = use_state(|| false);
|
let is_creating = use_state(|| false);
|
||||||
|
|
||||||
// External Calendar state
|
// External Calendar state
|
||||||
let external_name_ref = use_node_ref();
|
let external_name = use_state(|| String::new());
|
||||||
let external_url_ref = use_node_ref();
|
let external_url = use_state(|| String::new());
|
||||||
let external_selected_color = use_state(|| Some("#4285f4".to_string()));
|
let external_selected_color = use_state(|| Some("#4285f4".to_string()));
|
||||||
let external_is_loading = use_state(|| false);
|
let external_is_loading = use_state(|| false);
|
||||||
let external_error_message = use_state(|| None::<String>);
|
let external_error_message = use_state(|| None::<String>);
|
||||||
@@ -43,6 +43,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
let selected_color = selected_color.clone();
|
let selected_color = selected_color.clone();
|
||||||
let create_error_message = create_error_message.clone();
|
let create_error_message = create_error_message.clone();
|
||||||
let is_creating = is_creating.clone();
|
let is_creating = is_creating.clone();
|
||||||
|
let external_name = external_name.clone();
|
||||||
|
let external_url = external_url.clone();
|
||||||
let external_is_loading = external_is_loading.clone();
|
let external_is_loading = external_is_loading.clone();
|
||||||
let external_error_message = external_error_message.clone();
|
let external_error_message = external_error_message.clone();
|
||||||
let external_selected_color = external_selected_color.clone();
|
let external_selected_color = external_selected_color.clone();
|
||||||
@@ -56,6 +58,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
selected_color.set(None);
|
selected_color.set(None);
|
||||||
create_error_message.set(None);
|
create_error_message.set(None);
|
||||||
is_creating.set(false);
|
is_creating.set(false);
|
||||||
|
external_name.set(String::new());
|
||||||
|
external_url.set(String::new());
|
||||||
external_is_loading.set(false);
|
external_is_loading.set(false);
|
||||||
external_error_message.set(None);
|
external_error_message.set(None);
|
||||||
external_selected_color.set(Some("#4285f4".to_string()));
|
external_selected_color.set(Some("#4285f4".to_string()));
|
||||||
@@ -146,8 +150,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
|
|
||||||
// External Calendar handlers
|
// External Calendar handlers
|
||||||
let on_external_submit = {
|
let on_external_submit = {
|
||||||
let external_name_ref = external_name_ref.clone();
|
let external_name = external_name.clone();
|
||||||
let external_url_ref = external_url_ref.clone();
|
let external_url = external_url.clone();
|
||||||
let external_selected_color = external_selected_color.clone();
|
let external_selected_color = external_selected_color.clone();
|
||||||
let external_is_loading = external_is_loading.clone();
|
let external_is_loading = external_is_loading.clone();
|
||||||
let external_error_message = external_error_message.clone();
|
let external_error_message = external_error_message.clone();
|
||||||
@@ -157,24 +161,28 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
Callback::from(move |e: SubmitEvent| {
|
Callback::from(move |e: SubmitEvent| {
|
||||||
e.prevent_default();
|
e.prevent_default();
|
||||||
|
|
||||||
let name = external_name_ref
|
let name = (*external_name).trim().to_string();
|
||||||
.cast::<HtmlInputElement>()
|
let url = (*external_url).trim().to_string();
|
||||||
.map(|input| input.value())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.trim()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let url = external_url_ref
|
|
||||||
.cast::<HtmlInputElement>()
|
|
||||||
.map(|input| input.value())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.trim()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let color = (*external_selected_color).clone().unwrap_or_else(|| "#4285f4".to_string());
|
let color = (*external_selected_color).clone().unwrap_or_else(|| "#4285f4".to_string());
|
||||||
|
|
||||||
if name.is_empty() || url.is_empty() {
|
// Debug logging to understand the issue
|
||||||
external_error_message.set(Some("Name and URL are required".to_string()));
|
web_sys::console::log_1(&format!("External calendar form submission - Name: '{}', URL: '{}'", name, url).into());
|
||||||
|
|
||||||
|
if name.is_empty() {
|
||||||
|
external_error_message.set(Some("Calendar name is required".to_string()));
|
||||||
|
web_sys::console::log_1(&"Validation failed: empty name".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.is_empty() {
|
||||||
|
external_error_message.set(Some("Calendar URL is required".to_string()));
|
||||||
|
web_sys::console::log_1(&"Validation failed: empty URL".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic URL validation
|
||||||
|
if !url.starts_with("http://") && !url.starts_with("https://") {
|
||||||
|
external_error_message.set(Some("Please enter a valid HTTP or HTTPS URL".to_string()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +212,25 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// External input change handlers
|
||||||
|
let on_external_name_change = {
|
||||||
|
let external_name = external_name.clone();
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
|
||||||
|
external_name.set(input.value());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_external_url_change = {
|
||||||
|
let external_url = external_url.clone();
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
|
||||||
|
external_url.set(input.value());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
if !props.is_open {
|
if !props.is_open {
|
||||||
return html! {};
|
return html! {};
|
||||||
}
|
}
|
||||||
@@ -333,7 +360,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="external-name"
|
id="external-name"
|
||||||
ref={external_name_ref.clone()}
|
value={(*external_name).clone()}
|
||||||
|
onchange={on_external_name_change}
|
||||||
placeholder="Enter calendar name"
|
placeholder="Enter calendar name"
|
||||||
disabled={*external_is_loading}
|
disabled={*external_is_loading}
|
||||||
/>
|
/>
|
||||||
@@ -344,7 +372,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html {
|
|||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
id="external-url"
|
id="external-url"
|
||||||
ref={external_url_ref.clone()}
|
value={(*external_url).clone()}
|
||||||
|
onchange={on_external_url_change}
|
||||||
placeholder="https://example.com/calendar.ics"
|
placeholder="https://example.com/calendar.ics"
|
||||||
disabled={*external_is_loading}
|
disabled={*external_is_loading}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ pub mod external_calendar_modal;
|
|||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod mobile_warning_modal;
|
pub mod mobile_warning_modal;
|
||||||
pub mod month_view;
|
pub mod month_view;
|
||||||
pub mod print_preview_modal;
|
|
||||||
pub mod recurring_edit_modal;
|
pub mod recurring_edit_modal;
|
||||||
pub mod route_handler;
|
pub mod route_handler;
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
@@ -33,7 +32,6 @@ pub use event_modal::EventModal;
|
|||||||
pub use login::Login;
|
pub use login::Login;
|
||||||
pub use mobile_warning_modal::MobileWarningModal;
|
pub use mobile_warning_modal::MobileWarningModal;
|
||||||
pub use month_view::MonthView;
|
pub use month_view::MonthView;
|
||||||
pub use print_preview_modal::PrintPreviewModal;
|
|
||||||
pub use recurring_edit_modal::{RecurringEditAction, RecurringEditModal};
|
pub use recurring_edit_modal::{RecurringEditAction, RecurringEditModal};
|
||||||
pub use route_handler::RouteHandler;
|
pub use route_handler::RouteHandler;
|
||||||
pub use sidebar::{Sidebar, Theme, ViewMode};
|
pub use sidebar::{Sidebar, Theme, ViewMode};
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
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<NaiveDate, Vec<VEvent>>,
|
|
||||||
pub user_info: Option<UserInfo>,
|
|
||||||
pub external_calendars: Vec<ExternalCalendar>,
|
|
||||||
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::<web_sys::HtmlSelectElement>();
|
|
||||||
if let Some(select) = target {
|
|
||||||
if let Ok(hour) = select.value().parse::<u32>() {
|
|
||||||
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::<web_sys::HtmlSelectElement>();
|
|
||||||
if let Some(select) = target {
|
|
||||||
if let Ok(hour) = select.value().parse::<u32>() {
|
|
||||||
if hour > *start_hour && hour <= 24 {
|
|
||||||
end_hour.set(hour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let on_print = {
|
|
||||||
let start_hour = *start_hour;
|
|
||||||
let end_hour = *end_hour;
|
|
||||||
let view_mode = props.view_mode.clone();
|
|
||||||
Callback::from(move |_: MouseEvent| {
|
|
||||||
if let Some(window) = web_sys::window() {
|
|
||||||
if let Some(document) = window.document() {
|
|
||||||
if let Some(body) = document.body() {
|
|
||||||
// Add print attributes to body for CSS targeting
|
|
||||||
if view_mode == ViewMode::Week {
|
|
||||||
let _ = body.set_attribute("data-print-start-hour", &start_hour.to_string());
|
|
||||||
let _ = body.set_attribute("data-print-end-hour", &end_hour.to_string());
|
|
||||||
}
|
|
||||||
let _ = body.set_attribute("data-print-mode", "true");
|
|
||||||
|
|
||||||
// Trigger print
|
|
||||||
if let Err(e) = window.print() {
|
|
||||||
web_sys::console::log_1(&format!("Print failed: {:?}", e).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up attributes after a short delay
|
|
||||||
let cleanup_body = body.clone();
|
|
||||||
let cleanup_callback = Closure::wrap(Box::new(move || {
|
|
||||||
let _ = cleanup_body.remove_attribute("data-print-start-hour");
|
|
||||||
let _ = cleanup_body.remove_attribute("data-print-end-hour");
|
|
||||||
let _ = cleanup_body.remove_attribute("data-print-mode");
|
|
||||||
}) as Box<dyn FnMut()>);
|
|
||||||
|
|
||||||
let _ = window.set_timeout_with_callback_and_timeout_and_arguments_0(
|
|
||||||
cleanup_callback.as_ref().unchecked_ref(),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
cleanup_callback.forget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<div class="modal-backdrop print-preview-modal-backdrop" onclick={backdrop_click}>
|
|
||||||
<div class="modal-content print-preview-modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>{"Print Preview"}</h3>
|
|
||||||
<button class="modal-close" onclick={close_modal.clone()}>{"×"}</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body print-preview-body">
|
|
||||||
<div class="print-preview-controls">
|
|
||||||
{
|
|
||||||
if props.view_mode == ViewMode::Week {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="start-hour">{"Start Hour:"}</label>
|
|
||||||
<select id="start-hour" onchange={on_start_hour_change}>
|
|
||||||
{
|
|
||||||
(0..24).map(|hour| {
|
|
||||||
html! {
|
|
||||||
<option value={hour.to_string()} selected={hour == *start_hour}>
|
|
||||||
{format_hour(hour)}
|
|
||||||
</option>
|
|
||||||
}
|
|
||||||
}).collect::<Html>()
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="end-hour">{"End Hour:"}</label>
|
|
||||||
<select id="end-hour" onchange={on_end_hour_change}>
|
|
||||||
{
|
|
||||||
(1..=24).map(|hour| {
|
|
||||||
html! {
|
|
||||||
<option value={hour.to_string()} selected={hour == *end_hour}>
|
|
||||||
{if hour == 24 { "12 AM".to_string() } else { format_hour(hour) }}
|
|
||||||
</option>
|
|
||||||
}
|
|
||||||
}).collect::<Html>()
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="hour-range-info">
|
|
||||||
{format!("Will print from {} to {}",
|
|
||||||
format_hour(*start_hour),
|
|
||||||
if *end_hour == 24 { "12 AM".to_string() } else { format_hour(*end_hour) }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {
|
|
||||||
<div class="month-info">
|
|
||||||
{"Will print entire month view"}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<div class="zoom-display-info">
|
|
||||||
<label>{"Zoom: "}</label>
|
|
||||||
<span>{format!("{}%", (*zoom_level * 100.0) as i32)}</span>
|
|
||||||
<span class="zoom-hint">{"(scroll to zoom)"}</span>
|
|
||||||
</div>
|
|
||||||
<div class="preview-actions">
|
|
||||||
<button class="btn-primary" onclick={on_print}>{"Print"}</button>
|
|
||||||
<button class="btn-secondary" onclick={close_modal}>{"Cancel"}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="print-preview-display" onwheel={{
|
|
||||||
let zoom_level = zoom_level.clone();
|
|
||||||
Callback::from(move |e: WheelEvent| {
|
|
||||||
e.prevent_default(); // Prevent page scroll
|
|
||||||
let delta_y = e.delta_y();
|
|
||||||
let zoom_change = if delta_y < 0.0 { 1.1 } else { 1.0 / 1.1 };
|
|
||||||
let new_zoom = (*zoom_level * zoom_change).clamp(0.2, 1.5);
|
|
||||||
zoom_level.set(new_zoom);
|
|
||||||
})
|
|
||||||
}}>
|
|
||||||
<div class="print-preview-paper"
|
|
||||||
data-start-hour={start_hour.to_string()}
|
|
||||||
data-end-hour={end_hour.to_string()}
|
|
||||||
style={format!("--print-start-hour: {}; --print-end-hour: {}; transform: scale({}); transform-origin: top center;", *start_hour, *end_hour, *zoom_level)}>
|
|
||||||
<div class="print-preview-content">
|
|
||||||
{
|
|
||||||
match props.view_mode {
|
|
||||||
ViewMode::Week => html! {
|
|
||||||
<WeekView
|
|
||||||
key={format!("week-preview-{}-{}", *start_hour, *end_hour)}
|
|
||||||
current_date={props.current_date}
|
|
||||||
today={props.today}
|
|
||||||
events={props.events.clone()}
|
|
||||||
on_event_click={Callback::noop()}
|
|
||||||
user_info={props.user_info.clone()}
|
|
||||||
external_calendars={props.external_calendars.clone()}
|
|
||||||
time_increment={props.time_increment}
|
|
||||||
/>
|
|
||||||
},
|
|
||||||
ViewMode::Month => html! {
|
|
||||||
<MonthView
|
|
||||||
key={format!("month-preview-{}-{}", *start_hour, *end_hour)}
|
|
||||||
current_month={props.current_date}
|
|
||||||
selected_date={Some(props.selected_date)}
|
|
||||||
today={props.today}
|
|
||||||
events={props.events.clone()}
|
|
||||||
on_day_select={None::<Callback<NaiveDate>>}
|
|
||||||
on_event_click={Callback::noop()}
|
|
||||||
user_info={props.user_info.clone()}
|
|
||||||
external_calendars={props.external_calendars.clone()}
|
|
||||||
/>
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -438,13 +438,13 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
// Time labels
|
// Time labels
|
||||||
<div class={classes!("time-labels", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
<div class={classes!("time-labels", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||||
{
|
{
|
||||||
time_labels.iter().enumerate().map(|(hour, time)| {
|
time_labels.iter().map(|time| {
|
||||||
let is_quarter_mode = props.time_increment == 15;
|
let is_quarter_mode = props.time_increment == 15;
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!(
|
<div class={classes!(
|
||||||
"time-label",
|
"time-label",
|
||||||
if is_quarter_mode { Some("quarter-mode") } else { None }
|
if is_quarter_mode { Some("quarter-mode") } else { None }
|
||||||
)} data-hour={hour.to_string()}>
|
)}>
|
||||||
{time}
|
{time}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -701,10 +701,10 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
>
|
>
|
||||||
// Time slot backgrounds - 24 hour slots to represent full day
|
// Time slot backgrounds - 24 hour slots to represent full day
|
||||||
{
|
{
|
||||||
(0..24).map(|hour| {
|
(0..24).map(|_hour| {
|
||||||
let slots_per_hour = 60 / props.time_increment;
|
let slots_per_hour = 60 / props.time_increment;
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!("time-slot", if props.time_increment == 15 { Some("quarter-mode") } else { None })} data-hour={hour.to_string()}>
|
<div class={classes!("time-slot", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||||
{
|
{
|
||||||
(0..slots_per_hour).map(|_slot| {
|
(0..slots_per_hour).map(|_slot| {
|
||||||
let slot_class = if props.time_increment == 15 {
|
let slot_class = if props.time_increment == 15 {
|
||||||
|
|||||||
Reference in New Issue
Block a user