Compare commits
3
Commits
de10d694e1
...
aa1c11047e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa1c11047e | ||
|
|
50c0b99f20 | ||
|
|
01c33f4a1d |
@@ -68,6 +68,7 @@ pub async fn delete_ticket(
|
|||||||
pub async fn get_tickets(
|
pub async fn get_tickets(
|
||||||
State(data): State<Arc<AppState>>,
|
State(data): State<Arc<AppState>>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
println!("get_tickets called");
|
||||||
let tickets = sqlx::query_as::<_, Ticket>(
|
let tickets = sqlx::query_as::<_, Ticket>(
|
||||||
r#"SELECT * FROM tickets WHERE status <> 'Archived' ORDER BY date DESC"#,
|
r#"SELECT * FROM tickets WHERE status <> 'Archived' ORDER BY date DESC"#,
|
||||||
)
|
)
|
||||||
@@ -80,6 +81,7 @@ pub async fn get_tickets(
|
|||||||
});
|
});
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
|
||||||
})?;
|
})?;
|
||||||
|
println!("Tickets fetched");
|
||||||
|
|
||||||
let ticket_response = tickets
|
let ticket_response = tickets
|
||||||
.iter()
|
.iter()
|
||||||
@@ -87,6 +89,7 @@ pub async fn get_tickets(
|
|||||||
.collect::<Vec<TicketResponse>>();
|
.collect::<Vec<TicketResponse>>();
|
||||||
|
|
||||||
let json_response = serde_json::json!(ticket_response);
|
let json_response = serde_json::json!(ticket_response);
|
||||||
|
println!("Json contructed");
|
||||||
Ok(Json(json_response))
|
Ok(Json(json_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@ serde = { version = "1.0.228", features = ["derive"] }
|
|||||||
wasm-bindgen = "0.2.118"
|
wasm-bindgen = "0.2.118"
|
||||||
wasm-bindgen-futures = "0.4.68"
|
wasm-bindgen-futures = "0.4.68"
|
||||||
web-sys = { version = "0.3.95", features = [
|
web-sys = { version = "0.3.95", features = [
|
||||||
"Window","Document","Request","Response","Headers",
|
"Window","Document","Request","Response","Headers", "HtmlSelectElement",
|
||||||
"SubmitEvent","InputEvent","HtmlInputElement","Event"
|
"SubmitEvent","InputEvent","HtmlInputElement","Event", "HtmlFormElement"
|
||||||
] }
|
] }
|
||||||
gloo-net = "0.7.0"
|
gloo-net = "0.7.0"
|
||||||
yew-router = "0.20.0"
|
yew-router = "0.20.0"
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ addresses = ["127.0.0.1"]
|
|||||||
# address = "0.0.0.0"
|
# address = "0.0.0.0"
|
||||||
# The port to serve on.
|
# The port to serve on.
|
||||||
port = 8000
|
port = 8000
|
||||||
|
|
||||||
|
[[proxy]]
|
||||||
|
backend = "http://localhost:8001/api"
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ enum Route {
|
|||||||
TicketById { id: i32 },
|
TicketById { id: i32 },
|
||||||
#[at("/tickets")]
|
#[at("/tickets")]
|
||||||
AllTickets,
|
AllTickets,
|
||||||
|
#[at("/register")]
|
||||||
|
Register,
|
||||||
#[not_found]
|
#[not_found]
|
||||||
#[at("/404")]
|
#[at("/404")]
|
||||||
NotFound,
|
NotFound,
|
||||||
@@ -25,6 +27,7 @@ fn switch(route: Route) -> Html {
|
|||||||
Route::Ticket => html! { <ticket::SubmitTicket/> },
|
Route::Ticket => html! { <ticket::SubmitTicket/> },
|
||||||
Route::TicketById { id } => html! { <ticket::ShowTicketByID id={id}/> },
|
Route::TicketById { id } => html! { <ticket::ShowTicketByID id={id}/> },
|
||||||
Route::AllTickets => html! { <ticket::AllTickets/> },
|
Route::AllTickets => html! { <ticket::AllTickets/> },
|
||||||
|
Route::Register => html! { <user::Register/> },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod basic_pages;
|
pub mod basic_pages;
|
||||||
pub mod ticket;
|
pub mod ticket;
|
||||||
|
pub mod user;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use gloo_net::http::Request;
|
use gloo_net::http::Request;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
use web_sys::HtmlSelectElement;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
@@ -12,6 +13,11 @@ pub struct TicketCreateScheme {
|
|||||||
pub room: i16,
|
pub room: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
pub struct TicketUpdateScheme {
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
pub struct Ticket {
|
pub struct Ticket {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
@@ -32,7 +38,7 @@ pub struct TicketProps {
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct ApiError {
|
pub struct ApiError {
|
||||||
message: String,
|
message: String,
|
||||||
status: String,
|
_status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component(SubmitTicket)]
|
#[component(SubmitTicket)]
|
||||||
@@ -66,7 +72,7 @@ pub fn submit_ticket_component() -> Html {
|
|||||||
room,
|
room,
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = Request::post("http://localhost:8001/api/tickets/create")
|
let request = Request::post("/api/tickets/create")
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
.expect("Failed to build request");
|
.expect("Failed to build request");
|
||||||
|
|
||||||
@@ -81,8 +87,8 @@ pub fn submit_ticket_component() -> Html {
|
|||||||
|
|
||||||
let category_change = {
|
let category_change = {
|
||||||
let category = category.clone();
|
let category = category.clone();
|
||||||
Callback::from(move |e: InputEvent| {
|
Callback::from(move |e: Event| {
|
||||||
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
let input: web_sys::HtmlSelectElement = e.target_unchecked_into();
|
||||||
category.set(input.value());
|
category.set(input.value());
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -122,7 +128,14 @@ pub fn submit_ticket_component() -> Html {
|
|||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
<label>{ "Kategorie:" }
|
<label>{ "Kategorie:" }
|
||||||
<input type="text" value={(*category).clone()} oninput={category_change}/>
|
<select value={(*category).clone()} onchange={category_change}>
|
||||||
|
<option value="Whiteboard Beamer">{ "Whiteboard Beamer" }</option>
|
||||||
|
<option value="Internet">{ "Internet" }</option>
|
||||||
|
<option value="iPad Koffer">{ "iPad Koffer" }</option>
|
||||||
|
<option value="Apple TV">{ "Apple TV" }</option>
|
||||||
|
<option value="Docu Cam">{ "Dokumenten Kamera" }</option>
|
||||||
|
<option value="Sonstiges">{ "Sonstiges" }</option>
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
<label>{ "Raum:" }
|
<label>{ "Raum:" }
|
||||||
@@ -149,6 +162,7 @@ pub fn show_ticket_by_id_component(props: &TicketProps) -> Html {
|
|||||||
let error = use_state(|| None::<String>);
|
let error = use_state(|| None::<String>);
|
||||||
let loading = use_state(|| false);
|
let loading = use_state(|| false);
|
||||||
let id = props.id;
|
let id = props.id;
|
||||||
|
let status = use_state(|| "".to_string());
|
||||||
|
|
||||||
{
|
{
|
||||||
let ticket = ticket.clone();
|
let ticket = ticket.clone();
|
||||||
@@ -161,7 +175,7 @@ pub fn show_ticket_by_id_component(props: &TicketProps) -> Html {
|
|||||||
let error = error.clone();
|
let error = error.clone();
|
||||||
let id = *id_ref;
|
let id = *id_ref;
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let url = format!("http://localhost:8001/api/tickets/{}", id);
|
let url = format!("/api/tickets/{}", id);
|
||||||
match Request::get(&url).send().await {
|
match Request::get(&url).send().await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
@@ -190,6 +204,43 @@ pub fn show_ticket_by_id_component(props: &TicketProps) -> Html {
|
|||||||
|| ()
|
|| ()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let onsubmit = {
|
||||||
|
let status = status.clone();
|
||||||
|
let id = id.clone();
|
||||||
|
let error = error.clone();
|
||||||
|
|
||||||
|
Callback::from(move |e: SubmitEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
let form: web_sys::HtmlFormElement = e.target_unchecked_into();
|
||||||
|
let select_element = form
|
||||||
|
.query_selector("select[name=\"status\"]")
|
||||||
|
.ok()
|
||||||
|
.and_then(|opt| opt)
|
||||||
|
.and_then(|el| el.dyn_into::<HtmlSelectElement>().ok());
|
||||||
|
let new_status = select_element
|
||||||
|
.map(|s| s.value())
|
||||||
|
.unwrap_or_else(|| (*status).clone());
|
||||||
|
status.set(new_status.clone());
|
||||||
|
|
||||||
|
let id = id.clone();
|
||||||
|
let error = error.clone();
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
let value = TicketUpdateScheme { status: new_status };
|
||||||
|
|
||||||
|
let url = format!("/api/tickets/{}", id);
|
||||||
|
let request = Request::patch(&url)
|
||||||
|
.json(&value)
|
||||||
|
.expect("Failed to construct request");
|
||||||
|
|
||||||
|
match request.send().await {
|
||||||
|
Ok(response) if response.status() == 200 => error.set(Some("Success".into())),
|
||||||
|
Ok(response) => error.set(Some(format!("Error: {}", response.status()))),
|
||||||
|
Err(err) => error.set(Some(format!("Network error: {}", err))),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
if *loading {
|
if *loading {
|
||||||
html! {<p>{ "Loading" }</p>}
|
html! {<p>{ "Loading" }</p>}
|
||||||
@@ -204,7 +255,25 @@ pub fn show_ticket_by_id_component(props: &TicketProps) -> Html {
|
|||||||
<p><strong>{ "Beschreibung: " }</strong>{ &t.description }</p>
|
<p><strong>{ "Beschreibung: " }</strong>{ &t.description }</p>
|
||||||
<p><strong>{ "Raum: " }</strong>{ t.room }</p>
|
<p><strong>{ "Raum: " }</strong>{ t.room }</p>
|
||||||
<p><strong>{ "Datum: " }</strong>{ &t.date }</p>
|
<p><strong>{ "Datum: " }</strong>{ &t.date }</p>
|
||||||
<p><strong>{ "Status: "}</strong>{ &t.status }</p>
|
<p><strong>{ "Status: "}</strong>{ match t.status.as_str() {
|
||||||
|
"ToDo" => "Zu tun",
|
||||||
|
"InProgress" => "In Bearbeitung",
|
||||||
|
"Completed" => "Erledigt",
|
||||||
|
"Archived" => "Archiviert",
|
||||||
|
_ => "Ungültiger Status"
|
||||||
|
}}</p>
|
||||||
|
|
||||||
|
<form {onsubmit}>
|
||||||
|
<label>{ "Status ändern" }
|
||||||
|
<select name="status">
|
||||||
|
<option value="ToDo">{ "Zu tun" }</option>
|
||||||
|
<option value="InProgress">{ "In Bearbeitung" }</option>
|
||||||
|
<option value="Completed">{ "Erledigt" }</option>
|
||||||
|
<option value="Archived">{ "Archiviert" }</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button type="submit">{ "Aktualisieren" }</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -226,7 +295,7 @@ pub fn all_tickets_component() -> Html {
|
|||||||
use_effect_with((), move |_| {
|
use_effect_with((), move |_| {
|
||||||
loading.set(true);
|
loading.set(true);
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let url = format!("http://localhost:8001/api/tickets");
|
let url = format!("/api/tickets");
|
||||||
match Request::get(&url).send().await {
|
match Request::get(&url).send().await {
|
||||||
Ok(response) if response.status() == 200 => {
|
Ok(response) if response.status() == 200 => {
|
||||||
match response.json::<Vec<Ticket>>().await {
|
match response.json::<Vec<Ticket>>().await {
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
use gloo_net::http::Request;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i16,
|
||||||
|
pub last_name: String,
|
||||||
|
pub first_name: String,
|
||||||
|
pub username: String,
|
||||||
|
pub is_admin: bool,
|
||||||
|
pub pwd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct UserCreateScheme {
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
pub username: String,
|
||||||
|
pub is_admin: bool,
|
||||||
|
pub pwd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct LoginScheme {
|
||||||
|
pub username: String,
|
||||||
|
pub pwd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct FilteredUser {
|
||||||
|
pub id: i16,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
pub username: String,
|
||||||
|
pub is_admin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component(Register)]
|
||||||
|
pub fn register_component() -> Html {
|
||||||
|
let first_name = use_state(|| "".to_string());
|
||||||
|
let last_name = use_state(|| "".to_string());
|
||||||
|
let username = use_state(|| "".to_string());
|
||||||
|
let is_admin = use_state(|| false);
|
||||||
|
let pwd = use_state(|| "".to_string());
|
||||||
|
let status = use_state(|| None::<String>);
|
||||||
|
|
||||||
|
let onsubmit = {
|
||||||
|
let first_name = first_name.clone();
|
||||||
|
let last_name = last_name.clone();
|
||||||
|
let username = username.clone();
|
||||||
|
let is_admin = is_admin.clone();
|
||||||
|
let pwd = pwd.clone();
|
||||||
|
let status = status.clone();
|
||||||
|
|
||||||
|
Callback::from(move |e: SubmitEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
let first_name = (*first_name).clone();
|
||||||
|
let last_name = (*last_name).clone();
|
||||||
|
let username = (*username).clone();
|
||||||
|
let is_admin = *is_admin;
|
||||||
|
let pwd = (*pwd).clone();
|
||||||
|
let status = status.clone();
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
let payload = UserCreateScheme {
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
username,
|
||||||
|
is_admin,
|
||||||
|
pwd,
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = Request::post("/api/register")
|
||||||
|
.json(&payload)
|
||||||
|
.expect("Error building request");
|
||||||
|
|
||||||
|
match request.send().await {
|
||||||
|
Ok(response) if response.status() == 200 => status.set(Some("Success".into())),
|
||||||
|
Ok(response) => status.set(Some(format!("Error: {}", response.status()))),
|
||||||
|
Err(err) => status.set(Some(format!("Network error: {}", err))),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let fn_change = {
|
||||||
|
let first_name = first_name.clone();
|
||||||
|
Callback::from(move |e: InputEvent| {
|
||||||
|
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
||||||
|
first_name.set(input.value());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let ln_change = {
|
||||||
|
let last_name = last_name.clone();
|
||||||
|
Callback::from(move |e: InputEvent| {
|
||||||
|
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
||||||
|
last_name.set(input.value());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let un_change = {
|
||||||
|
let username = username.clone();
|
||||||
|
Callback::from(move |e: InputEvent| {
|
||||||
|
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
||||||
|
username.set(input.value());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let admin_change = {
|
||||||
|
let is_admin = is_admin.clone();
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
||||||
|
is_admin.set(input.checked());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let pwd_change = {
|
||||||
|
let pwd = pwd.clone();
|
||||||
|
Callback::from(move |e: InputEvent| {
|
||||||
|
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
|
||||||
|
pwd.set(input.value());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<form {onsubmit}>
|
||||||
|
<label>{ "Vorname:" }
|
||||||
|
<input type="text" value={(*first_name).clone()} oninput={fn_change}/>
|
||||||
|
</label>
|
||||||
|
<label>{ "Nachname:" }
|
||||||
|
<input type="text" value={(*last_name).clone()} oninput={ln_change}/>
|
||||||
|
</label>
|
||||||
|
<label>{ "Benutzername:" }
|
||||||
|
<input type="text" value={(*username).clone()} oninput={un_change}/>
|
||||||
|
</label>
|
||||||
|
<label>{ "Admin:" }
|
||||||
|
<input type="checkbox" checked={*is_admin} onchange={admin_change}/>
|
||||||
|
</label>
|
||||||
|
<label>{ "Password:" }
|
||||||
|
<input type="password" value={(*pwd).clone()} oninput={pwd_change}/>
|
||||||
|
</label>
|
||||||
|
<button type="submit">{ "Bestätigen" }</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user