Compare commits

..

3 Commits

Author SHA1 Message Date
5a583a94ff some frontend change 2025-02-03 22:23:53 +01:00
8383080395 schema updates 2025-02-03 22:23:37 +01:00
2e8ee7ca5c table interaction 2025-02-03 22:22:27 +01:00
7 changed files with 1388 additions and 1185 deletions

View File

@ -16,3 +16,34 @@ Funktionen:
- Erklärung MP3
- Quittierung MP3
- Verabschiedung MP3
## API Endpoint planning
alertContacts (CRUD Fully implemented)
alerts -> Only get
actionPlan (CRUD)
- select all prios
priorities (CRUD)
- select actionPlan
- Only allow changes to priority
content (CRUD)
- Howto handle upload?
POST /alert/[:alert_hook]
-> Check actionplan if hook exists and select current prios -> Write call request to XYXYX
1. create one or more alertContacts
2. create 4x content (all phases)
3. create actionplan with contents
4. create one or more priorities

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
//// THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
//// ------------------------------------------------------
Project "AssetFlow" {
Project "ATAS" {
database_type: ''
Note: ''
}
@ -10,11 +10,11 @@ Project "AssetFlow" {
Table alerts {
id Int [pk, increment]
type alertType [not null]
message String
state alertState [not null]
description String
date DateTime [not null]
actionplan actionPlan
actionplanId Int
date DateTime [not null]
state alertState [not null]
acknowledged_by alertContacts [not null]
acknowledged_at DateTime
}
@ -30,9 +30,9 @@ Table alertContacts {
Table actionPlan {
id Int [pk, increment]
name String [not null]
name String [unique, not null]
comment String
alert_hook String [unique, not null]
alerthook String [unique, not null]
prio priorities [not null]
content content [not null]
alerts alerts [not null]
@ -85,7 +85,7 @@ Enum alertType {
}
Enum alertState {
incomming
incoming
running
failed
acknowledged

View File

@ -26,7 +26,7 @@ generator dbml {
provider = "prisma-dbml-generator"
output = "../docs"
outputName = "schema.dbml"
projectName = "AssetFlow"
projectName = "ATAS"
}
@ -46,7 +46,7 @@ enum alertType {
}
enum alertState {
incomming // Incomming alerts
incoming // Incoming alerts
running // Started calling
failed // Failed to get acknowledgement of any alertContacts
acknowledged // Some user acknowledged alert
@ -55,16 +55,19 @@ enum alertState {
model alerts {
id Int @id @unique @default(autoincrement())
type alertType
message String?
state alertState
description String?
date DateTime
actionplan actionPlan? @relation(fields: [actionplanId], references: [id])
actionplanId Int?
date DateTime
state alertState
acknowledged_by alertContacts[]
acknowledged_at DateTime?
@@fulltext([message])
}
@@fulltext([description])
}
model alertContacts {
id Int @id @unique @default(autoincrement())
@ -73,17 +76,20 @@ model alertContacts {
comment String?
prios priorities[]
alerts alerts[]
@@fulltext([name, phone, comment])
}
model actionPlan {
id Int @id @unique @default(autoincrement())
name String
name String @unique
comment String?
alert_hook String @unique
alerthook String @unique
prio priorities[]
content content[] // aka. all voice files
alerts alerts[]
@@fulltext([name, comment])
}
@ -104,12 +110,10 @@ model content {
name String
filename String
actionplan actionPlan[]
@@fulltext([name, filename])
}
// https://spacecdn.de/file/bma_stoe_v1.mp3
// https://spacecdn.de/file/quittiert_v1.mp3
// https://spacecdn.de/file/angenehmen_tag_v1.mp3

View File

@ -59,9 +59,62 @@ let _api = {
}
return result;
},
delete: async function (path, data) {
const options = {
method: 'DELETE',
headers: new Headers({ 'content-type': 'application/json' }),
body: JSON.stringify(data)
};
const response = await fetch(_apiConfig.basePath + path, options);
// Handle the response
if (!response.ok) {
_testPageFail(response.statusText);
return;
}
const result = await response.json();
// Handle the result, was json valid?
if (!result) {
_testPageFail('Invalid JSON response');
return;
}
return result;
},
patch: async function (path, data) {
const options = {
method: 'PATCH',
headers: new Headers({ 'content-type': 'application/json' }),
body: JSON.stringify(data)
};
const response = await fetch(_apiConfig.basePath + path, options);
// Handle the response
if (!response.ok) {
_testPageFail(response.statusText);
return;
}
const result = await response.json();
// Handle the result, was json valid?
if (!result) {
_testPageFail('Invalid JSON response');
return;
}
return result;
}
};
function updateRow(tableName, id, data) {
invalidateCache(tableName);
return _api.patch(`${tableName}`, { id: id, ...data });
}
function deleteRow(tableName, id) {
invalidateCache(tableName);
return _api.delete(`${tableName}`, { id: id });
}
function getApiDescriptionByTable(tableName) {
const keyDesc = `desc:${tableName}`;
const keyTime = `${keyDesc}:time`;

View File

@ -39,6 +39,9 @@ tables.forEach(async (table) => {
// Get THs and attach onClick event to sort
const ths = table.querySelectorAll('th');
ths.forEach((th) => {
if(th.getAttribute('fnc') == "actions") {
return;
}
th.style.cursor = 'pointer';
th.style.userSelect = 'none';
th.addEventListener('click', async function () {
@ -216,9 +219,17 @@ modalForms.forEach((modalForm) => {
jsonData[key] = value;
});
console.log('JSON Data: ', jsonData);
let resp = await createEntry(table, jsonData);
let resp = {};
if(modalForm.getAttribute('data-action') == 'edit') {
Rid = modalForm.getAttribute('data-rid');
resp = await updateRow(table, Rid,jsonData);
modalForm.setAttribute('data-action', 'create');
} else {
resp = await createEntry(table, jsonData);
}
console.log('Response: ', resp);
if (resp['status'] == 'CREATED') {
if (resp['status'] == 'CREATED' || resp['status'] == 'UPDATED') {
console.log('Entry created successfully');
modalForm.closest('.modal').classList.remove('is-active');
modalForm.reset();
@ -358,10 +369,18 @@ function writeDataToTable(table, data, paginationPassOn) {
// All required cols
let requiredCols = [];
let actionFields = [];
columns.forEach((column) => {
// console.log('Column: ', column, ' FNC: ', column.getAttribute('data-fnc'), column.attributes);
if(column.getAttribute('data-fnc') == "actions") {
console.log('!!! Found actions column !!!');
actionFields.push(column);
return;
}
requiredCols.push(column.getAttribute('data-dataCol'));
});
// Get paginationPassOn
const start = paginationPassOn['start'];
const end = paginationPassOn['end'];
@ -457,6 +476,100 @@ function writeDataToTable(table, data, paginationPassOn) {
td.innerText = row[column];
tr.appendChild(td);
});
// Add action fields
actionFields.forEach((actionField) => {
const td = document.createElement('td');
const actions = actionField.getAttribute('data-actions').split(',');
actions.forEach((action) => {
const button = document.createElement('button');
let icon = '';
let color = 'is-primary';
switch(action) {
case 'edit': {
icon = '<i class="bi bi-pencil"></i>';
break;
}
case 'delete': {
icon = '<i class="bi bi-trash"></i>';
color = 'is-danger';
break;
}
}
// Add classes
button.classList.add('button');
button.classList.add('is-small');
button.classList.add(color);
button.classList.add('is-outlined');
button.innerHTML = ` <span class="icon is-small">${icon}</span> `;
button.style.marginRight = '5px';
// Add data-action and data-id
button.setAttribute('data-action', action);
button.setAttribute("data-id", row["id"]);
// Add event listener
button.addEventListener('click', async function() {
const table = actionField.closest('table');
const row = button.closest('tr');
const columns = table.querySelectorAll('th');
const columnIndices = [];
columns.forEach((column, index) => {
columnIndices[column.getAttribute('data-dataCol')] = index;
});
const data = [];
columns.forEach((column) => {
data[column.getAttribute('data-dataCol')] = row.children[columnIndices[column.getAttribute('data-dataCol')]].innerText;
});
console.log('Data: ', data);
switch(action) {
case 'edit': {
// Open modal with form
const form = document.querySelector("form[data-targetTable='" + table.getAttribute('data-dataSource') + "']");
const formTitle = form.querySelector('.title');
const entryPhase = form.querySelector('.entryPhase');
const loadPhase = form.querySelector('.loadPhase');
const fields = form.querySelectorAll('input');
// Set modal to edit mode
form.setAttribute('data-action', 'edit');
form.setAttribute('data-rid', button.getAttribute('data-id'));
formTitle.innerText = 'Edit entry';
fields.forEach((field) => {
// Skip for submit button
if(field.getAttribute('type') == 'submit') {
return;
}
field.value = data[field.getAttribute('name')];
});
form.closest('.modal').classList.add('is-active');
// TBD
break;
}
case 'delete': {
// confirm
const confirm = window.confirm('Do you really want to delete this entry?');
// Delete entry
if(confirm) {
const table = actionField.closest('table');
const id = button.getAttribute('data-id');
const resp = await deleteRow(table.getAttribute('data-dataSource'), id);
if(resp['status'] == 'DELETED') {
refreshTable(table);
updateSingeltonsByTableName(table.getAttribute('data-dataSource'));
} else {
// Show error message
// TODO: Show error message
}
}
break;
}
}
}
);
td.appendChild(button);
});
tr.appendChild(td);
});
tbody.appendChild(tr);
}
}

View File

@ -43,8 +43,9 @@
<i class="bi bi-arrow-clockwise title"></i>
</div>
<div class="box entryPhase">
<h2 class="title">New Contact</h1>
<form data-targetTable="AlertContacts">
<h2 class="title">New Contact</h1>
<div class="field">
<label class="label">Name</label>
<div class="control has-icons-left">
@ -108,6 +109,7 @@
<th data-dataCol = "name">Name</th>
<th data-dataCol = "phone">Telefon Nummer</th>
<th data-dataCol = "comment">Kommentar</th>
<th data-fnc="actions" data-actions="edit,delete">Aktionen</th>
</tr>
</thead>
<tbody>