Compare commits

..

2 Commits

14 changed files with 227 additions and 22 deletions

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n SELECT\n event.id AS eventId,\n event.startTimestamp,\n event.endTimestamp,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\n event.voluntaryFuehrungsassistent,\n event.amountOfPosten,\n event.clothing,\n event.canceled,\n event.note,\n location.id,\n location.name AS locationName,\n location.areaId AS locationAreaId,\n clothing.id AS clothingId,\n clothing.name AS clothingName\n FROM event\n JOIN location ON event.locationId = location.id\n JOIN clothing ON event.clothing = clothing.id\n WHERE starttimestamp::date >= $1\n AND starttimestamp::date <= $2\n AND location.areaId = $3;\n ", "query": "\n SELECT\n event.id AS eventId,\n event.startTimestamp,\n event.endTimestamp,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\n event.voluntaryFuehrungsassistent,\n event.amountOfPosten,\n event.clothing,\n event.canceled,\n event.note,\n location.id,\n location.name AS locationName,\n location.areaId AS locationAreaId,\n clothing.id AS clothingId,\n clothing.name AS clothingName\n FROM event\n JOIN location ON event.locationId = location.id\n JOIN clothing ON event.clothing = clothing.id\n WHERE starttimestamp::date >= $1\n AND starttimestamp::date <= $2\n AND location.areaId = $3\n ORDER BY event.starttimestamp;\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -110,5 +110,5 @@
false false
] ]
}, },
"hash": "10b4b80f351b66ac5e778a3031288ac5dc66efd0a66b38b7e30f4c954df91bdf" "hash": "65367483e39e07cd0aa142f9bb76c7a5d6dd0611e6b41edd5a593c9f955b5d04"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n SELECT\n availability.id,\n availability.userId,\n availability.startTimestamp,\n availability.endTimestamp,\n availability.comment\n FROM availability\n WHERE availability.userId = $1\n AND availability.starttimestamp::date >= $2\n AND availability.endtimestamp::date <= $3;\n ", "query": "\n SELECT\n availability.id,\n availability.userId,\n availability.startTimestamp,\n availability.endTimestamp,\n availability.comment\n FROM availability\n WHERE availability.userId = $1\n AND availability.starttimestamp::date >= $2\n AND availability.endtimestamp::date <= $3\n ORDER BY availability.starttimestamp;\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -44,5 +44,5 @@
true true
] ]
}, },
"hash": "f60053118df6a791d31fa258ee3737881f8f97ca41cbebd92eb22c967292d2ee" "hash": "66638de4a321ba610206a9f5c237cb17eb5221c1dbe4d4c5e83167b2d2807db4"
} }

View File

@ -0,0 +1,59 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n assignment.eventId,\n assignment.availabilityId,\n assignment.function AS \"function: Function\",\n assignment.startTimestamp,\n assignment.endTimestamp\n FROM assignment\n JOIN availability ON assignment.availabilityId = availability.id\n WHERE assignment.starttimestamp::date >= $1\n AND assignment.starttimestamp::date <= $2\n AND availability.userId = $3\n ORDER BY assignment.starttimestamp;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "availabilityid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 3,
"name": "starttimestamp",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "endtimestamp",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Date",
"Date",
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "6788747f70812d6a87833d821c9845f59b4338bc42a40050a68736ba30d937d5"
}

View File

