Implement dynamic 15-minute time grid density and remove final boundary
- Scale time grid height dynamically based on time increment (1530px/2970px) - Add quarter-mode CSS classes for 15-minute blocks (30px each, same as 30-min blocks) - Update pixel-to-time conversion functions with 2px:1min scaling in 15-min mode - Generate correct number of time slots (4 per hour in 15-min mode) - Remove unnecessary final boundary time label and related CSS - Fix CSS grid layout by removing malformed CSS syntax - All time-related containers scale properly between 30-minute and 15-minute modes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -95,8 +95,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
"#3B82F6".to_string()
|
"#3B82F6".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate time labels - 24 hours plus the final midnight boundary
|
// Generate time labels - 24 hours
|
||||||
let mut time_labels: Vec<String> = (0..24)
|
let time_labels: Vec<String> = (0..24)
|
||||||
.map(|hour| {
|
.map(|hour| {
|
||||||
if hour == 0 {
|
if hour == 0 {
|
||||||
"12 AM".to_string()
|
"12 AM".to_string()
|
||||||
@@ -110,9 +110,6 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
})
|
})
|
||||||
.collect();
|
.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
|
// Handlers for recurring event modification modal
|
||||||
let on_recurring_choice = {
|
let on_recurring_choice = {
|
||||||
let pending_recurring_edit = pending_recurring_edit.clone();
|
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
|
// Scrollable content area with time grid
|
||||||
<div class="week-content">
|
<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
|
// 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)| {
|
time_labels.iter().map(|time| {
|
||||||
let is_final = index == time_labels.len() - 1;
|
let is_quarter_mode = props.time_increment == 15;
|
||||||
html! {
|
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}
|
{time}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -404,12 +404,12 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Day columns
|
// 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)| {
|
week_days.iter().enumerate().map(|(_column_index, date)| {
|
||||||
let is_today = *date == props.today;
|
let is_today = *date == props.today;
|
||||||
let day_events = props.events.get(date).cloned().unwrap_or_default();
|
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
|
// Drag event handlers
|
||||||
let drag_state_clone = drag_state.clone();
|
let drag_state_clone = drag_state.clone();
|
||||||
@@ -500,8 +500,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
match ¤t_drag.drag_type {
|
match ¤t_drag.drag_type {
|
||||||
DragType::CreateEvent => {
|
DragType::CreateEvent => {
|
||||||
// Calculate start and end times
|
// Calculate start and end times
|
||||||
let start_time = pixels_to_time(current_drag.start_y);
|
let start_time = pixels_to_time(current_drag.start_y, time_increment);
|
||||||
let end_time = pixels_to_time(current_drag.current_y);
|
let end_time = pixels_to_time(current_drag.current_y, time_increment);
|
||||||
|
|
||||||
// Ensure start is before end
|
// Ensure start is before end
|
||||||
let (actual_start, actual_end) = if start_time <= end_time {
|
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;
|
let unsnapped_position = current_drag.current_y - current_drag.offset_y;
|
||||||
// Snap the final position to maintain time increment alignment
|
// Snap the final position to maintain time increment alignment
|
||||||
let event_top_position = snap_to_increment(unsnapped_position, time_increment);
|
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
|
// Calculate duration from original event
|
||||||
let original_duration = if let Some(end) = event.dtend {
|
let original_duration = if let Some(end) = event.dtend {
|
||||||
@@ -558,7 +558,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
},
|
},
|
||||||
DragType::ResizeEventStart(event) => {
|
DragType::ResizeEventStart(event) => {
|
||||||
// Calculate new start time based on drag position
|
// 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
|
// Keep the original end time
|
||||||
let original_end = if let Some(end) = event.dtend {
|
let original_end = if let Some(end) = event.dtend {
|
||||||
@@ -594,7 +594,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
},
|
},
|
||||||
DragType::ResizeEventEnd(event) => {
|
DragType::ResizeEventEnd(event) => {
|
||||||
// Calculate new end time based on drag position
|
// 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
|
// Keep the original start time
|
||||||
let original_start = event.dtstart.with_timezone(&chrono::Local).naive_local();
|
let original_start = event.dtstart.with_timezone(&chrono::Local).naive_local();
|
||||||
@@ -643,7 +643,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
class={classes!(
|
class={classes!(
|
||||||
"week-day-column",
|
"week-day-column",
|
||||||
if is_today { Some("today") } else { None },
|
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}
|
{onmousedown}
|
||||||
{onmousemove}
|
{onmousemove}
|
||||||
@@ -652,10 +653,21 @@ 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;
|
||||||
html! {
|
html! {
|
||||||
<div class="time-slot">
|
<div class={classes!("time-slot", if props.time_increment == 15 { Some("quarter-mode") } else { None })}>
|
||||||
<div class="time-slot-half"></div>
|
{
|
||||||
<div class="time-slot-half"></div>
|
(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>
|
</div>
|
||||||
}
|
}
|
||||||
}).collect::<Html>()
|
}).collect::<Html>()
|
||||||
@@ -665,7 +677,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
<div class="events-container">
|
<div class="events-container">
|
||||||
{
|
{
|
||||||
day_events.iter().enumerate().filter_map(|(event_idx, event)| {
|
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)
|
// Skip all-day events (they're rendered in the header)
|
||||||
if is_all_day {
|
if is_all_day {
|
||||||
@@ -693,7 +705,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
let drag_state = drag_state.clone();
|
let drag_state = drag_state.clone();
|
||||||
let event_for_drag = event.clone();
|
let event_for_drag = event.clone();
|
||||||
let date_for_drag = *date;
|
let date_for_drag = *date;
|
||||||
let _time_increment = props.time_increment;
|
let time_increment = props.time_increment;
|
||||||
Callback::from(move |e: MouseEvent| {
|
Callback::from(move |e: MouseEvent| {
|
||||||
e.stop_propagation(); // Prevent drag-to-create from starting on event clicks
|
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 };
|
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
|
// 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;
|
let event_start_pixels = event_start_pixels as f64;
|
||||||
|
|
||||||
// Convert click position to day column coordinates
|
// Convert click position to day column coordinates
|
||||||
@@ -939,8 +951,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
let height = (drag.current_y - drag.start_y).abs().max(20.0);
|
let height = (drag.current_y - drag.start_y).abs().max(20.0);
|
||||||
|
|
||||||
// Convert pixels to times for display
|
// Convert pixels to times for display
|
||||||
let start_time = pixels_to_time(start_y);
|
let start_time = pixels_to_time(start_y, props.time_increment);
|
||||||
let end_time = pixels_to_time(end_y);
|
let end_time = pixels_to_time(end_y, props.time_increment);
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div
|
<div
|
||||||
@@ -956,7 +968,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
let unsnapped_position = drag.current_y - drag.offset_y;
|
let unsnapped_position = drag.current_y - drag.offset_y;
|
||||||
// Snap the final position to maintain time increment alignment
|
// Snap the final position to maintain time increment alignment
|
||||||
let preview_position = snap_to_increment(unsnapped_position, props.time_increment);
|
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 {
|
let original_duration = if let Some(end) = event.dtend {
|
||||||
end.signed_duration_since(event.dtstart)
|
end.signed_duration_since(event.dtstart)
|
||||||
} else {
|
} else {
|
||||||
@@ -979,7 +991,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
},
|
},
|
||||||
DragType::ResizeEventStart(event) => {
|
DragType::ResizeEventStart(event) => {
|
||||||
// Show the event being resized from the start
|
// 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 {
|
let original_end = if let Some(end) = event.dtend {
|
||||||
end.with_timezone(&chrono::Local).naive_local()
|
end.with_timezone(&chrono::Local).naive_local()
|
||||||
} else {
|
} else {
|
||||||
@@ -987,7 +999,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Calculate positions for the preview
|
// 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_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);
|
let original_end_pixels = original_start_pixels + (original_duration.num_minutes() as f32);
|
||||||
|
|
||||||
@@ -1008,11 +1020,11 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
|||||||
},
|
},
|
||||||
DragType::ResizeEventEnd(event) => {
|
DragType::ResizeEventEnd(event) => {
|
||||||
// Show the event being resized from the end
|
// 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();
|
let original_start = event.dtstart.with_timezone(&chrono::Local).naive_local();
|
||||||
|
|
||||||
// Calculate positions for the preview
|
// 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_end_pixels = drag.current_y;
|
||||||
let new_height = (new_end_pixels - original_start_pixels as f64).max(20.0);
|
let new_height = (new_end_pixels - original_start_pixels as f64).max(20.0);
|
||||||
@@ -1089,22 +1101,25 @@ fn get_weekday_name(weekday: Weekday) -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the pixel position of an event based on its time
|
// Calculate the pixel position of an event based on its time
|
||||||
// Each hour is 60px, so we convert time to pixels
|
// Snap pixel position based on time increment and grid scaling
|
||||||
// Snap pixel position to 15-minute increments (15px = 15 minutes since 60px = 60 minutes)
|
// 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 {
|
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
|
(pixels / increment_px).round() * increment_px
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert pixel position to time (inverse of time to pixels)
|
// Convert pixel position to time (inverse of time to pixels)
|
||||||
fn pixels_to_time(pixels: f64) -> NaiveTime {
|
fn pixels_to_time(pixels: f64, time_increment: u32) -> NaiveTime {
|
||||||
// Since 60px = 1 hour, pixels directly represent minutes
|
let pixels_per_minute = if time_increment == 15 { 2.0 } else { 1.0 };
|
||||||
let total_minutes = pixels; // 1px = 1 minute
|
let total_minutes = pixels / pixels_per_minute;
|
||||||
let hours = (total_minutes / 60.0) as u32;
|
let hours = (total_minutes / 60.0) as u32;
|
||||||
let minutes = (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
|
// Handle midnight boundary - check against scaled boundary
|
||||||
if total_minutes >= 1440.0 {
|
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();
|
return NaiveTime::from_hms_opt(0, 0, 0).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1115,7 +1130,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())
|
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
|
// Convert UTC times to local time for display
|
||||||
let local_start = event.dtstart.with_timezone(&Local);
|
let local_start = event.dtstart.with_timezone(&Local);
|
||||||
let event_date = local_start.date_naive();
|
let event_date = local_start.date_naive();
|
||||||
@@ -1138,7 +1153,8 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool)
|
|||||||
// Calculate start position in pixels from midnight
|
// Calculate start position in pixels from midnight
|
||||||
let start_hour = local_start.hour() as f32;
|
let start_hour = local_start.hour() as f32;
|
||||||
let start_minute = local_start.minute() 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
|
// Calculate duration and height
|
||||||
let duration_pixels = if let Some(end) = event.dtend {
|
let duration_pixels = if let Some(end) = event.dtend {
|
||||||
@@ -1147,16 +1163,17 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool)
|
|||||||
|
|
||||||
// Handle events that span multiple days by capping at midnight
|
// Handle events that span multiple days by capping at midnight
|
||||||
if end_date > date {
|
if end_date > date {
|
||||||
// Event continues past midnight, cap at 24:00 (1440px)
|
// Event continues past midnight, cap at 24:00
|
||||||
1440.0 - start_pixels
|
let max_pixels = 24.0 * pixels_per_hour;
|
||||||
|
max_pixels - start_pixels
|
||||||
} else {
|
} else {
|
||||||
let end_hour = local_end.hour() as f32;
|
let end_hour = local_end.hour() as f32;
|
||||||
let end_minute = local_end.minute() 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
|
(end_pixels - start_pixels).max(20.0) // Minimum 20px height
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
(start_pixels, duration_pixels, false) // is_all_day = false
|
||||||
@@ -1183,13 +1200,13 @@ fn events_overlap(event1: &VEvent, event2: &VEvent) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate layout columns for overlapping events
|
// 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
|
// Filter and sort events that should appear on this date
|
||||||
let mut day_events: Vec<_> = events.iter()
|
let mut day_events: Vec<_> = events.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(idx, event)| {
|
.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 local_start = event.dtstart.with_timezone(&Local);
|
||||||
let event_date = local_start.date_naive();
|
let event_date = local_start.date_naive();
|
||||||
if event_date == date ||
|
if event_date == date ||
|
||||||
|
|||||||
@@ -733,7 +733,11 @@ body {
|
|||||||
.time-grid {
|
.time-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 80px 1fr;
|
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 */
|
/* Time Labels */
|
||||||
@@ -743,9 +747,15 @@ body {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 5;
|
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 {
|
.time-label {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -758,24 +768,31 @@ body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-label.final-boundary {
|
/* Time label height for 15-minute mode - double height */
|
||||||
height: 60px; /* Keep same height but this marks the end boundary */
|
.time-label.quarter-mode {
|
||||||
border-bottom: 2px solid #e9ecef; /* Stronger border to show day end */
|
height: 120px;
|
||||||
color: #999; /* Lighter color to indicate it's the boundary */
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Week Days Grid */
|
/* Week Days Grid */
|
||||||
.week-days-grid {
|
.week-days-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
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 {
|
.week-day-column {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-right: 1px solid var(--time-label-border, #e9ecef);
|
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 {
|
.week-day-column:last-child {
|
||||||
@@ -788,12 +805,16 @@ body {
|
|||||||
|
|
||||||
/* Time Slots */
|
/* Time Slots */
|
||||||
.time-slot {
|
.time-slot {
|
||||||
height: 60px;
|
height: 60px; /* 30-minute mode: 2 slots × 30px = 60px */
|
||||||
border-bottom: 1px solid var(--calendar-border, #f0f0f0);
|
border-bottom: 1px solid var(--calendar-border, #f0f0f0);
|
||||||
position: relative;
|
position: relative;
|
||||||
pointer-events: none; /* Don't capture mouse events */
|
pointer-events: none; /* Don't capture mouse events */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-slot.quarter-mode {
|
||||||
|
height: 120px; /* 15-minute mode: 4 slots × 30px = 120px */
|
||||||
|
}
|
||||||
|
|
||||||
.time-slot-half {
|
.time-slot-half {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-bottom: 1px dotted var(--calendar-border, #f5f5f5);
|
border-bottom: 1px dotted var(--calendar-border, #f5f5f5);
|
||||||
@@ -804,13 +825,17 @@ body {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-slot.boundary-slot {
|
.time-slot-quarter {
|
||||||
height: 60px; /* Match the final time label height */
|
height: 30px;
|
||||||
border-bottom: 2px solid #e9ecef; /* Strong border to match final boundary */
|
border-bottom: 1px dotted var(--calendar-border-light, #f8f8f8);
|
||||||
background: rgba(0,0,0,0.02); /* Slightly different background to indicate boundary */
|
|
||||||
pointer-events: none; /* Don't capture mouse events */
|
pointer-events: none; /* Don't capture mouse events */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-slot-quarter:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Events Container */
|
/* Events Container */
|
||||||
.events-container {
|
.events-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -648,6 +648,16 @@ body {
|
|||||||
border-bottom: none;
|
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 {
|
.time-slot.boundary-slot {
|
||||||
height: 60px; /* Match the final time label height */
|
height: 60px; /* Match the final time label height */
|
||||||
border-bottom: 2px solid #e9ecef; /* Strong border to match final boundary */
|
border-bottom: 2px solid #e9ecef; /* Strong border to match final boundary */
|
||||||
|
|||||||
Reference in New Issue
Block a user