Author SHA1 Message Date
schn33fuchs 99c5aa613c .gitignore 2026-05-13 16:59:32 +02:00
schn33fuchs cf24d6156c Small Fix
Removed unneccesary things from archive
2026-05-12 15:47:35 +02:00
schn33fuchs de2199e1c3 After merge fix
Forgor a &&
2026-05-11 21:23:08 +02:00
schn33fuchs 5199300856 Merge branch 'main' into styles-nino 2026-05-11 21:06:26 +02:00
schn33fuchs 289f83df3b Old logout look
It's better
2026-05-11 20:48:04 +02:00
schn33fuchs 928ca1de11 Improvements
More styling
2026-05-11 19:53:30 +02:00
schn33fuchs 10de47b911 Styling
Copied Ninos styles with copilot
2026-05-11 13:15:03 +02:00
27 changed files with 1110 additions and 358 deletions
-1
View File
@@ -26,4 +26,3 @@ frontend/node_modules/
# Added by cargo # Added by cargo
/target /target
.antigravitycli/
+23 -34
View File
@@ -275,7 +275,7 @@ pub async fn get_current_user(
/// - `404 Not Found` if user doesn't exist /// - `404 Not Found` if user doesn't exist
/// - `500 Internal Server Error` if database error occurs /// - `500 Internal Server Error` if database error occurs
pub async fn delete_user( pub async fn delete_user(
Path(id): Path<i16>, Path(id): Path<i32>,
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>)> {
let query = sqlx::query(r#"DELETE FROM users WHERE id = $1"#) let query = sqlx::query(r#"DELETE FROM users WHERE id = $1"#)
@@ -414,43 +414,32 @@ pub async fn get_user_by_id(
/// - Passwords are hashed using Argon2 before storage. /// - Passwords are hashed using Argon2 before storage.
/// - This endpoint typically requires admin privileges (enforced by middleware). /// - This endpoint typically requires admin privileges (enforced by middleware).
pub async fn update_user( pub async fn update_user(
Path(id): Path<i16>, Path(id): Path<i32>,
State(data): State<Arc<AppState>>, State(data): State<Arc<AppState>>,
Json(body): Json<UserUpdateScheme>, Json(body): Json<UserUpdateScheme>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> { ) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
let update_result = if !body.new_pwd.is_empty() { let argon = Argon2::default();
let argon = Argon2::default(); let salt = SaltString::generate(&mut OsRng);
let salt = SaltString::generate(&mut OsRng); let hashed_pwd = match argon.hash_password(body.new_pwd.clone().as_bytes(), &salt) {
let hashed_pwd = match argon.hash_password(body.new_pwd.clone().as_bytes(), &salt) { Ok(h) => h.to_string(),
Ok(h) => h.to_string(), Err(e) => panic!("Error hashing {:}", e),
Err(e) => panic!("Error hashing {:}", e), };
};
sqlx::query(r#"UPDATE users SET first_name = $1, last_name = $2, username = $3, pwd = $4, is_admin = $5 WHERE id = $6"#) let update_result = sqlx::query(r#"UPDATE users SET first_name = $1, last_name = $2, username = $3, pwd = $4, is_admin = $5 WHERE id = $6"#)
.bind(body.first_name.to_owned()) .bind(body.first_name.to_owned())
.bind(body.last_name.to_owned()) .bind(body.last_name.to_owned())
.bind(body.username.to_owned()) .bind(body.username.to_owned())
.bind(hashed_pwd) .bind(&hashed_pwd)
.bind(body.make_admin.to_owned()) .bind(body.make_admin.to_owned())
.bind(id) .bind(id)
.execute(&data.db) .execute(&data.db)
.await .await
} else { .map_err(|e| {
sqlx::query(r#"UPDATE users SET first_name = $1, last_name = $2, username = $3, is_admin = $4 WHERE id = $5"#) (
.bind(body.first_name.to_owned()) StatusCode::INTERNAL_SERVER_ERROR,
.bind(body.last_name.to_owned()) Json(json!({"status": "error", "message": format!("{:?}", e)})),
.bind(body.username.to_owned()) )
.bind(body.make_admin.to_owned()) })?;
.bind(id)
.execute(&data.db)
.await
}
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"status": "error", "message": format!("{:?}", e)})),
)
})?;
if update_result.rows_affected() == 0 { if update_result.rows_affected() == 0 {
let error_response = serde_json::json!({ let error_response = serde_json::json!({
+1 -1
View File
@@ -1,6 +1,6 @@
[serve] [serve]
# The address to serve on LAN. # The address to serve on LAN.
addresses = ["127.0.0.1"] #,"10.150.9.7"] addresses = ["127.0.0.1"] #,"10.150.9.116"]
# The address to serve on WAN. # The address to serve on WAN.
# address = "0.0.0.0" # address = "0.0.0.0"
# The port to serve on. # The port to serve on.
-2
View File
@@ -4,8 +4,6 @@
<head> <head>
<link data-trunk rel="rust" data-bin="bin" /> <link data-trunk rel="rust" data-bin="bin" />
<link data-trunk rel="scss" href="src/styles/main.scss" /> <link data-trunk rel="scss" href="src/styles/main.scss" />
<link data-trunk rel="icon" href="src/assets/favicon.ico" type="image/x-icon" />
<link data-trunk rel="copy-dir" href="src/assets" />
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Yew App</title> <title>Yew App</title>
</head> </head>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

-1
View File
@@ -269,7 +269,6 @@ fn switch(route: Route) -> Html {
pub fn app() -> Html { pub fn app() -> Html {
html! { html! {
<BrowserRouter> <BrowserRouter>
<basic_pages::Icon/>
<Switch<Route> render={switch} /> <Switch<Route> render={switch} />
</BrowserRouter> </BrowserRouter>
} }
+21 -18
View File
@@ -56,11 +56,15 @@ pub fn home_component() -> Html {
} }
html! { html! {
<div class="rundbg"> <div class="form-container">
<div class="page-header">
<h1>{ "Welcome" }</h1>
</div>
<crate::utilities::TicketCount/> <crate::utilities::TicketCount/>
<div>
<p>{ "You are logged in as: " }</p> <p>{ "You are logged in as: " }</p>
<p>{ &*name }</p> <p class="text-muted">{ &*name }</p>
</div>
</div> </div>
} }
} }
@@ -80,9 +84,12 @@ pub fn home_component() -> Html {
pub fn not_found_component() -> Html { pub fn not_found_component() -> Html {
let message = "404 Not found"; let message = "404 Not found";
html! { html! {
<div> <div class="form-container">
<h1>{&message}</h1> <div class="empty-state">
<Link<crate::Route> to={crate::Route::Home}>{ "Zurück zum Start" }</Link<crate::Route>> <h1>{&message}</h1>
<p>{ "The page you are looking for does not exist." }</p>
<Link<crate::Route> to={crate::Route::Home}>{ "Back to Home" }</Link<crate::Route>>
</div>
</div> </div>
} }
} }
@@ -102,17 +109,13 @@ pub fn not_found_component() -> Html {
#[component(PermissionDenied)] #[component(PermissionDenied)]
pub fn denied_component() -> Html { pub fn denied_component() -> Html {
html! { html! {
<div> <div class="form-container">
<h1>{ "Sie haben nicht die benötigten Rechte um diese Seite aufzurufen" }</h1> <div class="empty-state">
<h3>{ "Wenn sie denken, dass dies ein Fehler ist kontaktieren sie Herrn Winter" }</h3> <h1>{ "Access Denied" }</h1>
<Link<crate::Route> to={crate::Route::Home}>{ "Zurück zum Start" }</Link<crate::Route>> <p>{ "Sie haben nicht die benötigten Rechte um diese Seite aufzurufen" }</p>
<p class="text-muted">{ "Wenn sie denken, dass dies ein Fehler ist kontaktieren sie Herrn Winter" }</p>
<Link<crate::Route> to={crate::Route::Home}>{ "Back to Home" }</Link<crate::Route>>
</div>
</div> </div>
} }
} }
#[component(Icon)]
pub fn icon_component() -> Html {
html! {
<img src="assets/csg.png" class="csg-icon" />
}
}
+82 -85
View File
@@ -287,52 +287,53 @@ pub fn submit_ticket_component() -> Html {
let room_valid = (*room).is_some(); let room_valid = (*room).is_some();
html! { html! {
<form class="rundbg" {onsubmit}> <div class="form-container">
<label>{ "Betreff:" } <div class="page-header">
<input type="text" value={(*betreff).clone()} oninput={betreff_change}/> <h1>{ "Create Ticket" }</h1>
</label> </div>
<br/> <form {onsubmit}>
<label>{ "Beschreibung:" } <label>{ "Betreff:" }
<input type="text" value={(*description).clone()} oninput={description_change}/> <input type="text" value={(*betreff).clone()} oninput={betreff_change}/>
</label> </label>
<br/> <label>{ "Beschreibung:" }
<label>{ "Kategorie:" } <textarea value={(*description).clone()} oninput={description_change}/>
<select value={(*category).clone()} onchange={category_change}> </label>
<option value="Whiteboard Beamer">{ "Whiteboard Beamer" }</option> <label>{ "Kategorie:" }
<option value="Internet">{ "Internet" }</option> <select value={(*category).clone()} onchange={category_change}>
<option value="iPad Koffer">{ "iPad Koffer" }</option> <option value="Whiteboard Beamer">{ "Whiteboard Beamer" }</option>
<option value="Apple TV">{ "Apple TV" }</option> <option value="Internet">{ "Internet" }</option>
<option value="Docu Cam">{ "Dokumenten Kamera" }</option> <option value="iPad Koffer">{ "iPad Koffer" }</option>
<option value="Sonstiges">{ "Sonstiges" }</option> <option value="Apple TV">{ "Apple TV" }</option>
</select> <option value="Docu Cam">{ "Dokumenten Kamera" }</option>
</label> <option value="Sonstiges">{ "Sonstiges" }</option>
<br/> </select>
<label>{ "Raum:" } </label>
<input type="text" value={(*room_input).clone()} oninput={room_change}/> <label>{ "Raum:" }
</label> <input type="text" value={(*room_input).clone()} oninput={room_change}/>
{ </label>
if !room_valid { {
html! { if !room_valid {
<p style="color: red;">{ "Ungültiger oder nicht erlaubter Raum (z. B. 101, K12, D5)" }</p> html! {
<p class="alert error">{ "Ungültiger oder nicht erlaubter Raum (z. B. 101, K12, D5)" }</p>
}
} else {
html! {}
} }
} else {
html! {}
} }
} <button type="submit">{ "Send" }</button>
<br/>
<button type="submit">{ "Send" }</button>
<Link<crate::Route> to={crate::Route::AllTickets}>{ "Tickets ansehen" }</Link<crate::Route>> <Link<crate::Route> to={crate::Route::AllTickets}>{ "View All Tickets" }</Link<crate::Route>>
{ {
if let Some(s) = &*status { if let Some(s) = &*status {
html!{ <p>{ s }</p> } html!{ <p class="alert success">{ s }</p> }
} else { } else {
html!{} html!{}
}
} }
}
</form> </form>
</div>
} }
} }
@@ -664,34 +665,31 @@ pub fn all_tickets_component() -> Html {
html! { <p>{ format!("Error: {}", e) }</p> } html! { <p>{ format!("Error: {}", e) }</p> }
} else { } else {
html! { html! {
<ul class= "postits"> <div>
{ for tickets.iter().filter(|t| t.status != "Archived" && (if user.is_admin { true } else if let Some(uid) = user.id { t.user_id == uid } else { false })).map(|t| { <div class="page-header">
let status_class = match t.status.as_str() { <h1>{ "All Tickets" }</h1>
"ToDo" => "To-Do", </div>
"InProgress" => "InProgress", <ul class="ticket-list">
"Completed" => "Completed", { for tickets.iter().filter(|t| t.status != "Archived" && (if user.is_admin { true } else if let Some(uid) = user.id { t.user_id == uid } else { false })).map(|t| {
"Archived" => "Archived", let status_class = match t.status.as_str() {
_ => "To-Do" "ToDo" => "To-Do",
}; "InProgress" => "InProgress",
html! { "Completed" => "Completed",
<div class={classes!{status_class, "listbox"}}> "Archived" => "Archived",
<li key={t.id.to_string()}> _ => "To-Do"
<Link<crate::Route> to={crate::Route::TicketById{id: t.id}}><h3>{ format!("{} - #{}", t.betreff, t.id) }</h3></Link<crate::Route>> };
<p>{ &t.description }</p> html! {
<p>{ match t.status.as_str() { <li key={t.id.to_string()} class={status_class}>
"ToDo" => "Zu tun", <Link<crate::Route> to={crate::Route::TicketById{id: t.id}}><h3>{ format!("{}", t.betreff) }</h3></Link<crate::Route>>
"InProgress" => "In Bearbeitung", <p>{ &t.description }</p>
"Completed" => "Erledigt", </li>
"Archived" => "Archiviert", }
_ => "Ungültiger Status" })}
}}</p> </ul>
</li> <div class="ticket-list-actions">
<Link<crate::Route> to={crate::Route::Ticket}>{ "Zurück zur Startseite" }</Link<crate::Route>>
</div> </div>
}})} </div>
<Link<crate::Route> to={crate::Route::Ticket}>{ "Zurück zur Startseite" }</Link<crate::Route>>
</ul>
} }
} }
} }
@@ -807,24 +805,23 @@ pub fn archived_tickets_component() -> Html {
html! { <p>{ format!("Error: {}", e) }</p> } html! { <p>{ format!("Error: {}", e) }</p> }
} else { } else {
html! { html! {
<ul> <div>
{ for tickets.iter().filter(|t| t.status == "Archived" && (user.is_admin || if let Some(uid) = user.id { t.user_id == uid } else { false })).map(|t| html! { <div class="page-header">
<div> <h1>{ "Archivierte Tickets" }</h1>
<li key={t.id.to_string()}> </div>
<Link<crate::Route> to={crate::Route::TicketById{id: t.id}}><h3>{ format!("{} - #{}", t.betreff, t.id) }</h3></Link<crate::Route>> <ul class="ticket-list">
<p>{ &t.description }</p> { for tickets.iter().filter(|t| t.status == "Archived" && (user.is_admin || if let Some(uid) = user.id { t.user_id == uid } else { false })).map(|t| html! {
<p>{ match t.status.as_str() { <div>
"ToDo" => "Zu tun",
"InProgress" => "In Bearbeitung",
"Completed" => "Erledigt",
"Archived" => "Archiviert",
_ => "Ungültiger Status"
}}</p>
</li>
</div> <li key={t.id.to_string()}>
})} <Link<crate::Route> to={crate::Route::TicketById{id: t.id}}><h3>{ format!("{}", t.betreff) }</h3></Link<crate::Route>>
</ul> <p>{ &t.description }</p>
</li>
</div>
})}
</ul>
</div>
} }
} }
} }
+79 -53
View File
@@ -222,24 +222,29 @@ pub fn register_component() -> Html {
}; };
html! { html! {
<form {onsubmit} class="rundbg"> <div class="form-container">
<label>{ "Vorname:" } <div class="page-header">
<input type="text" value={(*first_name).clone()} oninput={fn_change}/> <h1>{ "Register User" }</h1>
</label> </div>
<label>{ "Nachname:" } <form {onsubmit}>
<input type="text" value={(*last_name).clone()} oninput={ln_change}/> <label>{ "Vorname:" }
</label> <input type="text" value={(*first_name).clone()} oninput={fn_change}/>
<label>{ "Benutzername:" } </label>
<input type="text" value={(*username).clone()} oninput={un_change}/> <label>{ "Nachname:" }
</label> <input type="text" value={(*last_name).clone()} oninput={ln_change}/>
<label>{ "Admin:" } </label>
<input type="checkbox" checked={*is_admin} onchange={admin_change}/> <label>{ "Benutzername:" }
</label> <input type="text" value={(*username).clone()} oninput={un_change}/>
<label>{ "Password:" } </label>
<input type="password" value={(*pwd).clone()} oninput={pwd_change}/> <label>{ "Admin:" }
</label> <input type="checkbox" checked={*is_admin} onchange={admin_change}/>
<button type="submit">{ "Bestätigen" }</button> </label>
</form> <label>{ "Password:" }
<input type="password" value={(*pwd).clone()} oninput={pwd_change}/>
</label>
<button type="submit">{ "Bestätigen" }</button>
</form>
</div>
} }
} }
@@ -328,30 +333,34 @@ pub fn login_component() -> Html {
}; };
html! { html! {
<div> <main class="content">
<h1 class="headline">{ "Anmeldung" }</h1> <div class="form-container">
<form {onsubmit} class="rundbg login"> <div class="page-header">
<input <h1>{ "Login" }</h1>
placeholder="username" </div>
value={(*username).clone()} <form {onsubmit}>
oninput={Callback::from(move |e: InputEvent| { <input
let input: web_sys::HtmlInputElement = e.target_unchecked_into(); placeholder="username"
username.set(input.value()); value={(*username).clone()}
})} oninput={Callback::from(move |e: InputEvent| {
/> let input: web_sys::HtmlInputElement = e.target_unchecked_into();
<input username.set(input.value());
type="password" })}
placeholder="password" />
value={(*pwd).clone()} <input
oninput={Callback::from(move |e: InputEvent| { type="password"
let input: web_sys::HtmlInputElement = e.target_unchecked_into(); placeholder="password"
pwd.set(input.value()); value={(*pwd).clone()}
})} oninput={Callback::from(move |e: InputEvent| {
/> let input: web_sys::HtmlInputElement = e.target_unchecked_into();
<button type="submit" disabled={*loading}>{ if *loading { "Logging in..." } else { "Login" } }</button> pwd.set(input.value());
if !error.is_empty() { <p style="color:red">{(*error).clone()}</p> } })}
</form> />
</div> <button type="submit" disabled={*loading}>{ if *loading { "Logging in..." } else { "Login" } }</button>
if !error.is_empty() { <p class="alert error">{(*error).clone()}</p> }
</form>
</div>
</main>
} }
} }
@@ -420,18 +429,37 @@ pub fn all_users_component() -> Html {
} }
if *loading { if *loading {
html! {<p>{ "Loading" }</p>} html! {
<div class="form-container">
<div class="page-header">
<h1>{ "All Users" }</h1>
</div>
<p>{ "Loading..." }</p>
</div>
}
} else if let Some(e) = &*error { } else if let Some(e) = &*error {
html! { <p>{ format!("Error: {}", e) }</p> } html! {
<div class="form-container">
<div class="page-header">
<h1>{ "All Users" }</h1>
</div>
<p class="alert error">{ format!("Error: {}", e) }</p>
</div>
}
} else { } else {
html! { html! {
<ul class="posti8s"> <div class="form-container">
{ for users.iter().map(|t| html! { <div class="page-header">
<li key={t.id.to_string()} class="listbox"> <h1>{ "All Users" }</h1>
<Link<crate::Route> to={crate::Route::UserByID{id: t.id}}><h3>{ format!("{} {}- #{}", t.first_name, t.last_name, t.id) }</h3></Link<crate::Route>> </div>
</li> <ul class="user-list">
})} { for users.iter().map(|t| html! {
</ul> <li key={t.id.to_string()}>
<Link<crate::Route> to={crate::Route::UserByID{id: t.id}}><h3>{ format!("{} {}", t.first_name, t.last_name) }</h3></Link<crate::Route>>
</li>
})}
</ul>
</div>
} }
} }
} }
@@ -566,7 +594,6 @@ pub fn user_by_id_component(props: &UserProps) -> Html {
let last_name = (*last_name).clone(); let last_name = (*last_name).clone();
let username = (*username).clone(); let username = (*username).clone();
let make_admin = *make_admin; let make_admin = *make_admin;
let new_pwd_state = new_pwd.clone();
let new_pwd = (*new_pwd).clone(); let new_pwd = (*new_pwd).clone();
saving.set(true); saving.set(true);
@@ -601,7 +628,6 @@ pub fn user_by_id_component(props: &UserProps) -> Html {
if let Ok(updated) = resp.json::<FilteredUser>().await { if let Ok(updated) = resp.json::<FilteredUser>().await {
user_state.set(Some(updated)); user_state.set(Some(updated));
} }
new_pwd_state.set(String::new());
save_success.set(true); save_success.set(true);
} }
Ok(resp) => { Ok(resp) => {
+1 -1
View File
@@ -289,7 +289,7 @@ pub fn ticket_count_component() -> Html {
html! { html! {
<div> <div>
<h2>{ "Offene Tickets" }</h2> <h2>{ "Offene Tickets" }</h2>
<h4>{ count }</h4> <h4 class="ticket_count">{ count }</h4>
</div> </div>
} }
} }
+7 -3
View File
@@ -1,8 +1,12 @@
@use "variables" as *; @use "variables" as *;
@mixin card { @mixin card {
backgroud: #fff; background: $color-container;
border-radius: &border-radius; border-radius: $border-radius;
box-shadow: 0 1px 3px rgba(0,0,0,0.06); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
padding: $spacing-md; padding: $spacing-md;
@media (prefers-color-scheme: dark) {
background: $color-container-dark;
}
} }
-1
View File
@@ -3,4 +3,3 @@
.u-hidden { display: none !important; } .u-hidden { display: none !important; }
.u-gap-sm { gap: $spacing-sm; } .u-gap-sm { gap: $spacing-sm; }
.text-muted { color: $color-muted; } .text-muted { color: $color-muted; }
+31 -5
View File
@@ -1,8 +1,34 @@
$color-bg: #ffffff; // Color Palette (Reference Style)
$color-sidebar: #570000; $color-bg: #f0f2f5;
$color-accent: #2563eb; $color-bg-dark: #121212;
$color-container: #ffffff;
$color-container-dark: #333333;
$color-sidebar: #0f172a;
$color-primary: #2b79c2;
$color-primary-hover: #1d5fa0;
$color-accent: #2b79c2;
$color-muted: #6b7280; $color-muted: #6b7280;
$color-text: #111827;
$color-text-dark: #e2e2e2;
// Status Colors
$color-status-todo: #ffcccc;
$color-status-todo-text: #a00;
$color-status-inprogress: #fff3cd;
$color-status-inprogress-text: #856404;
$color-status-done: #d4edda;
$color-status-done-text: #155724;
$color-status-archived: #e0e0e0;
$color-status-archived-text: #666666;
// Action Colors
$color-logout: #ff4d4d;
$color-logout-hover: #e60000;
// Spacing
$spacing-sm: 8px; $spacing-sm: 8px;
$spacing-md: 16px; $spacing-md: 16px;
$border-radius: 6px; $spacing-lg: 2rem;
$rundbgbackgroundcolor: #a0a0a0; $border-radius: 0.5rem;
$border-radius-lg: 10px;
@@ -0,0 +1,82 @@
@use "../variables" as *;
.ticket-list {
list-style: none;
padding: 0;
margin: 0;
margin-bottom: $spacing-lg;
li {
border-left: 4px solid;
padding: 1rem;
margin-bottom: 1rem;
border-radius: $border-radius-lg;
background: $color-container;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background: $color-container-dark;
}
h3 {
margin-top: 0;
margin-bottom: $spacing-md;
color: $color-primary;
a {
color: $color-primary;
text-decoration: none;
transition: color 0.2s ease-in-out;
&:hover {
color: $color-primary-hover;
text-decoration: underline;
}
}
}
p {
margin: 0.3rem 0;
}
&.To-Do {
border-left-color: $color-status-todo-text;
}
&.InProgress {
border-left-color: $color-status-inprogress-text;
}
&.Completed {
border-left-color: $color-status-done-text;
}
&.Archived {
border-left-color: $color-status-archived-text;
}
}
}
.ticket-list-actions {
display: flex;
gap: $spacing-md;
flex-wrap: wrap;
margin-top: $spacing-lg;
a {
display: inline-block;
padding: $spacing-md $spacing-lg;
background: $color-primary;
color: white;
text-decoration: none;
border-radius: $border-radius;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: $color-primary-hover;
}
}
}
@@ -0,0 +1,13 @@
@use "../variables" as *;
.user-list {
list-style: none;
padding-left: 0;
margin: 0;
li {
h3 {
margin-top: 0;
}
}
}
@@ -8,14 +8,18 @@
} }
.diagnostics-section { .diagnostics-section {
background-color: $color-bg; background-color: $color-container;
border-radius: $border-radius; border-radius: $border-radius;
padding: $spacing-md; padding: $spacing-md;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background-color: $color-container-dark;
}
h3 { h3 {
margin: 0 0 $spacing-md 0; margin: 0 0 $spacing-md 0;
color: #1f2937; color: $color-primary;
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 600; font-weight: 600;
} }
@@ -27,7 +31,12 @@
gap: 12px; gap: 12px;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
border-radius: $border-radius; border-radius: $border-radius;
background-color: #f9fafb; background-color: $color-container;
@media (prefers-color-scheme: dark) {
background-color: $color-container-dark;
border-color: #555;
}
} }
.weekday-bars { .weekday-bars {
@@ -50,7 +59,7 @@
.bar { .bar {
width: 100%; width: 100%;
background-color: $color-accent; background-color: $color-primary;
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
@@ -76,8 +85,12 @@
.day { .day {
font-weight: 600; font-weight: 600;
font-size: 12px; font-size: 12px;
color: #1f2937; color: $color-text;
margin-bottom: 2px; margin-bottom: 2px;
@media (prefers-color-scheme: dark) {
color: $color-text-dark;
}
} }
.value { .value {
@@ -94,7 +107,12 @@
padding: $spacing-md; padding: $spacing-md;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
border-radius: $border-radius; border-radius: $border-radius;
background-color: #f9fafb; background-color: $color-container;
@media (prefers-color-scheme: dark) {
background-color: $color-container-dark;
border-color: #555;
}
} }
.room-bar-item { .room-bar-item {
@@ -110,8 +128,12 @@
.room-label { .room-label {
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
color: #1f2937; color: $color-text;
min-width: 50px; min-width: 50px;
@media (prefers-color-scheme: dark) {
color: $color-text-dark;
}
} }
.room-count { .room-count {
@@ -128,9 +150,13 @@
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
@media (prefers-color-scheme: dark) {
background-color: #555;
}
.room-bar { .room-bar {
height: 100%; height: 100%;
background-color: #570000; background-color: $color-primary;
transition: width 0.3s ease; transition: width 0.3s ease;
} }
} }
@@ -0,0 +1,97 @@
@use "../variables" as *;
.form-container {
display: flex;
flex-direction: column;
gap: $spacing-lg;
margin-bottom: $spacing-lg;
form {
display: flex;
flex-direction: column;
gap: $spacing-md;
label {
display: flex;
flex-direction: column;
gap: $spacing-sm;
font-weight: 500;
color: $color-text;
@media (prefers-color-scheme: dark) {
color: $color-text-dark;
}
}
input, textarea, select {
padding: $spacing-md;
border: 1px solid #ccc;
border-radius: $border-radius;
font-family: Arial, sans-serif;
font-size: 1rem;
@media (prefers-color-scheme: dark) {
background-color: #6b6b6b;
color: $color-text-dark;
border-color: #555;
}
&:focus {
outline: none;
border-color: $color-primary;
box-shadow: 0 0 0 2px rgba($color-primary, 0.2);
}
}
textarea {
min-height: 120px;
resize: vertical;
}
button {
align-self: flex-start;
padding: $spacing-md $spacing-lg;
background-color: $color-primary;
color: white;
border: none;
border-radius: $border-radius;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: $color-primary-hover;
}
&:active {
transform: scale(0.98);
}
}
a {
display: inline-block;
padding: $spacing-md $spacing-lg;
background: $color-primary;
color: white;
text-decoration: none;
border-radius: $border-radius;
text-align: center;
transition: background-color 0.2s ease-in-out;
align-self: flex-start;
&:hover {
background-color: $color-primary-hover;
}
}
}
ul {
list-style: none;
padding-left: 0;
li h3 {
margin-top: 0;
}
}
}
@@ -1,7 +0,0 @@
.headline {
font-size: 40px;
font-weight: bold;
text-align: center;
color: #570000;
margin-top: 150px;
}
-17
View File
@@ -1,17 +0,0 @@
.csg-icon {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 9999;
width: auto;
height: auto;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
img {
width: auto;
height: auto;
}
}
@@ -0,0 +1,39 @@
@use "../variables" as *;
.login-btn {
display: inline-block;
padding: 12px 400px;
background: $color-primary;
color: white;
border-radius: $border-radius-lg;
text-decoration: none;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
border: none;
transition: all 0.2s ease-in-out;
margin-bottom: 15px;
&:hover {
background: $color-primary-hover;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
}
.logout-btn {
display: inline-block;
padding: 10px 20px;
background: $color-logout;
color: white;
border-radius: $border-radius-lg;
text-decoration: none;
font-weight: bold;
transition: 0.2s ease-in-out;
margin-bottom: 20px;
&:hover {
background: $color-logout-hover;
transform: scale(1.05);
}
}
+272
View File
@@ -0,0 +1,272 @@
@use "../variables" as *;
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-lg;
padding-bottom: $spacing-md;
border-bottom: 2px solid #e5e7eb;
@media (prefers-color-scheme: dark) {
border-bottom-color: #555;
}
h1 {
margin: 0;
color: $color-primary;
font-size: 1.75rem;
}
}
.page-actions {
display: flex;
gap: $spacing-md;
flex-wrap: wrap;
a, button {
display: inline-block;
padding: $spacing-md;
background: $color-primary;
color: white;
text-decoration: none;
border: none;
border-radius: $border-radius;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: $color-primary-hover;
}
}
}
input[type="checkbox"] {
width: fit-content;
}
.grid-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: $spacing-lg;
margin-top: $spacing-lg;
.grid-item {
background: $color-container;
border: 1px solid #e5e7eb;
border-radius: $border-radius;
padding: $spacing-md;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.2s ease, transform 0.2s ease;
@media (prefers-color-scheme: dark) {
background: $color-container-dark;
border-color: #555;
}
&:hover {
box-shadow: 0 0 15px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
h3 {
color: $color-primary;
margin-top: 0;
margin-bottom: $spacing-md;
}
p {
margin: $spacing-sm 0;
color: $color-muted;
&:last-child {
margin-bottom: 0;
}
}
.item-actions {
display: flex;
gap: $spacing-md;
margin-top: $spacing-md;
flex-wrap: wrap;
a, button {
flex: 1;
min-width: 100px;
padding: $spacing-md;
border: none;
border-radius: $border-radius;
font-weight: bold;
cursor: pointer;
text-decoration: none;
text-align: center;
transition: background-color 0.2s ease-in-out;
}
a.view, button.view {
background: $color-primary;
color: white;
&:hover {
background-color: $color-primary-hover;
}
}
a.edit, button.edit {
background: #f59e0b;
color: white;
&:hover {
background-color: #d97706;
}
}
a.delete, button.delete {
background: $color-logout;
color: white;
&:hover {
background-color: $color-logout-hover;
}
}
}
}
}
.table-container {
overflow-x: auto;
margin-top: $spacing-lg;
border-radius: $border-radius;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
table {
width: 100%;
border-collapse: collapse;
background: $color-container;
@media (prefers-color-scheme: dark) {
background: $color-container-dark;
}
thead {
background: $color-primary;
color: white;
th {
padding: $spacing-md;
text-align: left;
font-weight: 600;
border-bottom: 2px solid darken($color-primary, 10%);
}
}
tbody {
tr {
border-bottom: 1px solid #e5e7eb;
transition: background-color 0.2s ease;
@media (prefers-color-scheme: dark) {
border-bottom-color: #555;
}
&:hover {
background-color: #f9fafb;
@media (prefers-color-scheme: dark) {
background-color: #444;
}
}
td {
padding: $spacing-md;
color: $color-text;
@media (prefers-color-scheme: dark) {
color: $color-text-dark;
}
}
}
}
}
}
.alert {
padding: $spacing-md;
border-radius: $border-radius;
margin-bottom: $spacing-lg;
border-left: 4px solid;
&.success {
background: $color-status-done;
color: $color-status-done-text;
border-left-color: $color-status-done-text;
}
&.error {
background: $color-status-todo;
color: $color-status-todo-text;
border-left-color: $color-status-todo-text;
}
&.warning {
background: $color-status-inprogress;
color: $color-status-inprogress-text;
border-left-color: $color-status-inprogress-text;
}
&.info {
background: #dbeafe;
color: #1e40af;
border-left-color: #1e40af;
@media (prefers-color-scheme: dark) {
background: #1e3a8a;
color: #93c5fd;
border-left-color: #93c5fd;
}
}
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #e5e7eb;
border-top-color: $color-primary;
border-radius: 50%;
animation: spin 0.8s linear infinite;
@keyframes spin {
to { transform: rotate(360deg); }
}
}
.empty-state {
text-align: center;
padding: $spacing-lg;
color: $color-muted;
p {
font-size: 1.1rem;
margin-bottom: $spacing-md;
}
a {
display: inline-block;
padding: $spacing-md $spacing-lg;
background: $color-primary;
color: white;
text-decoration: none;
border-radius: $border-radius;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: $color-primary-hover;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
@use "../variables" as *;
.setup-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: $color-bg;
padding: $spacing-lg;
@media (prefers-color-scheme: dark) {
background: $color-bg-dark;
}
}
.setup-box {
width: 100%;
max-width: 400px;
background: $color-container;
padding: $spacing-lg;
border-radius: 1rem;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background: $color-container-dark;
}
h1 {
color: $color-primary;
text-align: center;
margin-top: 0;
margin-bottom: $spacing-md;
}
p {
text-align: center;
color: $color-muted;
margin-bottom: $spacing-lg;
}
}
.setup-form {
display: flex;
flex-direction: column;
gap: $spacing-md;
.form-group {
display: flex;
flex-direction: column;
gap: $spacing-sm;
label {
font-weight: 500;
color: $color-text;
@media (prefers-color-scheme: dark) {
color: $color-text-dark;
}
}
}
input {
padding: $spacing-md;
border: 1px solid #ccc;
border-radius: $border-radius;
font-family: Arial, sans-serif;
font-size: 1rem;
@media (prefers-color-scheme: dark) {
background-color: #6b6b6b;
color: $color-text-dark;
border-color: #555;
}
&:focus {
outline: none;
border-color: $color-primary;
box-shadow: 0 0 0 2px rgba($color-primary, 0.2);
}
}
button {
padding: $spacing-md;
background-color: $color-primary;
color: white;
border: none;
border-radius: $border-radius;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
margin-top: $spacing-md;
&:hover {
background-color: $color-primary-hover;
}
&:active {
transform: scale(0.98);
}
}
}
.success-message {
background: $color-status-done;
color: $color-status-done-text;
padding: $spacing-md;
border-radius: $border-radius;
margin-bottom: $spacing-md;
border-left: 4px solid $color-status-done-text;
}
.error-message {
background: $color-status-todo;
color: $color-status-todo-text;
padding: $spacing-md;
border-radius: $border-radius;
margin-bottom: $spacing-md;
border-left: 4px solid $color-status-todo-text;
}
+11 -14
View File
@@ -4,31 +4,30 @@
.sidebar { .sidebar {
width: 260px; width: 260px;
background: $color-sidebar; background: $color-sidebar;
color: #fffefe; color: #fff;
min-height: 100%; min-height: 100%;
padding: $spacing-md; padding: $spacing-md;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: $spacing-sm; flex: 1; } ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: $spacing-sm; flex: 1; }
a, .menu-toggle { a, .menu-toggle {
color: #ffffff; color: #fff;
text-decoration: none; text-decoration: none;
display: block; display: block;
padding: 8px 12px; padding: 8px 12px;
border-radius: 4px; border-radius: $border-radius;
&:hover { background: rgba(158, 45, 1, 0.842); } transition: background 0.2s ease-in-out;
&:hover { background: rgba(255,255,255,0.1); }
} }
.menu-toggle { background: #ff5a316c; border: none; text-align: left; width: 100%; cursor: pointer; } .menu-toggle { background: transparent; border: none; text-align: left; width: 100%; cursor: pointer; }
.submenu { .submenu {
margin-left: 8px; margin-left: 8px;
background: rgba(255,255,255,0.02); background: rgba(255,255,255,0.05);
border-radius: 4px; border-radius: $border-radius;
padding: 6px; padding: 6px;
li { padding: 4px 0; } li { padding: 4px 0; }
} }
@@ -40,16 +39,14 @@
} }
.logout-button { .logout-button {
background: rgba(255, 93, 43, 0.527); background: rgba(255, 255, 255, 0.1);
color: #ffffff; color: #fff;
border: 1px solid rgba(255, 255, 255, 0.801); border: 1px solid rgba(255, 255, 255, 0.2);
padding: 8px 12px; padding: 8px 12px;
border-radius: 4px; border-radius: 4px;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
&:hover { background: rgba(255,255,255,0.15); }
&:active { background: rgba(255,255,255,0.2); }
} }
&.user { width: 220px; background: darken($color-sidebar, 6%); } &.user { width: 220px; background: darken($color-sidebar, 6%); }
+51 -27
View File
@@ -1,54 +1,78 @@
@use "../mixins" as *; @use "../mixins" as *;
@use "../variables" as *; @use "../variables" as *;
.ticket {
border: 1px solid #ccc;
padding: 1rem;
margin-bottom: 1rem;
border-radius: $border-radius-lg;
background: $color-container;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background: $color-container-dark;
}
h3 {
margin-top: 0;
color: $color-primary;
}
p {
margin: 0.3rem 0;
}
}
.ticket-card { .ticket-card {
@include card(); @include card();
border-left: 4px solid $color-accent; border-left: 4px solid $color-primary;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $spacing-sm; gap: $spacing-sm;
.meta { color: $color-muted; font-size: 0.9rem; } .meta { color: $color-muted; font-size: 0.9rem; }
.title { font-weight: 600; } .title { font-weight: 600; }
} }
.listbox { .status {
max-width: auto; display: inline-block;
margin:0 0; padding: 4px 10px;
background-color: $rundbgbackgroundcolor; border-radius: 8px;
padding-bottom: 96px; font-size: 0.8rem;
color: white; font-weight: bold;
padding-left: 32px; margin-top: 8px;
padding-right: 32px;
padding-top: 96px;
border-radius: 20px;
border-color: #000000;
border-style: double;
box-shadow: 10px 10px 10px rgba(43, 43, 43, 0.8);
font-size: 26px;
list-style-type: none;
&.To-Do { &.To-Do {
background-color: #b1003b; background: $color-status-todo;
color: $color-status-todo-text;
} }
&.InProgress { &.InProgress {
background-color: #fff385; background: $color-status-inprogress;
color: $color-status-inprogress-text;
}
&.Done {
background: $color-status-done;
color: $color-status-done-text;
} }
&.Completed { &.Completed {
background-color: #90ff82; background: $color-status-done;
color: $color-status-done-text;
} }
&.Archived { &.Archived {
background-color: #af69ee; background: $color-status-archived;
color: $color-status-archived-text;
} }
} }
.postits { .ticket_count {
display: grid; text-align: center;
grid-template-columns: auto auto auto; align-content: center;
gap: 8px; color: #005f00;
border: 2px solid #848484;
border-radius: 10px;
height: 50px;
width: 250px;
} }
+138 -72
View File
@@ -3,107 +3,173 @@
@use "utilities"; @use "utilities";
@use "components/sidebar"; @use "components/sidebar";
@use "components/tickets"; @use "components/tickets";
@use "components/alltickets";
@use "components/allusers";
@use "components/forms";
@use "components/login";
@use "components/diagnostics"; @use "components/diagnostics";
@use "components/frontpage"; @use "components/pages";
@use "components/home"; @use "components/setup";
@use "components/icon";
* {
box-sizing: border-box;
}
body { body {
background: variables.$color-bg; background: variables.$color-bg;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; font-family: Arial, sans-serif;
color: #111827; color: variables.$color-text;
margin: 0; margin: 0;
padding: 0;
@media (prefers-color-scheme: dark) {
background: variables.$color-bg-dark;
color: variables.$color-text-dark;
}
}
input, textarea, select {
width: 100%;
padding: variables.$spacing-md;
font-size: 1rem;
border-radius: variables.$border-radius;
border: 1px solid #ccc;
box-sizing: border-box;
margin-bottom: 15px;
font-family: Arial, sans-serif;
@media (prefers-color-scheme: dark) {
background-color: #6b6b6b;
color: variables.$color-text-dark;
border-color: #555;
}
}
button {
padding: variables.$spacing-md;
font-size: 1rem;
border-radius: variables.$border-radius;
background-color: variables.$color-primary;
color: white;
border: none;
cursor: pointer;
margin-bottom: 15px;
transition: background-color 0.2s ease-in-out;
font-family: Arial, sans-serif;
font-weight: bold;
&:hover {
background-color: variables.$color-primary-hover;
}
}
form {
display: flex;
flex-direction: column;
gap: variables.$spacing-md;
label {
display: flex;
flex-direction: column;
gap: variables.$spacing-sm;
font-weight: 500;
}
a {
display: inline-block;
padding: variables.$spacing-md;
background: variables.$color-primary;
color: white;
text-decoration: none;
border-radius: variables.$border-radius;
text-align: center;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: variables.$color-primary-hover;
}
}
}
.container {
max-width: 900px;
margin: auto;
background: variables.$color-container;
padding: variables.$spacing-lg;
border-radius: 1rem;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background: variables.$color-container-dark;
}
}
.header-with-image {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-with-image img {
height: 60px;
} }
.admin { display: flex; } .admin { display: flex; }
.layout { .layout {
display: flex; display: flex;
height: 100vh; min-height: 100vh;
overflow: hidden; margin: 0;
box-sizing: border-box; padding: 0;
} }
.sidebar { .sidebar {
width: 260px; width: 260px;
flex-shrink: 0; flex-shrink: 0;
height: 100%; position: fixed;
left: 0;
top: 0;
height: 100vh;
overflow-y: auto; overflow-y: auto;
box-sizing: border-box;
} }
.content { .content {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
height: 100%; margin-left: 260px;
overflow-y: auto; padding: variables.$spacing-lg;
padding: variables.$spacing-md;
box-sizing: border-box; > :first-child {
max-width: 900px;
margin: 0 auto;
background: variables.$color-container;
padding: variables.$spacing-lg;
border-radius: 1rem;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
@media (prefers-color-scheme: dark) {
background: variables.$color-container-dark;
}
}
h1, h2, h3 {
color: variables.$color-primary;
margin-top: 0;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.sidebar { .sidebar {
position: fixed; position: fixed;
top: 0; left: -260px;
bottom: 0; transition: left .3s ease-in-out;
left: -100%;
transition: left .2s;
z-index: 1000; z-index: 1000;
height: 100%;
box-sizing: border-box;
} }
.sidebar.open { left: 0; } .sidebar.open {
.content { margin-left: 0; padding: variables.$spacing-md; box-sizing: border-box; } left: 0;
}
.rundbg {
display: flex;
justify-content: center;
flex-direction: column;
max-width: 40%;
margin:0 auto;
background-color: variables.$rundbgbackgroundcolor;
padding-bottom: 96px;
padding-left: 32px;
padding-right: 32px;
padding-top: 96px;
border-radius: 20px;
gap: 8px;
border-color: #000000;
border-style: double;
box-shadow: 20px 20px 10px rgb(43, 43, 43);
font-size: 26px;
label {
display: block;
width: 100%;
box-sizing: border-box;
} }
.content {
input, select, button { margin-left: 0;
width: 100%;
box-sizing: border-box;
padding: 16px;
font-size: 1.8rem;
background-color: #f0f0f0;
box-shadow: #6d6d6d;
box-shadow: 6px 6px 8px;
}
input[type="checkbox"] {
width: auto;
}
button {
margin: 16px 0;
}
&.login {
margin-top: 96px;
} }
} }
.setup-form input {
width: 100%;
box-sizing: border-box;
}