diff --git a/plan-proto/src/plan.rs b/plan-proto/src/plan.rs
index 77c6f78..0d98bbf 100644
--- a/plan-proto/src/plan.rs
+++ b/plan-proto/src/plan.rs
@@ -13,6 +13,8 @@ pub enum PlanError {
StartAfterEnd,
#[error("day 0 ends before the plan starts")]
Day0EndsBeforePlanStarts,
+ #[error("plan has no days")]
+ NoDays,
}
crate::id_impl!(PlanId);
@@ -25,6 +27,9 @@ pub struct CreatePlan {
}
impl CreatePlan {
pub fn check(&self) -> Result<(), PlanError> {
+ if self.days.is_empty() {
+ return Err(PlanError::NoDays);
+ }
let start_half_hour: HalfHour = self.start_time.time().into();
if let Some(day0) = self.days.get(&0u8)
diff --git a/plan-server/src/main.rs b/plan-server/src/main.rs
index 7d60fc6..6b13fd8 100644
--- a/plan-server/src/main.rs
+++ b/plan-server/src/main.rs
@@ -195,7 +195,10 @@ async fn main() {
),
)
.with_state(state)
- .layer(tower_http::cors::CorsLayer::permissive().allow_headers([header::AUTHORIZATION]))
+ .layer(
+ tower_http::cors::CorsLayer::permissive()
+ .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]),
+ )
.fallback(get(handle_http_static));
let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap();
log::info!("listening on {}", listener.local_addr().unwrap());
diff --git a/plan/img/plan-icon.svg b/plan/img/plan-icon.svg
new file mode 100644
index 0000000..595e550
--- /dev/null
+++ b/plan/img/plan-icon.svg
@@ -0,0 +1,46 @@
+
+
+
+
diff --git a/plan/index.html b/plan/index.html
index aefe204..7f5ff31 100644
--- a/plan/index.html
+++ b/plan/index.html
@@ -6,6 +6,8 @@
plan
+
+
diff --git a/plan/index.scss b/plan/index.scss
index 44f5323..22c3ca3 100644
--- a/plan/index.scss
+++ b/plan/index.scss
@@ -222,17 +222,15 @@ nav.user-nav {
align-items: center;
gap: 10px;
+ .submit {
+ margin-top: 20px;
-}
+ button {
+ padding: 5px 15px 5px 15px;
+ font-size: 2em;
+ }
-.field {
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- // width: max-content;
- // min-width: 60%;
- font-size: 1.5em;
- width: 100%;
+ }
}
.days {
@@ -255,7 +253,15 @@ nav.user-nav {
flex-direction: column;
align-items: flex-start;
flex-wrap: nowrap;
- // gap: 10px;
+
+ .field {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ font-size: 1.5em;
+ width: 100%;
+ }
+
.remove {
width: 100%;
@@ -386,11 +392,40 @@ nav.user-nav {
}
}
+.plan-details {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ width: 100%;
+
+ .add-day {
+ margin-top: 5px;
+ font-size: 1.5em;
+ padding: 5px 0 5px 0;
+ }
+}
+
.fields {
display: flex;
- align-items: center;
- flex-direction: column;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: baseline;
gap: 10px;
+ width: 100%;
+
+ .field {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ font-size: 1.5em;
+ flex-grow: 1;
+
+ input,
+ select {
+ font-size: 1em;
+ box-sizing: border-box;
+ }
+ }
}
@media only screen and (max-width : 999px) {
@@ -475,3 +510,142 @@ nav.user-nav {
}
}
}
+
+.create-days-view {
+ display: flex;
+ flex-wrap: nowrap;
+ flex-direction: column;
+ width: 80vw;
+ gap: 20px;
+
+ .controls {
+ align-self: flex-end;
+
+ button {
+ font-size: 1.5em;
+ }
+ }
+
+ .create-days-view-content {}
+}
+
+.calendar-view {
+ width: 100%;
+ user-select: none;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+
+ .headline {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ width: 100%;
+ justify-content: space-between;
+ font-size: 2em;
+ align-items: baseline;
+
+ button {
+ font-size: 1em;
+ padding: 0;
+ height: 2em;
+ width: 2em;
+ text-align: center;
+ }
+
+ .inactive {
+ cursor: not-allowed;
+ background-color: rgba(255, 255, 255, 0.2);
+ color: white;
+ filter: brightness(40%);
+
+ &:hover {
+ filter: brightness(25%);
+ }
+ }
+ }
+
+ .days {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+
+ .weekday {
+ text-align: center;
+ }
+
+ .sun {
+ color: red;
+ }
+
+ .calendar-day {
+ border: 1px solid white;
+ padding: 5px;
+ cursor: pointer;
+ font-size: 2em;
+ text-align: center;
+
+ &.inactive {
+ cursor: not-allowed;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ background-color: rgba(255, 255, 255, 0.2);
+ color: white;
+ filter: brightness(40%);
+
+ &:hover {
+ filter: brightness(40%);
+ }
+ }
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+ }
+
+ &.marked {
+ background-color: rgba(0, 255, 0, 0.3);
+ border: 1px solid rgba(0, 255, 0, 0.6);
+ // color: rgba(0, 255, 0, 0.6);
+
+ &:hover {
+ background-color: rgba(0, 255, 0, 0.2);
+ }
+ }
+ }
+ }
+}
+
+@media only screen and (max-width: 299px) {
+
+ .days,
+ .headline {
+ font-size: 0.5rem !important;
+ }
+
+ .days {
+ gap: 3px;
+ }
+}
+
+@media only screen and (min-width: 300px) and (max-width : 425px) {
+
+ .calendar-day,
+ .headline {
+ font-size: 1em !important;
+ }
+
+ .days {
+ gap: 3px;
+ }
+}
+
+input,
+select {
+ border: 1px solid rgba(255, 255, 255, 0.7);
+ background-color: rgba(255, 255, 255, 0.07);
+ color: white;
+
+ &:focus {
+ outline: 1px solid white;
+ background-color: white;
+ color: black;
+ }
+}
diff --git a/plan/src/components/half_hour_range_select.rs b/plan/src/components/half_hour_range_select.rs
index b5cc44c..ed42958 100644
--- a/plan/src/components/half_hour_range_select.rs
+++ b/plan/src/components/half_hour_range_select.rs
@@ -35,7 +35,7 @@ pub fn HalfHourRangeSelect(
.collect::();
let cb = on_change.clone();
let on_change_range = range.clone();
- let on_change = Callback::from(move |ev: Event| {
+ let on_change_cb = Callback::from(move |ev: Event| {
if let Some(select) = ev.target_dyn_into::() {
let selected = select.selected_index();
if selected == -1 {
@@ -46,8 +46,39 @@ pub fn HalfHourRangeSelect(
}
}
});
+ let on_wheel = {
+ let on_change = on_change.clone();
+ let range = range.clone();
+ Callback::from(move |ev: WheelEvent| {
+ let Some(target) = ev.target_dyn_into::() else {
+ return;
+ };
+ let index = target.selected_index();
+ let new_index = match ev.delta_y().total_cmp(&0.0) {
+ core::cmp::Ordering::Equal => return,
+ core::cmp::Ordering::Less => {
+ if index != 0 {
+ index - 1
+ } else {
+ (target.children().length() - 1) as i32
+ }
+ }
+ core::cmp::Ordering::Greater => {
+ if index + 1 < target.children().length() as i32 {
+ index + 1
+ } else {
+ 0
+ }
+ }
+ };
+ target.set_selected_index(new_index);
+ if let Some(selected) = range.clone().nth(new_index as _) {
+ on_change.emit(selected);
+ }
+ })
+ };
html! {
-