Some checks failed
Build and Push Docker Image / docker (push) Failing after 1m7s
Moved event fetching logic from CalendarView to Calendar component to properly use the visible date range instead of hardcoded current month. The Calendar component already tracks the current visible date through navigation, so events now load correctly for August and other months when navigating. Changes: - Calendar component now manages its own events state and fetching - Event fetching responds to current_date changes from navigation - CalendarView simplified to just render Calendar component - Fixed cargo fmt/clippy formatting across codebase 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
199 lines
7.5 KiB
Rust
199 lines
7.5 KiB
Rust
use yew::prelude::*;
|
||
|
||
#[derive(Properties, PartialEq)]
|
||
pub struct CreateCalendarModalProps {
|
||
pub is_open: bool,
|
||
pub on_close: Callback<()>,
|
||
pub on_create: Callback<(String, Option<String>, Option<String>)>, // name, description, color
|
||
pub available_colors: Vec<String>,
|
||
}
|
||
|
||
#[function_component]
|
||
pub fn CreateCalendarModal(props: &CreateCalendarModalProps) -> Html {
|
||
let calendar_name = use_state(|| String::new());
|
||
let description = use_state(|| String::new());
|
||
let selected_color = use_state(|| None::<String>);
|
||
let error_message = use_state(|| None::<String>);
|
||
let is_creating = use_state(|| false);
|
||
|
||
let on_name_change = {
|
||
let calendar_name = calendar_name.clone();
|
||
Callback::from(move |e: InputEvent| {
|
||
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
||
calendar_name.set(input.value());
|
||
})
|
||
};
|
||
|
||
let on_description_change = {
|
||
let description = description.clone();
|
||
Callback::from(move |e: InputEvent| {
|
||
let input: web_sys::HtmlTextAreaElement = e.target_unchecked_into();
|
||
description.set(input.value());
|
||
})
|
||
};
|
||
|
||
let on_submit = {
|
||
let calendar_name = calendar_name.clone();
|
||
let description = description.clone();
|
||
let selected_color = selected_color.clone();
|
||
let error_message = error_message.clone();
|
||
let is_creating = is_creating.clone();
|
||
let on_create = props.on_create.clone();
|
||
|
||
Callback::from(move |e: SubmitEvent| {
|
||
e.prevent_default();
|
||
|
||
let name = (*calendar_name).trim();
|
||
if name.is_empty() {
|
||
error_message.set(Some("Calendar name is required".to_string()));
|
||
return;
|
||
}
|
||
|
||
if name.len() > 100 {
|
||
error_message.set(Some(
|
||
"Calendar name too long (max 100 characters)".to_string(),
|
||
));
|
||
return;
|
||
}
|
||
|
||
error_message.set(None);
|
||
is_creating.set(true);
|
||
|
||
let desc = if (*description).trim().is_empty() {
|
||
None
|
||
} else {
|
||
Some((*description).clone())
|
||
};
|
||
|
||
on_create.emit((name.to_string(), desc, (*selected_color).clone()));
|
||
})
|
||
};
|
||
|
||
let on_backdrop_click = {
|
||
let on_close = props.on_close.clone();
|
||
Callback::from(move |e: MouseEvent| {
|
||
// Only close if clicking the backdrop, not the modal content
|
||
if e.target() == e.current_target() {
|
||
on_close.emit(());
|
||
}
|
||
})
|
||
};
|
||
|
||
if !props.is_open {
|
||
return html! {};
|
||
}
|
||
|
||
html! {
|
||
<div class="modal-backdrop" onclick={on_backdrop_click}>
|
||
<div class="create-calendar-modal">
|
||
<div class="modal-header">
|
||
<h2>{"Create New Calendar"}</h2>
|
||
<button class="close-button" onclick={props.on_close.reform(|_| ())}>
|
||
{"×"}
|
||
</button>
|
||
</div>
|
||
|
||
<form class="modal-body" onsubmit={on_submit}>
|
||
{
|
||
if let Some(ref error) = *error_message {
|
||
html! {
|
||
<div class="error-message">
|
||
{error}
|
||
</div>
|
||
}
|
||
} else {
|
||
html! {}
|
||
}
|
||
}
|
||
|
||
<div class="form-group">
|
||
<label for="calendar-name">{"Calendar Name *"}</label>
|
||
<input
|
||
id="calendar-name"
|
||
type="text"
|
||
value={(*calendar_name).clone()}
|
||
oninput={on_name_change}
|
||
placeholder="Enter calendar name"
|
||
maxlength="100"
|
||
disabled={*is_creating}
|
||
/>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="calendar-description">{"Description"}</label>
|
||
<textarea
|
||
id="calendar-description"
|
||
value={(*description).clone()}
|
||
oninput={on_description_change}
|
||
placeholder="Optional calendar description"
|
||
rows="3"
|
||
disabled={*is_creating}
|
||
/>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>{"Calendar Color"}</label>
|
||
<div class="color-grid">
|
||
{
|
||
props.available_colors.iter().enumerate().map(|(index, color)| {
|
||
let color = color.clone();
|
||
let selected_color = selected_color.clone();
|
||
let is_selected = selected_color.as_ref() == Some(&color);
|
||
let on_color_select = {
|
||
let color = color.clone();
|
||
Callback::from(move |_: MouseEvent| {
|
||
selected_color.set(Some(color.clone()));
|
||
})
|
||
};
|
||
|
||
let class_name = if is_selected {
|
||
"color-option selected"
|
||
} else {
|
||
"color-option"
|
||
};
|
||
|
||
html! {
|
||
<button
|
||
key={index}
|
||
type="button"
|
||
class={class_name}
|
||
style={format!("background-color: {}", color)}
|
||
onclick={on_color_select}
|
||
disabled={*is_creating}
|
||
/>
|
||
}
|
||
}).collect::<Html>()
|
||
}
|
||
</div>
|
||
<p class="color-help-text">{"Optional: Choose a color for your calendar"}</p>
|
||
</div>
|
||
|
||
<div class="modal-actions">
|
||
<button
|
||
type="button"
|
||
class="cancel-button"
|
||
onclick={props.on_close.reform(|_| ())}
|
||
disabled={*is_creating}
|
||
>
|
||
{"Cancel"}
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
class="create-button"
|
||
disabled={*is_creating}
|
||
>
|
||
{
|
||
if *is_creating {
|
||
"Creating..."
|
||
} else {
|
||
"Create Calendar"
|
||
}
|
||
}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|