@ -1,4 +1,4 @@
use chrono::NaiveDateTime; use chrono::{NaiveDate, NaiveDateTime};
use sqlx::{PgPool, query}; use sqlx::{PgPool, query};
use super::{AssignmentChangeset, Function, Result}; use super::{AssignmentChangeset, Function, Result};
@ -101,6 +101,47 @@ impl Assignment {
Ok(assignemnets) Ok(assignemnets)
} }
pub async fn read_all_by_daterange_and_user(
pool: &PgPool,
date_range: (&NaiveDate, &NaiveDate),
user_id: i32,
) -> Result<Vec<Assignment>> {
let records = query!(
r##"
SELECT
assignment.eventId,
assignment.availabilityId,
assignment.function AS "function: Function",
assignment.startTimestamp,
assignment.endTimestamp
FROM assignment
JOIN availability ON assignment.availabilityId = availability.id
WHERE assignment.starttimestamp::date >= $1
AND assignment.starttimestamp::date <= $2
AND availability.userId = $3
ORDER BY assignment.starttimestamp;
"##,
date_range.0,
date_range.1,
user_id
)
.fetch_all(pool)
.await?;
let assignemnets = records
.iter()
.map(|r| Assignment {
event_id: r.eventid,
availability_id: r.availabilityid,
function: r.function,
start: r.starttimestamp.naive_utc(),
end: r.endtimestamp.naive_utc(),
})
.collect();
Ok(assignemnets)
}
pub async fn read( pub async fn read(
pool: &PgPool, pool: &PgPool,
event_id: i32, event_id: i32,

View File

@ -357,7 +357,8 @@ impl Availability {
FROM availability FROM availability
WHERE availability.userId = $1 WHERE availability.userId = $1
AND availability.starttimestamp::date >= $2 AND availability.starttimestamp::date >= $2
AND availability.endtimestamp::date <= $3; AND availability.endtimestamp::date <= $3
ORDER BY availability.starttimestamp;
"##, "##,
user_id, user_id,
date_range.0, date_range.0,

View File

@ -124,7 +124,8 @@ impl Event {
JOIN clothing ON event.clothing = clothing.id JOIN clothing ON event.clothing = clothing.id
WHERE starttimestamp::date >= $1 WHERE starttimestamp::date >= $1
AND starttimestamp::date <= $2 AND starttimestamp::date <= $2
AND location.areaId = $3; AND location.areaId = $3
ORDER BY event.starttimestamp;
"#, "#,
date_range.0, date_range.0,
date_range.1, date_range.1,

View File

@ -7,11 +7,11 @@ use crate::{
filters, filters,
utils::{ utils::{
ApplicationError, ApplicationError,
DateTimeFormat::{DayMonthYearHourMinute, HourMinute, WeekdayDayMonthYear}, DateTimeFormat::{HourMinute, WeekdayDayMonthYearHourMinute},
TemplateResponse, TemplateResponse,
}, },
}; };
use brass_db::models::{Assignment, Availability, Event, Function, Role, User}; use brass_db::models::{Assignment, Availability, Event, Role, User};
#[derive(Template)] #[derive(Template)]
#[template(path = "overview.html")] #[template(path = "overview.html")]
@ -19,6 +19,7 @@ struct OverviewTemplate {
user: User, user: User,
events: Vec<Event>, events: Vec<Event>,
availabilities: Vec<Availability>, availabilities: Vec<Availability>,
assignments: Vec<(Assignment, String)>,
} }
#[actix_web::get("/")] #[actix_web::get("/")]
@ -28,22 +29,36 @@ async fn get(
) -> Result<impl Responder, ApplicationError> { ) -> Result<impl Responder, ApplicationError> {
let today = Utc::now().date_naive(); let today = Utc::now().date_naive();
let next_month = today.checked_add_months(Months::new(1)).unwrap(); let next_month = today.checked_add_months(Months::new(1)).unwrap();
let date_range = (&today, &next_month);
let events = Event::read_all_by_daterange_and_area_including_location( let events = Event::read_all_by_daterange_and_area_including_location(
pool.get_ref(), pool.get_ref(),
(&today, &next_month), date_range,
user.area_id, user.area_id,
) )
.await?; .await?;
let availabilities = let availabilities =
Availability::read_by_user_and_daterange(pool.get_ref(), user.id, (&today, &next_month)) Availability::read_by_user_and_daterange(pool.get_ref(), user.id, date_range).await?;
.await?;
let assignments =
Assignment::read_all_by_daterange_and_user(pool.get_ref(), date_range, user.id)
.await?
.into_iter()
.map(|a| {
if let Some(e) = events.iter().find(|e| e.id == a.event_id) {
(a, e.name.clone())
} else {
(a, String::new())
}
})
.collect();
let template = OverviewTemplate { let template = OverviewTemplate {
user: user.into_inner(), user: user.into_inner(),
events, events,
availabilities, availabilities,
assignments,
}; };
Ok(template.to_response()?) Ok(template.to_response()?)

View File

@ -8,6 +8,7 @@ pub enum DateTimeFormat {
HourMinute, HourMinute,
WeekdayDayMonth, WeekdayDayMonth,
WeekdayDayMonthYear, WeekdayDayMonthYear,
WeekdayDayMonthYearHourMinute,
} }
impl From<DateTimeFormat> for &'static str { impl From<DateTimeFormat> for &'static str {
@ -21,6 +22,7 @@ impl From<DateTimeFormat> for &'static str {
DateTimeFormat::HourMinute => "%H:%M", DateTimeFormat::HourMinute => "%H:%M",
DateTimeFormat::WeekdayDayMonth => "%A, %d.%m.", DateTimeFormat::WeekdayDayMonth => "%A, %d.%m.",
DateTimeFormat::WeekdayDayMonthYear => "%A, %d.%m.%Y", DateTimeFormat::WeekdayDayMonthYear => "%A, %d.%m.%Y",
DateTimeFormat::WeekdayDayMonthYearHourMinute => "%A, %d.%m.%Y %H:%M",
} }
} }
} }

