This commit is contained in:
Keanu D?lle 2022-01-12 22:20:36 +01:00
parent e592c88768
commit 9809533e9e
19 changed files with 347 additions and 71 deletions

View File

@ -0,0 +1,8 @@
-- This file should undo anything in `up.sql`
alter table eu_position_instances drop constraint eu_position_instances_pk;
alter table eu_position_instances
add constraint eu_position_instances_pk
primary key (instance_id, position_id);
alter table eu_position_instances
drop position_instance_id;

View File

@ -0,0 +1,9 @@
-- Your SQL goes here
alter table eu_position_instances
add position_instance_id uuid default uuid_generate_v1();
alter table eu_position_instances drop constraint eu_position_instances_pk;
alter table eu_position_instances
add constraint eu_position_instances_pk
primary key (position_instance_id);

View File

@ -0,0 +1,16 @@
-- This file should undo anything in `up.sql`
alter table eu_positions_templates
drop constraint eu_positions_templates_pk;
alter table eu_positions_templates
add constraint eu_positions_templates_pk
primary key (position_entity_id, template_id);
alter table eu_positions_templates
drop position_template_id;
alter table eu_positions_templates
drop num;
alter table eu_positions_templates
drop tag;

View File

@ -0,0 +1,15 @@
alter table eu_positions_templates
add position_template_id uuid default uuid_generate_v1();
alter table eu_positions_templates
add num int default 1 not null;
alter table eu_positions_templates
add tag text;
alter table eu_positions_templates drop constraint eu_positions_templates_pk;
alter table eu_positions_templates
add constraint eu_positions_templates_pk
primary key (position_template_id);

View File

