feat: basic index and new availabilly page
This commit is contained in:
parent
066cc62262
commit
497e6fbf6c
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -372,6 +372,16 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_actix"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4b0dd17cfe203b00ba3853a89fba459ecf24c759b738b244133330607c78e55"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"askama",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_derive"
|
||||
version = "0.12.1"
|
||||
@ -635,6 +645,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"askama",
|
||||
"askama_actix",
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"serde",
|
||||
@ -714,6 +725,7 @@ dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
@ -8,12 +8,13 @@ edition = "2021"
|
||||
[dependencies]
|
||||
sqlx = { version = "0.6", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
|
||||
actix-web = { version = "4" }
|
||||
askama = "0.12.0"
|
||||
serde = { version = "1.0.164", features = [ "derive"]}
|
||||
askama = { version = "0.12.0", features = ["with-actix-web"] }
|
||||
serde = { version = "1.0.164", features = ["derive"] }
|
||||
argon2 = { version = "0.5.0", features = [ "std"]}
|
||||
anyhow = "1.0.71"
|
||||
dotenv = "0.15.0"
|
||||
actix-session = { version = "0.7.2", features = ["cookie-session"] }
|
||||
actix-identity = "0.5.2"
|
||||
chrono = "0.4.33"
|
||||
chrono = { version = "0.4.33", features = ["serde"] }
|
||||
actix-files = "0.6.5"
|
||||
askama_actix = "0.14.0"
|
||||
|
41
src/calendar/get_availabillity_new.rs
Normal file
41
src/calendar/get_availabillity_new.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use askama_actix::TemplateToResponse;
|
||||
use chrono::NaiveDate;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::{role::Role, user::User};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "availabillity_new.html")]
|
||||
struct AvailabillityNewTemplate {
|
||||
user_role: Role,
|
||||
date: NaiveDate
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvailabillityNewQuery {
|
||||
date: NaiveDate,
|
||||
}
|
||||
|
||||
#[actix_web::get("/availabillity/new")]
|
||||
pub async fn get_availabillity_new(
|
||||
user: Option<Identity>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<AvailabillityNewQuery>,
|
||||
) -> impl Responder {
|
||||
|
||||
if let Some(user) = user {
|
||||
let current_user = User::read_by_id(pool.as_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
|
||||
|
||||
let template = AvailabillityNewTemplate { user_role: current_user.role, date: query.date };
|
||||
|
||||
template.to_response()
|
||||
} else {
|
||||
HttpResponse::PermanentRedirect()
|
||||
.insert_header((LOCATION, "/login"))
|
||||
.finish()
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
pub mod routes;
|
||||
mod get_availabillity_new;
|
||||
|
||||
pub use routes::init;
|
||||
|
@ -1,26 +1,66 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::{role::Role, user::User};
|
||||
use crate::models::{
|
||||
area::Area, availabillity::Availabillity, event::Event, role::Role, user::User,
|
||||
};
|
||||
|
||||
use super::get_availabillity_new::get_availabillity_new;
|
||||
|
||||
|
||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_index);
|
||||
cfg.service(get_availabillity_new);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CalendarQuery {
|
||||
date: Option<NaiveDate>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct CalendarTemplate {
|
||||
user_role: Role,
|
||||
date: NaiveDate,
|
||||
area: Area,
|
||||
events: Vec<Event>,
|
||||
availabillities: Vec<Availabillity>,
|
||||
}
|
||||
|
||||
#[actix_web::get("/")]
|
||||
async fn get_index(user: Option<Identity>, pool: web::Data<PgPool>) -> impl Responder {
|
||||
async fn get_index(
|
||||
user: Option<Identity>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<CalendarQuery>,
|
||||
) -> impl Responder {
|
||||
if let Some(user) = user {
|
||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
|
||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let date = match query.date {
|
||||
Some(given_date) => given_date,
|
||||
None => Utc::now().date_naive(),
|
||||
};
|
||||
let area = Area::read_by_id(pool.get_ref(), current_user.area_id)
|
||||
.await
|
||||
.unwrap();
|
||||
let events = Event::read_by_date(pool.get_ref(), date).await.unwrap();
|
||||
let availabillities = Availabillity::read_by_date(pool.get_ref(), date)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let template = CalendarTemplate{ user_role: current_user.role };
|
||||
let template = CalendarTemplate {
|
||||
user_role: current_user.role,
|
||||
date,
|
||||
area,
|
||||
events,
|
||||
availabillities,
|
||||
};
|
||||
|
||||
HttpResponse::Ok().body(template.render().unwrap())
|
||||
} else {
|
||||
|
@ -12,8 +12,8 @@ impl Area {
|
||||
Ok(result.id)
|
||||
}
|
||||
|
||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> anyhow::Result<Option<Area>> {
|
||||
let record = query_as!(Area, "SELECT * FROM area WHERE id = $1", id).fetch_optional(pool).await?;
|
||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> anyhow::Result<Area> {
|
||||
let record = query_as!(Area, "SELECT * FROM area WHERE id = $1", id).fetch_one(pool).await?;
|
||||
|
||||
Ok(record)
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use sqlx::{query, PgPool};
|
||||
|
||||
pub struct Availabillity {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub date: NaiveDate,
|
||||
pub start_time: Option<NaiveTime>,
|
||||
pub end_time: Option<NaiveTime>
|
||||
}
|
||||
|
||||
impl Availabillity {
|
||||
pub async fn create(pool: &PgPool, user_id: i32, date: NaiveDate, start_time: Option<NaiveTime>, end_time: Option<NaiveTime>) -> anyhow::Result<i32> {
|
||||
let mut result = match (start_time, end_time) {
|
||||
(Some(start_time), Some(end_time)) => query!("INSERT INTO availabillity (userId, date, startTime, endTime) VALUES ($1, $2, $3, $4) RETURNING id;", user_id, date, start_time, end_time).fetch_one(pool).await?.id,
|
||||
(_, _) => query!("INSERT INTO availabillity (userId, date) VALUES ($1, $2) RETURNING id;", user_id, date).fetch_one(pool).await?.id
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn read_by_date(pool: &PgPool, date: NaiveDate) -> anyhow::Result<Vec<Availabillity>> {
|
||||
let records = query!("SELECT * FROM availabillity WHERE date = $1", date)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let availabillities = records
|
||||
.iter()
|
||||
.map(|a| Availabillity {
|
||||
id: a.id,
|
||||
user_id: a.userid,
|
||||
date: a.date,
|
||||
start_time: a.starttime,
|
||||
end_time: a.endtime
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(availabillities)
|
||||
}
|
||||
}
|
@ -1,9 +1,38 @@
|
||||
use sqlx::{query, PgPool};
|
||||
|
||||
pub struct Location {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub areaId: i32
|
||||
pub area_id: i32,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub async fn create(pool: &PgPool, name: &str, area_id: i32) -> anyhow::Result<i32> {
|
||||
let result = query!(
|
||||
"INSERT INTO location (name, areaId) VALUES ($1, $2) RETURNING id;",
|
||||
name,
|
||||
area_id
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(result.id)
|
||||
}
|
||||
|
||||
pub async fn read_by_area(pool: &PgPool, area_id: i32) -> anyhow::Result<Vec<Location>> {
|
||||
let records = query!("SELECT * FROM location WHERE areaId = $1;", area_id)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let locations = records
|
||||
.iter()
|
||||
.map(|lr| Location {
|
||||
id: lr.id,
|
||||
name: lr.name.to_string(),
|
||||
area_id: lr.areaid,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(locations)
|
||||
}
|
||||
}
|
||||
|
110
templates/availabillity_new.html
Normal file
110
templates/availabillity_new.html
Normal file
@ -0,0 +1,110 @@
|
||||
{% extends "nav.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<form>
|
||||
<h1 class="title">Neue Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Funktion</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="function">
|
||||
<option value="1">Posten</option>
|
||||
<option value="10">Wachhabender</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Zeitangabe</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="radio">
|
||||
<input type="radio" id="wholeDay" name="hasTime" checked>
|
||||
ganztägig
|
||||
<label class="radio">
|
||||
<input type="radio" id="partDay" name="hasTime">
|
||||
zeitweise
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Von - Bis</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="from" value="00:00" disabled>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="till" value="23:59" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label"></div>
|
||||
<div class="field-body">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input class="button is-link" type="submit" value="Erstellen">
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-link is-light" href="/?date={{ date }}">Zurück</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const wholeDay = document.getElementById("wholeDay");
|
||||
const partDay = document.getElementById("partDay");
|
||||
|
||||
const from = document.getElementById("from");
|
||||
const till = document.getElementById("till");
|
||||
|
||||
let lastFrom = null;
|
||||
let lastTill = null;
|
||||
|
||||
wholeDay.addEventListener("click", (event) => {
|
||||
from.disabled = true
|
||||
till.disabled = true
|
||||
|
||||
lastFrom = from.value;
|
||||
lastTill = till.value;
|
||||
|
||||
from.value = null;
|
||||
till.value = null;
|
||||
});
|
||||
|
||||
partDay.addEventListener("click", (event) => {
|
||||
from.disabled = false
|
||||
till.disabled = false
|
||||
|
||||
if (lastFrom != null) {
|
||||
from.value = lastFrom;
|
||||
}
|
||||
|
||||
if (lastTill != null) {
|
||||
till.value = lastTill;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -5,7 +5,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Brass - Brasiwa Leipzig</title>
|
||||
<link rel="stylesheet" href="bulma.css">
|
||||
<link rel="stylesheet" href="/bulma.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -3,10 +3,72 @@
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title">
|
||||
Vefügbarkeit für |datum|
|
||||
</h1>
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<a class="button is-link level-item" href="/?date={{ date.pred() }}">←</a>
|
||||
</div>
|
||||
<div class="control level-item is-flex-grow-0">
|
||||
<input class="input" type="date" value="{{ date }}">
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<a class="button is-link level-item" href="/?date={{ date.succ() }}">→</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h3 class="title is-3">
|
||||
Events am {{ date.format("%d.%m.%Y") }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<a class="button" href="/event/new">Neues Event für diesen Tag</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if events.len() == 0 %}
|
||||
<div class="box">
|
||||
<h5 class="title is-5">keine Events geplant</h5>
|
||||
</div>
|
||||
{% else %}
|
||||
{% for event in events %}
|
||||
<div class="box">
|
||||
<h5 class="title is-5">{{ event.name }}</h5>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h3 class="title is-3">
|
||||
Verfügbarkeiten am {{ date.format("%d.%m.%Y") }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<a class="button" href="/availabillity/new?date={{ date }}">Neue Verfügbarkeit für diesen Tag</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if events.len() == 0 %}
|
||||
<div class="box">
|
||||
<h5 class="title is-5">keine Verfügbarkeiten eingetragen</h5>
|
||||
</div>
|
||||
{% else %}
|
||||
{% for availabillity in availabillities %}
|
||||
<div class="box">
|
||||
<h5 class="title is-5">{{ availabillity.user_id }}</h5>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user