Skip to content

Commit 7d5b33b

Browse files
authored
Merge pull request #58 from Pitastic/frontend-extensions
Frontend extensions
2 parents cfb9b45 + c91c7a0 commit 7d5b33b

12 files changed

Lines changed: 353 additions & 66 deletions

File tree

app/routes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
import os
55
from datetime import datetime
6+
import secrets
67
from flask import request, current_app, render_template, redirect, \
78
make_response, send_from_directory, session
8-
import secrets
99

1010

1111
class Routes:
@@ -383,12 +383,12 @@ def uploadIban(iban):
383383
Returns:
384384
json: Informationen zur Datei und Ergebnis der Untersuchung.
385385
"""
386-
input_file = request.files.get('file-input')
386+
input_file = request.files.get('file-batch')
387387
if not input_file:
388388
return {'error': 'Es wurde keine Datei übermittelt.'}, 400
389389

390390
# Store Upload file to tmp
391-
path = '/tmp/transactions.tmp'
391+
path = f"/tmp/{secrets.token_hex(12)}"
392392
content_type, size = parent.mv_fileupload(input_file, path)
393393

394394
# Daten einlesen und in Object speichern (Bank und Format default bzw. wird geraten)
@@ -416,7 +416,7 @@ def uploadIban(iban):
416416
insert_result = parent.db_handler.insert(parsed_data, iban)
417417
inserted = insert_result.get('inserted')
418418

419-
except (KeyError, ValueError, NotImplementedError) as ex:
419+
except (KeyError, ValueError) as ex:
420420
return {
421421
"error": (
422422
"Die hochgeladene Datei konnte nicht verarbeitet werden, "

app/static/css/style.css

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,31 @@ button.info {
104104
}
105105

106106
/* Tables */
107+
.transactions th {
108+
cursor: pointer;
109+
}
107110
.transactions .amount {
108111
white-space: nowrap;
109112
text-align: right;
110113
}
114+
.transactions th.amount {
115+
text-align: center;
116+
}
117+
.transactions th.sorted-asc::after, .transactions th.sorted-desc::after {
118+
position: absolute;
119+
margin-left: 4px;
120+
color: var(--pico-primary);
121+
}
122+
.transactions th.sorted-asc::after { content: "\2193"; }
123+
.transactions th.sorted-desc:after { content: "\2191"; }
111124

112125
#dynamic-results tr th {
113126
font-weight: bold;
114127
}
115128

116129
/* S : Slim button in transaction table */
117130
@media (max-width: 767px) {
118-
.transactions th.amount {
119-
text-align: center;
120-
}
131+
121132
.transactions tr {
122133
border-bottom: var(--pico-border-width) solid var(--pico-table-border-color);
123134
}

app/static/js/functions.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,156 @@ function removeTagBullet(element, hiddenInputId) {
166166

167167
}
168168

169+
170+
/**
171+
* Show a popup with a result message from an operation
172+
*
173+
* @param {string} heading Heading for the PopUp
174+
* @param {list} domlist List of DOMELements to add in the "content" section
175+
*/
176+
function responsePopUp(heading, domlist) {
177+
const popup = document.getElementById('response-popup');
178+
popup.querySelector('header h2').textContent = heading;
179+
180+
const content = document.getElementById('response-content');
181+
content.innerHTML = '';
182+
domlist.forEach(dom => {
183+
content.appendChild(dom);
184+
});
185+
186+
// Close actual Popup if open and open Response PopUp
187+
const openPopUp = document.querySelector('dialog[open] header button')
188+
if (openPopUp) {
189+
openPopUp.click();
190+
}
191+
openModal(popup, { 'currentTarget': { 'dataset': {} } });
192+
}
193+
194+
/** Show a popup with an error message from an operation
195+
*
196+
* @param {string} heading Heading for the PopUp
197+
* @param {number} error Error code
198+
* @param {string} responseText Response text from the operation
199+
*/
200+
function errorPopUp(heading, error, responseText) {
201+
const reason1 = document.createElement('p');
202+
reason1.innerHTML = "Fehler " + error;
203+
reason1.className = 'error';
204+
const reason2 = document.createElement('pre');
205+
reason2.innerHTML = responseText;
206+
responsePopUp(heading, [reason1, reason2]);
207+
}
208+
209+
/**
210+
* Sort Table Rows
211+
*
212+
* @param {string} table_id The ID of the tabel to sort rows in
213+
*
214+
* @param {number} col_index The index of the column to compare rows content with
215+
*/
216+
function sortTable(table_id, col_index){
217+
const table = document.getElementById(table_id);
218+
if (!table){
219+
console.error("Table '", table_id, "' to sort was not found");
220+
}
221+
if (typeof col_index == 'undefined'){
222+
col_index = 0;
223+
}
224+
225+
var switching = true;
226+
var switchcount = 0;
227+
var dir = "asc";
228+
229+
while (switching) {
230+
// Loop until nothing else was switched
231+
switching = false;
232+
var rows = table.rows;
233+
234+
/*
235+
Loop through the actual order of rows and
236+
compare if one pair should be switched.
237+
Break if a pair is found and switch it later.
238+
This loop will be started again after the switch.
239+
*/
240+
for (var i = 1; i < (rows.length - 1); i++) {
241+
var should_switch = false;
242+
var x = rows[i].getElementsByTagName('td')[col_index];
243+
var y = rows[i + 1].getElementsByTagName('td')[col_index];
244+
245+
// Compare values with special handling for numbers
246+
if (col_index == 0) {
247+
// Compare timestamp from dataset
248+
var x_val = Number(x.getElementsByTagName('input')[0].dataset.txdate);
249+
var y_val = Number(y.getElementsByTagName('input')[0].dataset.txdate);
250+
251+
} else if (col_index == 5){
252+
// Compare amount (numbers)
253+
var x_val = Number(x.innerText.slice(0, -4));
254+
var y_val = Number(y.innerText.slice(0, -4));
255+
256+
} else {
257+
// Compare normal innerText
258+
var x_val = x.innerText.toLowerCase();
259+
var y_val = y.innerText.toLowerCase();
260+
}
261+
262+
// Compare (depending on direction)
263+
if (dir == "asc"){
264+
265+
if (x_val > y_val) {
266+
// Switch needed: Brake!
267+
should_switch = true;
268+
break;
269+
}
270+
271+
} else if (dir == "desc") {
272+
273+
if (x_val < y_val) {
274+
// Switch needed: Brake!
275+
should_switch = true;
276+
break;
277+
}
278+
279+
}
280+
}
281+
282+
// Was a needed switch found in the loop?
283+
if (should_switch) {
284+
// Yes: Switch it now
285+
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
286+
287+
// Remember that we at least switched one time
288+
switchcount ++;
289+
290+
// Reset switching-var to get into the next iteration of the while loop
291+
switching = true;
292+
293+
} else {
294+
// There is no need for a switch...
295+
if (switchcount == 0 && dir == "asc") {
296+
// ...and there weren't any switches before:
297+
// Change the direction of the sorting if it is still default 'asc'
298+
dir = "desc";
299+
300+
// Reset switching-var to get into the next iteration of the while loop
301+
switching = true;
302+
}
303+
}
304+
305+
}
306+
307+
const table_headings = rows[0].getElementsByTagName('th');
308+
for (var i=0; i < table_headings.length; i++){
309+
table_headings[i].classList.remove('sorted-asc');
310+
table_headings[i].classList.remove('sorted-desc');
311+
}
312+
if (col_index == 0){
313+
table_headings[1].classList.add('sorted-' + dir);
314+
} else {
315+
table_headings[col_index].classList.add('sorted-' + dir);
316+
}
317+
}
318+
169319
// ----------------------------------------------------------------------------
170320
// -- AJAX Functions ----------------------------------------------------------
171321
// ----------------------------------------------------------------------------

app/static/js/iban.js

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -228,26 +228,41 @@ function tagAndCat(operation) {
228228
}
229229

230230
apiSubmit(api_url + IBAN, payload, function (responseText, error) {
231+
const heading = operation == 'tag' ? 'Tagging' : 'Kategorisierung';
231232
if (error) {
232-
alert(operation + ' failed: ' + '(' + error + ')' + responseText);
233+
errorPopUp(
234+
operation.substring(1) + operation.substring(0, 1).toUpperCase() + ' fehlgeschlagen',
235+
error, responseText
236+
);
233237

234238
} else {
235-
alert(operation + ' successful!' + responseText);
236-
if (dry_run) {
237-
// List UUIDs which would have been tagged/cat
238-
const r = JSON.parse(responseText)
239-
let txt_list = ""
240-
r.entries.forEach(element => {
241-
txt_list += "\n- " + element
242-
});
243-
alert("Folgende UUIDs würden geändert werden:\n" + txt_list);
239+
const reason1 = document.createElement('p');
240+
reason1.innerHTML = dry_run ? "Folgende Transaktionen wären geändert worden:" : "Folgende Transaktionen wurden geändert:";
244241

245-
} else {
246-
// Tagged/Cat -> Reload
247-
window.location.reload();
242+
const reason2 = document.createElement('ul');
243+
let r = JSON.parse(responseText)
244+
245+
if (r.entries.length == 0) {
246+
const li = document.createElement('li');
247+
li.innerHTML = '(keine)';
248+
reason2.appendChild(li);
248249
}
249250

251+
r.entries.forEach(element => {
252+
let li = document.createElement('li');
253+
let a = document.createElement('a');
254+
a.href = '/' + IBAN + '/' + element;
255+
a.target = '_blank';
256+
a.innerHTML = element;
257+
li.appendChild(a);
258+
reason2.appendChild(li);
259+
});
260+
261+
responsePopUp(
262+
heading + ' erfolgreich',
263+
[reason1, reason2]);
250264
}
265+
251266
}, false);
252267
}
253268

@@ -270,11 +285,12 @@ function removeTags() {
270285

271286
apiSubmit(api_function, tags, function (responseText, error) {
272287
if (error) {
273-
alert('Tag removal failed: ' + '(' + error + ')' + responseText);
288+
errorPopUp('Tag entfernen fehlgeschlagen', error, responseText);
274289

275290
} else {
276-
alert('Entries tags deleted successfully!' + responseText);
277-
window.location.reload();
291+
const p = document.createElement('p');
292+
p.innerHTML = 'Die Tags der ausgewählten Einträge wurden erfolgreich entfernt.';
293+
responsePopUp('Tags entfernt', [p]);
278294

279295
}
280296
}, false);
@@ -300,11 +316,12 @@ function removeCats() {
300316

301317
apiSubmit(api_function, payload, function (responseText, error) {
302318
if (error) {
303-
alert('Cat removal failed: ' + '(' + error + ')' + responseText);
319+
errorPopUp('Kategorie entfernen fehlgeschlagen', error, responseText);
304320

305321
} else {
306-
alert('Entries category deleted successfully!' + responseText);
307-
window.location.reload();
322+
const p = document.createElement('p');
323+
p.innerHTML = 'Die Kategorie der ausgewählten Einträge wurden erfolgreich entfernt.';
324+
responsePopUp('Kategorie entfernt', [p]);
308325

309326
}
310327
}, false);
@@ -344,7 +361,7 @@ function addCat() {
344361
function getInfo(uuid, callback = alert) {
345362
apiGet('/' + IBAN + '/' + uuid, {}, function (responseText, error) {
346363
if (error) {
347-
alert('getTx failed: ' + '(' + error + ')' + responseText);
364+
errorPopUp('Transaktionsabruf fehlgeschlagen', error, responseText);
348365

349366
} else {
350367
callback(responseText);

0 commit comments

Comments
 (0)