@ -2,13 +2,13 @@
<div class="form-group row">
<label for="template_detailed_name" class="col-sm-3 col-form-label">Name</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="template_detailed_name" value="{{name}}">
<input type="text" class="form-control qsf" id="template_detailed_name" value="{{name}}">
</div>
</div>
<div class="form-group row">
<label for="template_detailed_description" class="col-sm-3 col-form-label">Beschreibung</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="template_detailed_description" value="{{description}}">
<input type="text" class="form-control qsf" id="template_detailed_description" value="{{description}}">
</div>
</div>
<div>
@ -24,42 +24,26 @@
</svg>
</button>
</th>
<th scope="col">Name</th>
<th scope="col">Position</th>
<th scope="col">Beschreibung</th>
<th scope="col">Bezeichnung</th>
<th scope="col" style="width:15%">Anzahl</th>
</tr>
</thead>
<tbody class="template_detailed_positions_tbody">
{{#each positions}}
<tr>
<td><input type="checkbox" class="template_detailed_position_checkbox" data-position-id="{{entity_id}}">
</td>
<td>{{name}}</td>
<td>{{description}}</td>
</tr>
{{> em_event_unit_templates_template_detailed_position_row}}
{{/each}}
<tr class="new_row">
<td><input type="checkbox" class="template_detailed_position_checkbox"></td>
<td colspan="2">{{> search base="add_position_search" type="position"}}</td>
<td><input type="text" id="add_position_tag" class="form-control"></td>
<td><input type="number" min="1" value="1" id="add_position_num" class="form-control"></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-danger btn-sm templates_detailed_delete_position_button">Löschen</button>
<button type="button" class="btn btn-danger btn-sm templates_detailed_delete_position_button qsf">Löschen</button>
<br><br>
<div class="row">
<b class="col-3">Hinzufügen: </b>
<div id="add_position-search" class="col-9">
<div class="input-group">
<input type="text" class="form-control" id="add_position_search-searchbar" data-search-type="position">
<span class="input-group-append">
<span class="btn btn-outline-secondary" type="button">
<svg width="16" height="16" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#search"></use>
</svg>
</span>
</span>
</div>
<div class="add_position_search-search-result-overlay" style="">
<ul class="add_position_search-search-result-overlay-list">
</ul>
</div>
</div>
</div>
<hr>
<h3>Fahrzeugpositionen</h3><br>
<table class="table table-striped table-hover">
@ -113,7 +97,4 @@
<button type="button" class="template_detailed_vehicle_add btn btn-secondary">Fahrzeugposition hinzufügen</button>
</div>
<input type="hidden" id="template_detailed_entity_id" value="{{entity_id}}"><br>
<button type="button" class="template_detailed_submit btn btn-primary" style="float: right; margin-bottom:15px;">
Änderungen Speichern
</button>
</div>

View File

@ -0,0 +1,8 @@
<tr>
<td><input type="checkbox" class="template_detailed_position_checkbox" data-position-id="{{position.entity_id}}">
</td>
<td>{{position.name}}</td>
<td>{{position.description}}</td>
<td><input type="text" class="form-control qsf" value="{{tag}}"></td>
<td><input type="number" class="form-control qsf" value="{{num}}"></td>
</tr>

7
resources/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,147 @@
limit = 10;
$(document).ready(async function(){
await EventUnitTemplatesModule.load_templates();
EventUnitTemplatesModule.setup_pagination();
await EventUnitTemplatesModule.load_eu_template_list();
});
EventUnitTemplatesModule = (function(){
let templates = {};
let eu_templates = new Map();
let old_offset = 0;
let pag = null;
let all_checked = false;
let all_checked_vehicles = false;
let positions_all_checked = false;
let load_templates = async function(){
const template_row = $.get("/templates/em_event_unit_templates_template_row.hbs");
const template_detailed = $.get("/templates/em_event_unit_templates_template_detailed.hbs");
const vehicle_row = $.get("/templates/em_event_unit_templates_vehicle_position_row.hbs");
const search = $.get("/templates/search.hbs");
const pagination = $.get("/templates/pagination.hbs");
await Promise.all([template_row, template_detailed, vehicle_row, search, pagination]).then(function(res){
Handlebars.registerPartial('search', Handlebars.compile(res[3]));
templates.template_row = Handlebars.compile(res[0]);
templates.template_detailed = Handlebars.compile(res[1]);
templates.vehicle_row = Handlebars.compile(res[2]);
templates.pagination = Handlebars.compile(res[4]);
});
};
let setup_pagination = function(){
pag = new Pagination("em_eu_templates_pagination", templates.pagination, ".pag", limit, load_eu_template_list);
}
let load_eu_template_list = async function(offset){
if(offset === undefined || !Number.isInteger(offset)){
offset = 0;
}
old_offset = offset;
$.ajax({
url: '/api/events/units/templates?limit='+limit+'&offset='+offset,
type: 'GET',
contentType: 'application/json',
success: function (data) {
if (is_ok(data)) {
eu_templates = new Map();
for(let t in data.templates){
eu_templates.set(data.templates[t].entity_id, data.templates[t]);
}
for (const [key, value] of eu_templates.entries()) {
$("#template_list_tbody").append(templates.template_row(value));
}
pag.render(data.total_template_count, offset);
$(".eu_template_tr").off("click").on("click", load_detailed_template);
}
},
timeout: 3000,
error: function () {
alert("Es ist ein Fehler aufgetreten!");
}
});
};
let load_detailed_template = async function(){
$(".template_detailed_card").show();
all_checked = false;
all_checked_vehicles = false;
positions_all_checked = false;
$(".eu_template_tr").removeClass("table-primary");
$(this).addClass("table-primary");
let entity_id = $(this).data("entity_id");
$(".template_detailed_card_body").remove();
let eu_template = eu_templates.get(entity_id);
if(eu_template){ //Check if template found for id
await load_positions_for_template(eu_template);
//await load_vehicle_positions();
$(".template_detailed_card").show();
}
};
let load_positions_for_template = async function(eu_template){
const data = await $.ajax({
url: '/api/events/units/templates/'+eu_template.entity_id+'/positions',
type: 'GET',
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Es ist ein Fehler aufgetreten!");
}
});
if(is_ok(data)){
eu_template.positions = data;
console.log(eu_template);
$(".template_detailed_card").append(templates.template_detailed(eu_template));
$(".template_detailed_check_all_positions").off("click").on("click", function(){
$($(".template_detailed_position_checkbox")).prop("checked", !positions_all_checked);
positions_all_checked = !positions_all_checked;
});
var position_search = new MiniSearchbar("add_position_search", null);
position_search.setup();
$(".new_row").on("click focus", function(){
});
/*$(".templates_detailed_delete_position_button").off("click").on("click", delete_positions_from_template);
$(".template_detailed_submit").off("click").on("click", function(){
let template = {};
template.name = $("#template_detailed_name").val();
if($("#template_detailed_description").val().length > 1){
template.description = $("#template_detailed_description").val();
}
template.entity_id = $("#template_detailed_entity_id").val();
if(template.name.length === 0){
alert("Bitte Namen angeben!");
}else{
update_template(template);
}
});
$(".template_detailed_vehicle_add").off("click").on("click", add_vehicle_position);
$(".templates_detailed_delete_vehicle_button").off("click").on("click", delete_vehicle_positions);
$(".template_detailed_check_all_vehicles").off("click").on("click", function(){
$(".eu_vehicle_position_checkbox").prop("checked", !all_checked_vehicles);
all_checked_vehicles = !all_checked_vehicles;
});
$(vehicle_categories).each(function(){
$("#template_detailed_vehicle_category_select").append("<option value='"+this.id+"'>"+this.name+"</option>")
});*/
}
};
return{
load_templates: load_templates,
setup_pagination: setup_pagination,
load_eu_template_list: load_eu_template_list,
}
}());

View File

@ -1,3 +1,6 @@
// DO NOT USE. DEPRECATED! USE em_eu_templates.js instead!!!
// TODO: remove
let all_checked = false;
let all_checked_vehicles = false;
let limit = 10;
@ -9,9 +12,12 @@ let current_template_selected;
$( document ).ready(function() {
EventUnitTemplatesModule.load_templates();
console.log("Don't use this script you honk! If someone pushed this to production hang him!");
//Do not execute code here which relies on templates. Use EventUnitTemplateModule.start function instead
});
// DO NOT USE. DEPRECATED! USE em_eu_templates.js instead!!!
EventUnitTemplatesModule = ( function() {
let templates = {};
let vehicle_categories = [];
@ -168,6 +174,7 @@ EventUnitTemplatesModule = ( function() {
});
if(is_ok(data)){
templ.positions = data;
console.log(templ);
$(".template_detailed_card").append(templates.template_detailed(templ));
$(".template_detailed_check_all_positions").off("click").on("click", function(){
$($(".template_detailed_position_checkbox")).prop("checked", !positions_all_checked);
@ -203,7 +210,7 @@ EventUnitTemplatesModule = ( function() {
};
let add_position_search_callback = function(sr){
$.ajax({
url: '/api/events/units/templates/'+current_template_selected+'/positions/'+$(sr).data("entity-id"),
url: '/api/events/units/templates/'+current_template_selected+'/positions/'+$(sr).data("entity-id")+"/times/1",
type: 'PUT',
contentType: 'application/json',
success: function (data) {

View File

@ -42,6 +42,16 @@ function MiniSearchbar(pbase, pcallback, value_id, value_name, delete_callback){
$("#"+ms.base).val($(caller).data("identifier")).attr("data-entity-id", $(caller).data("entity-id")).data("entity-id", $(caller).data("entity-id"));
$("#"+ms.base+"_input_group").show();
}
}else if(ms.searchtype === "position"){
ms.callback = function(caller){
$("#"+ms.base+"-search").hide();
let description = "";
if(caller.description){
description = " ("+caller.description+")"
}
$("#"+ms.base).val(caller.name+description).attr("data-entity-id", caller.entity_id);
$("#"+ms.base+"_input_group").show();
}
}else{
console.log("unknown searchtype:"+ms.searchtype);
}
@ -150,18 +160,30 @@ function MiniSearchbar(pbase, pcallback, value_id, value_name, delete_callback){
contentType: 'application/json',
success: function (data) {
if (is_ok(data)) {
let res_map = new Map();
ms.overlay_list.html("");
$.each(data.positions, function (index, value) {
ms.overlay_list.append("<span tabindex=\"0\" class=\"" + ms.base + "-search-result-overlay-result\" style=\"cursor: pointer\" data-entity-id=\"" + value.entity_id + "\" data-name=\"" + value.name + "\" data-description=\"" + value.description + "\"><li class='list-group-item'><span class=\"badge badge-secondary\">Hinzufügen:</span> "+value.name+"</li></span>")
res_map.set(value.entity_id, value);
ms.overlay_list.append("<span tabindex=\"0\" class=\"" + ms.base + "-search-result-overlay-result\" style=\"cursor: pointer\" data-entity-id=\"" + value.entity_id+"\"><li class='list-group-item'><span class=\"badge badge-secondary\">Hinzufügen:</span> "+value.name+"</li></span>")
});
// We are using the new way to pass the data to the callback: Instead of using data attributes
// for each field we save all retrieved data in a Map (res_map) and pass the entry from
// this Map to the callback.
// TODO: implement this for each search type! But be careful, you will need to change every callback.
//select search result on click
$("." + ms.base + "-search-result-overlay-result").off("click").on("click", function () {
ms.callback(this)
let res = res_map.get($(this).data("entity-id"));
ms.callback(res);
});
//select search result on enter key press
$("." + ms.base + "-search-result-overlay-result").off("keyup").on("keyup", function (e) {
if (e.keyCode === 13) {
ms.callback(this);
let res = res_map.get($(this).data("entity-id"));
ms.callback(res);
}
});
}

View File

@ -24,21 +24,8 @@
</tbody>
</table>
<button class="iconbutton check_all_templates"><svg width="1.25em" height="1.25em" style="margin-left: 12px;margin-right: 12px; fill="currentColor"><use xlink:href="/img/bootstrap-icons.svg#check-all"></use></svg></button><button type="button" class="btn btn-danger btn-sm templates_delete_button">Löschen</button>
<div class="row">
<nav aria-label="vehicle list" class="mx-auto">
<ul class="pagination">
<li class="page-item template_list_nav_back_li">
<a class="page-link template_list_nav_back" href="#" aria-label="Vorherige Seite">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item">
<a class="page-link template_list_nav_next" href="#" aria-label="Nächste Seite">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
<div class="row pag">
</div>
</div>
</div>

View File

@ -1 +1 @@
v0.2-62-gdd0d248
v0.2-67-g89a6232

View File

@ -207,14 +207,14 @@ pub fn add_event_unit_template(settings: &State<Settings>, eut: EventUnitTemplat
}
}
pub fn add_position_to_template(settings: &State<Settings>, template_id2 : uuid::Uuid, position_id : uuid::Uuid) -> Result<(), diesel::result::Error>{
pub fn add_position_to_template(settings: &State<Settings>, template_id2 : uuid::Uuid, position_id : uuid::Uuid, num_of_positions: i32) -> Result<(), diesel::result::Error>{
use crate::schema::eu_positions_templates::dsl::*;
let connection = establish_connection(settings);
match diesel::insert_into(eu_positions_templates).values((position_entity_id.eq(position_id), template_id.eq(template_id2))).execute(&connection){
match diesel::insert_into(eu_positions_templates).values((position_entity_id.eq(position_id), template_id.eq(template_id2), num.eq(num_of_positions))).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't add position to template: {}", e);
error!("Couldn't add position(s) to template: {}", e);
Err(e)
}
}
@ -258,16 +258,66 @@ pub fn get_event_unit_templates_count(settings: &State<Settings>,) -> Result<i64
}
}
pub fn get_event_unit_positions_for_template(settings: &State<Settings>, template: uuid::Uuid) -> Result<Vec<EventUnitPosition>, diesel::result::Error>{
use crate::schema::eu_positions_templates;
#[derive(Queryable, Clone, Deserialize, Serialize, AsChangeset, Insertable)]
#[table_name = "eu_positions_templates"]
#[changeset_options(treat_none_as_null = "true")]
pub struct RawEventUnitTemplatePosition{
pub(crate) position_entity_id: uuid::Uuid,
pub(crate) template_id: uuid::Uuid,
pub(crate) position_template_id: uuid::Uuid, //primary key
pub(crate) num: i32,
pub(crate) tag: Option<String>,
}
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct EventUnitTemplatePosition{
pub(crate) position_template_id: uuid::Uuid,
pub(crate) tag: Option<String>,
pub(crate) num: i32,
pub(crate) template_id: uuid::Uuid,
pub(crate) position: EventUnitPosition,
}
pub fn get_event_unit_positions_for_template(settings: &State<Settings>, template: uuid::Uuid) -> Result<Vec<EventUnitTemplatePosition>, diesel::result::Error>{
use crate::schema::eu_positions_templates::dsl::*;
let connection = establish_connection(settings);
let mut res: Vec<EventUnitTemplatePosition> = vec![];
let positions : Vec<RawEventUnitTemplatePosition> = match eu_positions_templates.filter(template_id.eq(template)).get_results(&connection){
Ok(pos) => pos,
Err(e) => {
error!("Couldn't get unit positions for template: {}", e);
return Err(e)
}
};
for position in positions{
res.push(EventUnitTemplatePosition{
position_template_id: position.position_template_id,
tag: position.tag,
num: position.num,
template_id: position.template_id,
position: get_event_unit_position(settings, position.position_entity_id)?
})
}
Ok(res)
}
fn get_event_unit_position(settings: &State<Settings>, position_id: uuid::Uuid) -> Result<EventUnitPosition, diesel::result::Error>{
use crate::schema::eu_positions::dsl::*;
let connection = establish_connection(settings);
match eu_positions.left_join(eu_positions_templates.on(position_entity_id.eq(entity_id))).filter(template_id.eq(template)).select((entity_id, name, description, requirements)).get_results(&connection){
match eu_positions.filter(entity_id.eq(position_id)).get_result(&connection){
Ok(pos) => Ok(pos),
Err(e) => {
error!("Couldn't get unit positions for template: {}", e);
error!("Couldn't get unit position: {}", e);
Err(e)
}
}
@ -342,9 +392,11 @@ pub fn add_position_instances_for_instance(settings: &State<Settings>, instance_
let connection = establish_connection(settings);
for position in positions{
match diesel::insert_into(eu_position_instances).values((instance_id.eq(instance_id2), position_id.eq(position.entity_id))).execute(&connection){
Ok(_) => {},
Err(e) => return Err(e)
for i in 1..position.num+1{
match diesel::insert_into(eu_position_instances).values((instance_id.eq(instance_id2), position_id.eq(position.position.entity_id))).execute(&connection){
Ok(_) => {},
Err(e) => return Err(e)
}
}
}
@ -375,7 +427,7 @@ pub fn get_instance_positions(settings: &State<Settings>, instance_id2: uuid::Uu
let connection = establish_connection(settings);
let position_instances: Result<Vec<EventUnitInstancePosition>, diesel::result::Error> = sql_query(
"SELECT instance_id, position_id, taken_by, name, description, requirements FROM eu_position_instances INNER JOIN eu_positions ON position_id = entity_id WHERE instance_id = $1;",
"SELECT position_instance_id, instance_id, position_id, taken_by, name, description, requirements FROM eu_position_instances INNER JOIN eu_positions ON position_id = entity_id WHERE instance_id = $1;",
)
.bind::<Uuid, _>(instance_id2)
.get_results(&connection);

View File

@ -101,6 +101,8 @@ pub struct EventUnitVehiclePosition{
#[derive(Queryable, Clone, Deserialize, Serialize, QueryableByName)]
pub struct EventUnitInstancePosition{
#[sql_type = "Uuid"]
pub(crate) position_instance_id: uuid::Uuid,
#[sql_type = "Uuid"]
pub(crate) instance_id: uuid::Uuid,
#[sql_type = "Uuid"]

View File

@ -6,7 +6,7 @@ use rocket::serde::json::Json;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid_string};
use crate::helper::translate_diesel_error::translate_diesel;
use crate::database::controller::events::{get_event_unit_templates, get_event_unit_templates_count, get_event_unit_positions_for_template, get_vehicle_positions_for_template};
use crate::database::controller::events::{get_event_unit_templates, get_event_unit_templates_count, get_event_unit_positions_for_template, get_vehicle_positions_for_template, EventUnitTemplatePosition};
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct EventUnitTemplateList{
@ -60,7 +60,7 @@ pub fn read_event_unit_template_positions(
settings: &State<Settings>,
cookie: SessionCookie,
template_id: String
) -> Result<Json<Vec<EventUnitPosition>>, Json<ApiErrorWrapper>> {
) -> Result<Json<Vec<EventUnitTemplatePosition>>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(

View File

@ -1,3 +1,4 @@
use diesel::sql_types::Integer;
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
@ -9,12 +10,13 @@ use crate::helper::translate_diesel_error::translate_diesel;
use crate::database::model::events::EventUnitTemplate;
use crate::modules::api::events::event_units::templates::create::CreateTemplateData;
#[put("/api/events/units/templates/<template_id>/positions/<position_id>", format = "json")]
#[put("/api/events/units/templates/<template_id>/positions/<position_id>/times/<num>", format = "json")]
pub fn put_position_in_template(
settings: &State<Settings>,
cookie: SessionCookie,
template_id: String,
position_id: String,
num: String,
) -> Result<(), Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
@ -25,8 +27,17 @@ pub fn put_position_in_template(
let template_id = parse_uuid_string(template_id)?;
let position_id = parse_uuid_string(position_id)?;
let num : i32 = match num.parse(){
Ok(num) => num,
Err(e) => {
warn!("Couldn't parse number of times for putting positions in template: {}", e);
return Err(Json(
ApiError::new(400, "Couldn't parse times as integer!".to_string()).to_wrapper(),
));
}
};
match add_position_to_template(settings, template_id, position_id){
match add_position_to_template(settings, template_id, position_id, num){
Ok(_) => Ok(()),
Err(e) => Err(translate_diesel(e))
}

View File

@ -224,8 +224,6 @@ pub fn http_basic_auth(
let pwd = auth.password;
let username = auth.username;
warn!("user: {}, pwd: {}", username, pwd);
match get_user_by_username(username.clone(), &settings){
None => {
match login_attempts_usernames_exceeded(settings, username.clone()){

View File

@ -31,9 +31,11 @@ pub fn event_unit_templates(cookie: SessionCookie, _settings: &State<Settings>)
};
let footer = Footer {
scripts: vec![Script {
path: "/js/em_event_unit_templates.js".to_string(),
path: "/js/em_eu_templates.js".to_string(),
}, Script {
path: "/js/mini_searchbar.js".to_string(),
}, Script {
path: "/js/pagination.js".to_string(),
}],
};
let mut sidebar = Sidebar::new(member.clone());

View File

@ -126,10 +126,11 @@ table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_position_instances (instance_id, position_id) {
eu_position_instances (position_instance_id) {
instance_id -> Uuid,
position_id -> Uuid,
taken_by -> Nullable<Uuid>,
position_instance_id -> Uuid,
}
}
@ -149,9 +150,12 @@ table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_positions_templates (position_entity_id, template_id) {
eu_positions_templates (position_template_id) {
position_entity_id -> Uuid,
template_id -> Uuid,
position_template_id -> Uuid,
num -> Int4,
tag -> Nullable<Text>,
}
}