View File

@ -23,5 +23,5 @@ pub fn get_return_url_for_date(date: &NaiveDate) -> String {
return String::from("/"); return String::from("/");
} }
format!("/?date={}", date) format!("/calendar?date={}", date)
} }

View File

@ -111,7 +111,7 @@
</button> </button>
</div> </div>
<div class="control"> <div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}"> <a class="button is-link is-light" hx-boost="true" href="/calendar?date={{ date }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" /> <use href="/static/feather-sprite.svg#arrow-left" />
</svg> </svg>

View File

@ -8,7 +8,7 @@
<div class="level is-hidden-mobile"> <div class="level is-hidden-mobile">
<div class="level-left"> <div class="level-left">
<a hx-boost="true" class="button is-link level-item" <a hx-boost="true" class="button is-link level-item"
href="/?date={{ date.pred() }}{{ selected_area|show_area_query(false) }}"> href="/calendar?date={{ date.pred() }}{{ selected_area|show_area_query(false) }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" /> <use href="/static/feather-sprite.svg#arrow-left" />
</svg> </svg>
@ -37,7 +37,7 @@
<div class="level-right"> <div class="level-right">
<a hx-boost="true" class="button is-link level-item" <a hx-boost="true" class="button is-link level-item"
href="/?date={{ date.succ() }}{{ selected_area|show_area_query(false) }}"> href="/calendar?date={{ date.succ() }}{{ selected_area|show_area_query(false) }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-right" /> <use href="/static/feather-sprite.svg#arrow-right" />
</svg> </svg>
@ -49,7 +49,7 @@
<div class="level is-hidden-tablet is-mobile"> <div class="level is-hidden-tablet is-mobile">
<div class="level-left"> <div class="level-left">
<a hx-boost="true" class="button is-link level-item" <a hx-boost="true" class="button is-link level-item"
href="/?date={{ date.pred() }}{{ selected_area|show_area_query(false) }}"> href="/calendar?date={{ date.pred() }}{{ selected_area|show_area_query(false) }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" /> <use href="/static/feather-sprite.svg#arrow-left" />
</svg> </svg>
@ -63,7 +63,7 @@
<div class="level-right"> <div class="level-right">
<a hx-boost="true" class="button is-link level-item" <a hx-boost="true" class="button is-link level-item"
href="/?date={{ date.succ() }}{{ selected_area|show_area_query(false) }}"> href="/calendar?date={{ date.succ() }}{{ selected_area|show_area_query(false) }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-right" /> <use href="/static/feather-sprite.svg#arrow-right" />
</svg> </svg>

View File

@ -225,7 +225,7 @@
</button> </button>
</div> </div>
<div class="control"> <div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}"> <a class="button is-link is-light" hx-boost="true" href="/calendar?date={{ date }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" /> <use href="/static/feather-sprite.svg#arrow-left" />
</svg> </svg>

View File

@ -64,7 +64,7 @@
</div> </div>
<div class="control"> <div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date={{ event.start.date() }}"> <a class="button is-link is-light" hx-boost="true" href="/calendar?date={{ event.start.date() }}">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" /> <use href="/static/feather-sprite.svg#arrow-left" />
</svg> </svg>

View File

@ -1,9 +1,95 @@
{% extends "nav.html" %} {% extends "nav.html" %}
{% block content %} {% block content %}
<section id="progress" class="section"> <section class="section">
<div class="container"> <div class="container">
Übersicht <h1 class="title is-1">Übersicht</h1>
<h3 class="title is-3">geplante Veranstaltungen</h3>
<p class="subtitle is-5">in den nächsten 31 Tagen</p>
{% if events.len() == 0 %}
<div class="notification">
Keine Veranstaltungen für diesen Zeitraum geplant.
</div>
{% else %}
<div class="panel p-2">
{% for e in events %}
<div class="panel-block is-justify-content-space-between">
<span>
<b>{{ e.name }}</b> &nbsp; {{ e.start|fmt_datetime(WeekdayDayMonthYearHourMinute) }} - {{
e.end|fmt_datetime(HourMinute) }}
</span>
<a class="button is-small is-link is-light" href="/calendar?date={{ e.start.date() }}">
<svg class="icon">
<use href="/static/feather-sprite.svg#calendar" />
</svg>
<span>im Kalender anzeigen</span>
</a>
</div>
{% endfor %}
</div>
{% endif %}
<h3 class="title is-3">Deine Verfügbarkeiten</h3>
<p class="subtitle is-5">in den nächsten 31 Tagen</p>
{% if availabilities.len() == 0 %}
<div class="notification">
Keine Verfügbarkeiten für diesen Zeitraum hinterlegt.
</div>
{% else %}
<div class="panel p-2">
{% for a in availabilities %}
<div class="panel-block is-justify-content-space-between">
<span>
{{ a.start|fmt_datetime(WeekdayDayMonthYearHourMinute) }} - {{
a.end|fmt_datetime(HourMinute) }} {% if let Some(comment) = a.comment %}&nbsp; Kommentar: &nbsp; {{ comment }}
{% endif %}
</span>
<div class="buttons are-small">
<a class="button is-primary is-light" href="/availability/edit/{{ a.id }}">
<svg class="icon">
<use href="/static/feather-sprite.svg#edit" />
</svg>
<span>Bearbeiten</span>
</a>
<a class="button is-link is-light" href="/calendar?date={{ a.start.date() }}">
<svg class="icon">
<use href="/static/feather-sprite.svg#calendar" />
</svg>
<span>im Kalender anzeigen</span>
</a>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<h3 class="title is-3">Deine Einteilungen</h3>
<p class="subtitle is-5">in den nächsten 31 Tagen</p>
{% if assignments.len() == 0 %}
<div class="notification">
Keine Einteilungen für diesen Zeitraum vorhanden.
</div>
{% else %}
<div class="panel p-2">
{% for (a, event_name) in assignments %}
<div class="panel-block is-justify-content-space-between">
<span>
{{ a.start|fmt_datetime(WeekdayDayMonthYearHourMinute) }} - {{
a.end|fmt_datetime(HourMinute) }} bei {{ event_name }} als {{ a.function }}
</span>
<a class="button is-small is-link is-light" href="/calendar?date={{ a.start.date() }}">
<svg class="icon">
<use href="/static/feather-sprite.svg#calendar" />
</svg>
<span>im Kalender anzeigen</span>
</a>
</div>
{% endfor %}
</div>
{% endif %}
</div> </div>
</section> </section>
{% endblock %} {% endblock %}