Compare commits
4 Commits
419cb3d790
...
c0bdd3d8c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0bdd3d8c2 | ||
|
|
2b98c4d229 | ||
|
|
ceae654a39 | ||
|
|
fb28fa95c9 |
@@ -95,8 +95,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
"#3B82F6".to_string()
|
||||
};
|
||||
|
||||
// Generate time labels - 24 hours plus the final midnight boundary
|
||||
let mut time_labels: Vec<String> = (0..24)
|
||||
// Generate time labels - 24 hours
|
||||
let time_labels: Vec<String> = (0..24)
|
||||
.map(|hour| {
|
||||
if hour == 0 {
|
||||
"12 AM".to_string()
|
||||
@@ -110,9 +110,6 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Add the final midnight boundary to show where the day ends
|
||||
time_labels.push("12 AM".to_string());
|
||||
|
||||
// Handlers for recurring event modification modal
|
||||
let on_recurring_choice = {
|
||||
let pending_recurring_edit = pending_recurring_edit.clone();
|
||||
@@ -388,14 +385,17 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
|
||||
// Scrollable content area with time grid
|
||||
<div class="week-content">
|
||||
<div class="time-grid">
|
||||
<div class={classes!("time-grid", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||
// Time labels
|
||||
<div class="time-labels">
|
||||
<div class={classes!("time-labels", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||
{
|
||||
time_labels.iter().enumerate().map(|(index, time)| {
|
||||
let is_final = index == time_labels.len() - 1;
|
||||
time_labels.iter().map(|time| {
|
||||
let is_quarter_mode = props.time_increment == 15;
|
||||
html! {
|
||||
<div class={classes!("time-label", if is_final { Some("final-boundary") } else { None })}>
|
||||
<div class={classes!(
|
||||
"time-label",
|
||||
if is_quarter_mode { Some("quarter-mode") } else { None }
|
||||
)}>
|
||||
{time}
|
||||
</div>
|
||||
}
|
||||
@@ -404,12 +404,12 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
</div>
|
||||
|
||||
// Day columns
|
||||
<div class="week-days-grid">
|
||||
<div class={classes!("week-days-grid", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||
{
|
||||
week_days.iter().enumerate().map(|(_column_index, date)| {
|
||||
let is_today = *date == props.today;
|
||||
let day_events = props.events.get(date).cloned().unwrap_or_default();
|
||||
let event_layouts = calculate_event_layout(&day_events, *date);
|
||||
let event_layouts = calculate_event_layout(&day_events, *date, props.time_increment);
|
||||
|
||||
// Drag event handlers
|
||||
let drag_state_clone = drag_state.clone();
|
||||
@@ -500,8 +500,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
match ¤t_drag.drag_type {
|
||||
DragType::CreateEvent => {
|
||||
// Calculate start and end times
|
||||
let start_time = pixels_to_time(current_drag.start_y);
|
||||
let end_time = pixels_to_time(current_drag.current_y);
|
||||
let start_time = pixels_to_time(current_drag.start_y, time_increment);
|
||||
let end_time = pixels_to_time(current_drag.current_y, time_increment);
|
||||
|
||||
// Ensure start is before end
|
||||
let (actual_start, actual_end) = if start_time <= end_time {
|
||||
@@ -529,7 +529,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let unsnapped_position = current_drag.current_y - current_drag.offset_y;
|
||||
// Snap the final position to maintain time increment alignment
|
||||
let event_top_position = snap_to_increment(unsnapped_position, time_increment);
|
||||
let new_start_time = pixels_to_time(event_top_position);
|
||||
let new_start_time = pixels_to_time(event_top_position, time_increment);
|
||||
|
||||
// Calculate duration from original event
|
||||
let original_duration = if let Some(end) = event.dtend {
|
||||
@@ -558,7 +558,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
},
|
||||
DragType::ResizeEventStart(event) => {
|
||||
// Calculate new start time based on drag position
|
||||
let new_start_time = pixels_to_time(current_drag.current_y);
|
||||
let new_start_time = pixels_to_time(current_drag.current_y, time_increment);
|
||||
|
||||
// Keep the original end time
|
||||
let original_end = if let Some(end) = event.dtend {
|
||||
@@ -594,7 +594,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
},
|
||||
DragType::ResizeEventEnd(event) => {
|
||||
// Calculate new end time based on drag position
|
||||
let new_end_time = pixels_to_time(current_drag.current_y);
|
||||
let new_end_time = pixels_to_time(current_drag.current_y, time_increment);
|
||||
|
||||
// Keep the original start time
|
||||
let original_start = event.dtstart.with_timezone(&chrono::Local).naive_local();
|
||||
@@ -643,7 +643,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
class={classes!(
|
||||
"week-day-column",
|
||||
if is_today { Some("today") } else { None },
|
||||
if is_creating_event { Some("creating-event") } else { None }
|
||||
if is_creating_event { Some("creating-event") } else { None },
|
||||
if props.time_increment == 15 { Some("quarter-mode") } else { None }
|
||||
)}
|
||||
{onmousedown}
|
||||
{onmousemove}
|
||||
@@ -652,10 +653,21 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
// Time slot backgrounds - 24 hour slots to represent full day
|
||||
{
|
||||
(0..24).map(|_hour| {
|
||||
let slots_per_hour = 60 / props.time_increment;
|
||||
html! {
|
||||
<div class="time-slot">
|
||||
<div class="time-slot-half"></div>
|
||||
<div class="time-slot-half"></div>
|
||||
<div class={classes!("time-slot", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||
{
|
||||
(0..slots_per_hour).map(|_slot| {
|
||||
let slot_class = if props.time_increment == 15 {
|
||||
"time-slot-quarter"
|
||||
} else {
|
||||
"time-slot-half"
|
||||
};
|
||||
html! {
|
||||
<div class={slot_class}></div>
|
||||
}
|
||||
}).collect::<Html>()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}).collect::<Html>()
|
||||
@@ -665,7 +677,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
<div class="events-container">
|
||||
{
|
||||
day_events.iter().enumerate().filter_map(|(event_idx, event)| {
|
||||
let (start_pixels, duration_pixels, is_all_day) = calculate_event_position(event, *date);
|
||||
let (start_pixels, duration_pixels, is_all_day) = calculate_event_position(event, *date, props.time_increment);
|
||||
|
||||
// Skip all-day events (they're rendered in the header)
|
||||
if is_all_day {
|
||||
@@ -693,7 +705,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let drag_state = drag_state.clone();
|
||||
let event_for_drag = event.clone();
|
||||
let date_for_drag = *date;
|
||||
let _time_increment = props.time_increment;
|
||||
let time_increment = props.time_increment;
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
e.stop_propagation(); // Prevent drag-to-create from starting on event clicks
|
||||
|
||||
@@ -707,7 +719,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let click_y_relative = if click_y_relative > 0.0 { click_y_relative } else { e.offset_y() as f64 };
|
||||
|
||||
// Get event's current position in day column coordinates
|
||||
let (event_start_pixels, _, _) = calculate_event_position(&event_for_drag, date_for_drag);
|
||||
let (event_start_pixels, _, _) = calculate_event_position(&event_for_drag, date_for_drag, time_increment);
|
||||
let event_start_pixels = event_start_pixels as f64;
|
||||
|
||||
// Convert click position to day column coordinates
|
||||
@@ -903,7 +915,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
// Event content
|
||||
<div class="event-content">
|
||||
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
|
||||
{if !is_all_day {
|
||||
{if !is_all_day && duration_pixels > 30.0 {
|
||||
html! { <div class="event-time">{time_display}</div> }
|
||||
} else {
|
||||
html! {}
|
||||
@@ -939,8 +951,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let height = (drag.current_y - drag.start_y).abs().max(20.0);
|
||||
|
||||
// Convert pixels to times for display
|
||||
let start_time = pixels_to_time(start_y);
|
||||
let end_time = pixels_to_time(end_y);
|
||||
let start_time = pixels_to_time(start_y, props.time_increment);
|
||||
let end_time = pixels_to_time(end_y, props.time_increment);
|
||||
|
||||
html! {
|
||||
<div
|
||||
@@ -956,7 +968,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let unsnapped_position = drag.current_y - drag.offset_y;
|
||||
// Snap the final position to maintain time increment alignment
|
||||
let preview_position = snap_to_increment(unsnapped_position, props.time_increment);
|
||||
let new_start_time = pixels_to_time(preview_position);
|
||||
let new_start_time = pixels_to_time(preview_position, props.time_increment);
|
||||
let original_duration = if let Some(end) = event.dtend {
|
||||
end.signed_duration_since(event.dtstart)
|
||||
} else {
|
||||
@@ -973,13 +985,17 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
style={format!("top: {}px; height: {}px; background-color: {}; opacity: 0.7;", preview_position, duration_pixels, event_color)}
|
||||
>
|
||||
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
|
||||
<div class="event-time">{format!("{} - {}", new_start_time.format("%I:%M %p"), new_end_time.format("%I:%M %p"))}</div>
|
||||
{if duration_pixels > 30.0 {
|
||||
html! { <div class="event-time">{format!("{} - {}", new_start_time.format("%I:%M %p"), new_end_time.format("%I:%M %p"))}</div> }
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
},
|
||||
DragType::ResizeEventStart(event) => {
|
||||
// Show the event being resized from the start
|
||||
let new_start_time = pixels_to_time(drag.current_y);
|
||||
let new_start_time = pixels_to_time(drag.current_y, props.time_increment);
|
||||
let original_end = if let Some(end) = event.dtend {
|
||||
end.with_timezone(&chrono::Local).naive_local()
|
||||
} else {
|
||||
@@ -987,7 +1003,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
};
|
||||
|
||||
// Calculate positions for the preview
|
||||
let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date);
|
||||
let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment);
|
||||
let original_duration = original_end.signed_duration_since(event.dtstart.with_timezone(&chrono::Local).naive_local());
|
||||
let original_end_pixels = original_start_pixels + (original_duration.num_minutes() as f32);
|
||||
|
||||
@@ -1002,17 +1018,21 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
style={format!("top: {}px; height: {}px; background-color: {}; opacity: 0.7;", new_start_pixels, new_height, event_color)}
|
||||
>
|
||||
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
|
||||
<div class="event-time">{format!("{} - {}", new_start_time.format("%I:%M %p"), original_end.time().format("%I:%M %p"))}</div>
|
||||
{if new_height > 30.0 {
|
||||
html! { <div class="event-time">{format!("{} - {}", new_start_time.format("%I:%M %p"), original_end.time().format("%I:%M %p"))}</div> }
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
},
|
||||
DragType::ResizeEventEnd(event) => {
|
||||
// Show the event being resized from the end
|
||||
let new_end_time = pixels_to_time(drag.current_y);
|
||||
let new_end_time = pixels_to_time(drag.current_y, props.time_increment);
|
||||
let original_start = event.dtstart.with_timezone(&chrono::Local).naive_local();
|
||||
|
||||
// Calculate positions for the preview
|
||||
let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date);
|
||||
let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment);
|
||||
|
||||
let new_end_pixels = drag.current_y;
|
||||
let new_height = (new_end_pixels - original_start_pixels as f64).max(20.0);
|
||||
@@ -1025,7 +1045,11 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
style={format!("top: {}px; height: {}px; background-color: {}; opacity: 0.7;", original_start_pixels, new_height, event_color)}
|
||||
>
|
||||
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
|
||||
<div class="event-time">{format!("{} - {}", original_start.time().format("%I:%M %p"), new_end_time.format("%I:%M %p"))}</div>
|
||||
{if new_height > 30.0 {
|
||||
html! { <div class="event-time">{format!("{} - {}", original_start.time().format("%I:%M %p"), new_end_time.format("%I:%M %p"))}</div> }
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1089,22 +1113,25 @@ fn get_weekday_name(weekday: Weekday) -> &'static str {
|
||||
}
|
||||
|
||||
// Calculate the pixel position of an event based on its time
|
||||
// Each hour is 60px, so we convert time to pixels
|
||||
// Snap pixel position to 15-minute increments (15px = 15 minutes since 60px = 60 minutes)
|
||||
// Snap pixel position based on time increment and grid scaling
|
||||
// In 30-minute mode: 60px per hour (1px = 1 minute)
|
||||
// In 15-minute mode: 120px per hour (2px = 1 minute)
|
||||
fn snap_to_increment(pixels: f64, increment: u32) -> f64 {
|
||||
let increment_px = increment as f64; // Convert to pixels (1px = 1 minute)
|
||||
let pixels_per_minute = if increment == 15 { 2.0 } else { 1.0 };
|
||||
let increment_px = increment as f64 * pixels_per_minute;
|
||||
(pixels / increment_px).round() * increment_px
|
||||
}
|
||||
|
||||
// Convert pixel position to time (inverse of time to pixels)
|
||||
fn pixels_to_time(pixels: f64) -> NaiveTime {
|
||||
// Since 60px = 1 hour, pixels directly represent minutes
|
||||
let total_minutes = pixels; // 1px = 1 minute
|
||||
fn pixels_to_time(pixels: f64, time_increment: u32) -> NaiveTime {
|
||||
let pixels_per_minute = if time_increment == 15 { 2.0 } else { 1.0 };
|
||||
let total_minutes = pixels / pixels_per_minute;
|
||||
let hours = (total_minutes / 60.0) as u32;
|
||||
let minutes = (total_minutes % 60.0) as u32;
|
||||
|
||||
// Handle midnight boundary - if we're at exactly 1440 pixels (24:00), return midnight
|
||||
if total_minutes >= 1440.0 {
|
||||
// Handle midnight boundary - check against scaled boundary
|
||||
let max_pixels = 1440.0 * pixels_per_minute; // 24 hours in pixels
|
||||
if pixels >= max_pixels {
|
||||
return NaiveTime::from_hms_opt(0, 0, 0).unwrap();
|
||||
}
|
||||
|
||||
@@ -1115,7 +1142,7 @@ fn pixels_to_time(pixels: f64) -> NaiveTime {
|
||||
NaiveTime::from_hms_opt(hours, minutes, 0).unwrap_or(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
|
||||
}
|
||||
|
||||
fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool) {
|
||||
fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32) -> (f32, f32, bool) {
|
||||
// Convert UTC times to local time for display
|
||||
let local_start = event.dtstart.with_timezone(&Local);
|
||||
let event_date = local_start.date_naive();
|
||||
@@ -1138,7 +1165,8 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool)
|
||||
// Calculate start position in pixels from midnight
|
||||
let start_hour = local_start.hour() as f32;
|
||||
let start_minute = local_start.minute() as f32;
|
||||
let start_pixels = (start_hour + start_minute / 60.0) * 60.0; // 60px per hour
|
||||
let pixels_per_hour = if time_increment == 15 { 120.0 } else { 60.0 };
|
||||
let start_pixels = (start_hour + start_minute / 60.0) * pixels_per_hour;
|
||||
|
||||
// Calculate duration and height
|
||||
let duration_pixels = if let Some(end) = event.dtend {
|
||||
@@ -1147,16 +1175,17 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool)
|
||||
|
||||
// Handle events that span multiple days by capping at midnight
|
||||
if end_date > date {
|
||||
// Event continues past midnight, cap at 24:00 (1440px)
|
||||
1440.0 - start_pixels
|
||||
// Event continues past midnight, cap at 24:00
|
||||
let max_pixels = 24.0 * pixels_per_hour;
|
||||
max_pixels - start_pixels
|
||||
} else {
|
||||
let end_hour = local_end.hour() as f32;
|
||||
let end_minute = local_end.minute() as f32;
|
||||
let end_pixels = (end_hour + end_minute / 60.0) * 60.0;
|
||||
let end_pixels = (end_hour + end_minute / 60.0) * pixels_per_hour;
|
||||
(end_pixels - start_pixels).max(20.0) // Minimum 20px height
|
||||
}
|
||||
} else {
|
||||
60.0 // Default 1 hour if no end time
|
||||
pixels_per_hour // Default 1 hour if no end time
|
||||
};
|
||||
|
||||
(start_pixels, duration_pixels, false) // is_all_day = false
|
||||
@@ -1183,13 +1212,13 @@ fn events_overlap(event1: &VEvent, event2: &VEvent) -> bool {
|
||||
}
|
||||
|
||||
// Calculate layout columns for overlapping events
|
||||
fn calculate_event_layout(events: &[VEvent], date: NaiveDate) -> Vec<(usize, usize)> {
|
||||
fn calculate_event_layout(events: &[VEvent], date: NaiveDate, time_increment: u32) -> Vec<(usize, usize)> {
|
||||
|
||||
// Filter and sort events that should appear on this date
|
||||
let mut day_events: Vec<_> = events.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, event)| {
|
||||
let (_, _, _) = calculate_event_position(event, date);
|
||||
let (_, _, _) = calculate_event_position(event, date, time_increment);
|
||||
let local_start = event.dtstart.with_timezone(&Local);
|
||||
let event_date = local_start.date_naive();
|
||||
if event_date == date ||
|
||||
|
||||
@@ -271,8 +271,8 @@ impl CalendarService {
|
||||
pub fn expand_recurring_events(events: Vec<VEvent>) -> Vec<VEvent> {
|
||||
let mut expanded_events = Vec::new();
|
||||
let today = chrono::Utc::now().date_naive();
|
||||
let start_range = today - Duration::days(30); // Show past 30 days
|
||||
let end_range = today + Duration::days(365); // Show next 365 days
|
||||
let start_range = today - Duration::days(36500); // Show past 100 years (to catch any historical yearly events)
|
||||
let end_range = today + Duration::days(36500); // Show next 100 years
|
||||
|
||||
for event in events {
|
||||
if let Some(ref rrule) = event.rrule {
|
||||
|
||||
@@ -733,7 +733,11 @@ body {
|
||||
.time-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
min-height: 1530px;
|
||||
min-height: 1530px; /* 30-minute mode */
|
||||
}
|
||||
|
||||
.time-grid.quarter-mode {
|
||||
min-height: 2970px; /* 15-minute mode */
|
||||
}
|
||||
|
||||
/* Time Labels */
|
||||
@@ -743,9 +747,15 @@ body {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
min-height: 1440px; /* Match the time slots height */
|
||||
min-height: 1530px; /* 30-minute mode */
|
||||
}
|
||||
|
||||
/* Scale time labels container for 15-minute mode */
|
||||
.time-labels.quarter-mode {
|
||||
min-height: 2970px; /* 15-minute mode */
|
||||
}
|
||||
|
||||
/* Default time label height for 30-minute mode */
|
||||
.time-label {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
@@ -758,24 +768,31 @@ body {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-label.final-boundary {
|
||||
height: 60px; /* Keep same height but this marks the end boundary */
|
||||
border-bottom: 2px solid #e9ecef; /* Stronger border to show day end */
|
||||
color: #999; /* Lighter color to indicate it's the boundary */
|
||||
font-size: 0.7rem;
|
||||
/* Time label height for 15-minute mode - double height */
|
||||
.time-label.quarter-mode {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
|
||||
/* Week Days Grid */
|
||||
.week-days-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
min-height: 1440px; /* Ensure grid is tall enough for 24 time slots */
|
||||
min-height: 1530px; /* 30-minute mode */
|
||||
}
|
||||
|
||||
.week-days-grid.quarter-mode {
|
||||
min-height: 2970px; /* 15-minute mode */
|
||||
}
|
||||
|
||||
.week-day-column {
|
||||
position: relative;
|
||||
border-right: 1px solid var(--time-label-border, #e9ecef);
|
||||
min-height: 1440px; /* 24 time slots × 60px = 1440px total */
|
||||
min-height: 1530px; /* 30-minute mode */
|
||||
}
|
||||
|
||||
.week-day-column.quarter-mode {
|
||||
min-height: 2970px; /* 15-minute mode */
|
||||
}
|
||||
|
||||
.week-day-column:last-child {
|
||||
@@ -788,12 +805,16 @@ body {
|
||||
|
||||
/* Time Slots */
|
||||
.time-slot {
|
||||
height: 60px;
|
||||
height: 60px; /* 30-minute mode: 2 slots × 30px = 60px */
|
||||
border-bottom: 1px solid var(--calendar-border, #f0f0f0);
|
||||
position: relative;
|
||||
pointer-events: none; /* Don't capture mouse events */
|
||||
}
|
||||
|
||||
.time-slot.quarter-mode {
|
||||
height: 120px; /* 15-minute mode: 4 slots × 30px = 120px */
|
||||
}
|
||||
|
||||
.time-slot-half {
|
||||
height: 30px;
|
||||
border-bottom: 1px dotted var(--calendar-border, #f5f5f5);
|
||||
@@ -804,13 +825,17 @@ body {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.time-slot.boundary-slot {
|
||||
height: 60px; /* Match the final time label height */
|
||||
border-bottom: 2px solid #e9ecef; /* Strong border to match final boundary */
|
||||
background: rgba(0,0,0,0.02); /* Slightly different background to indicate boundary */
|
||||
.time-slot-quarter {
|
||||
height: 30px;
|
||||
border-bottom: 1px dotted var(--calendar-border-light, #f8f8f8);
|
||||
pointer-events: none; /* Don't capture mouse events */
|
||||
}
|
||||
|
||||
.time-slot-quarter:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
/* Events Container */
|
||||
.events-container {
|
||||
position: absolute;
|
||||
@@ -3242,6 +3267,7 @@ body {
|
||||
--accent-color: #667eea;
|
||||
--calendar-bg: white;
|
||||
--calendar-border: #f0f0f0;
|
||||
--calendar-border-light: #f8f8f8;
|
||||
--calendar-day-bg: white;
|
||||
--calendar-day-hover: #f8f9ff;
|
||||
--calendar-day-prev-next: #fafafa;
|
||||
@@ -3275,6 +3301,7 @@ body {
|
||||
--accent-color: #2196F3;
|
||||
--calendar-bg: #ffffff;
|
||||
--calendar-border: #bbdefb;
|
||||
--calendar-border-light: #e3f2fd;
|
||||
--calendar-day-bg: #ffffff;
|
||||
--calendar-day-hover: #e1f5fe;
|
||||
--calendar-day-prev-next: #f3f8ff;
|
||||
@@ -3317,6 +3344,7 @@ body {
|
||||
--accent-color: #4CAF50;
|
||||
--calendar-bg: #ffffff;
|
||||
--calendar-border: #c8e6c9;
|
||||
--calendar-border-light: #e8f5e8;
|
||||
--calendar-day-bg: #ffffff;
|
||||
--calendar-day-hover: #f1f8e9;
|
||||
--calendar-day-prev-next: #f9fbe7;
|
||||
@@ -3359,6 +3387,7 @@ body {
|
||||
--accent-color: #FF9800;
|
||||
--calendar-bg: #ffffff;
|
||||
--calendar-border: #ffe0b2;
|
||||
--calendar-border-light: #fff3e0;
|
||||
--calendar-day-bg: #ffffff;
|
||||
--calendar-day-hover: #fff8e1;
|
||||
--calendar-day-prev-next: #fffde7;
|
||||
@@ -3401,6 +3430,7 @@ body {
|
||||
--accent-color: #9C27B0;
|
||||
--calendar-bg: #ffffff;
|
||||
--calendar-border: #ce93d8;
|
||||
--calendar-border-light: #f3e5f5;
|
||||
--calendar-day-bg: #ffffff;
|
||||
--calendar-day-hover: #f8e9fc;
|
||||
--calendar-day-prev-next: #fce4ec;
|
||||
@@ -3443,6 +3473,7 @@ body {
|
||||
--accent-color: #666666;
|
||||
--calendar-bg: #1f1f1f;
|
||||
--calendar-border: #333333;
|
||||
--calendar-border-light: #2a2a2a;
|
||||
--calendar-day-bg: #1f1f1f;
|
||||
--calendar-day-hover: #2a2a2a;
|
||||
--calendar-day-prev-next: #1a1a1a;
|
||||
@@ -3495,6 +3526,7 @@ body {
|
||||
--accent-color: #E91E63;
|
||||
--calendar-bg: #ffffff;
|
||||
--calendar-border: #f8bbd9;
|
||||
--calendar-border-light: #fce4ec;
|
||||
--calendar-day-bg: #ffffff;
|
||||
--calendar-day-hover: #fdf2f8;
|
||||
--calendar-day-prev-next: #fef7ff;
|
||||
@@ -3537,6 +3569,7 @@ body {
|
||||
--accent-color: #26A69A;
|
||||
--calendar-bg: #ffffff;
|
||||
--calendar-border: #b2dfdb;
|
||||
--calendar-border-light: #e0f2f1;
|
||||
--calendar-day-bg: #ffffff;
|
||||
--calendar-day-hover: #f0fdfc;
|
||||
--calendar-day-prev-next: #f7ffff;
|
||||
|
||||
@@ -648,6 +648,16 @@ body {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.time-slot-quarter {
|
||||
height: 30px;
|
||||
border-bottom: 1px dotted var(--calendar-border-light, #f8f8f8);
|
||||
pointer-events: none; /* Don't capture mouse events */
|
||||
}
|
||||
|
||||
.time-slot-quarter:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.time-slot.boundary-slot {
|
||||
height: 60px; /* Match the final time label height */
|
||||
border-bottom: 2px solid #e9ecef; /* Strong border to match final boundary */
|
||||
|
||||
Reference in New Issue
Block a user