Compare commits

..

25 Commits

Author SHA1 Message Date
StephanRichter 1cdf825bcb Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m5s
Build Docker Image / Clean-Registry (push) Successful in 6s
2026-04-23 11:10:21 +02:00
StephanRichter 71c86e512d Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m32s
Build Docker Image / Clean-Registry (push) Successful in 5s
2026-04-23 10:37:56 +02:00
StephanRichter 9d8013bc33 Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m26s
Build Docker Image / Clean-Registry (push) Successful in 6s
2026-04-22 08:38:09 +02:00
StephanRichter 05733d3b7a improved autocomplete
Build Docker Image / Docker-Build (push) Successful in 2m25s
Build Docker Image / Clean-Registry (push) Successful in 6s
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-04-21 09:30:48 +02:00
StephanRichter 71c071bbdd Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m30s
Build Docker Image / Clean-Registry (push) Successful in 6s
2026-04-21 08:27:45 +02:00
StephanRichter 1241fee61d Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m6s
Build Docker Image / Clean-Registry (push) Successful in 5s
2026-04-20 23:04:31 +02:00
StephanRichter d64cb886c9 Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m12s
Build Docker Image / Clean-Registry (push) Successful in 5s
2026-04-20 22:38:58 +02:00
StephanRichter ac8149e6bb Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m1s
Build Docker Image / Clean-Registry (push) Successful in 6s
2026-04-20 22:23:05 +02:00
StephanRichter 493b61465b Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m11s
Build Docker Image / Clean-Registry (push) Successful in 5s
2026-04-20 21:55:23 +02:00
StephanRichter df372e9cfd Merge branch 'accounting' into dev 2026-04-20 08:25:39 +02:00
StephanRichter ff58f3ae82 Merge branch 'bugfix/trim-tags' into dev
Build Docker Image / Docker-Build (push) Successful in 2m19s
Build Docker Image / Clean-Registry (push) Successful in 5s
2026-04-19 11:07:54 +02:00
StephanRichter b71db96b47 improved build workflow
Build Docker Image / Docker-Build (push) Successful in 1m56s
Build Docker Image / Clean-Registry (push) Successful in 5s
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-04-19 10:45:58 +02:00
StephanRichter 02434419f4 Merge branch 'bugfix/trim-tags' into dev
Build Docker Image / Docker-Build (push) Successful in 2m14s
Build Docker Image / Clean-Registry (push) Successful in 6s
2026-04-19 10:40:09 +02:00
StephanRichter bd096dc61f Merge branch 'bugfix/trim-tags' into dev
Build Docker Image / Docker-Build (push) Successful in 2m18s
Build Docker Image / Clean-Registry (push) Successful in -18s
2026-04-18 15:44:37 +02:00
StephanRichter 9f286f3121 Merge branch 'bugfix/trim-tags' into dev
Build Docker Image / Docker-Build (push) Successful in 1m53s
Build Docker Image / Clean-Registry (push) Successful in -18s
2026-04-18 15:28:30 +02:00
StephanRichter 2211f4f39d Merge branch 'bugfix/tag-header' into dev
Build Docker Image / Docker-Build (push) Successful in 3m1s
Build Docker Image / Clean-Registry (push) Successful in -18s
2026-04-18 15:08:02 +02:00
StephanRichter a6b988df3a Merge branch 'accounting' into dev 2026-04-18 15:05:10 +02:00
StephanRichter 1316d3fb1e Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m15s
Build Docker Image / Clean-Registry (push) Successful in -18s
2026-04-15 18:16:37 +02:00
StephanRichter 99fa75a980 Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m17s
Build Docker Image / Clean-Registry (push) Successful in -16s
2026-04-14 23:22:35 +02:00
StephanRichter 6fc590d795 Merge branch 'accounting' into dev 2026-04-14 22:54:44 +02:00
StephanRichter 7afc804586 added menu symbol for accounting
Build Docker Image / Docker-Build (push) Successful in 1m59s
Build Docker Image / Clean-Registry (push) Successful in -17s
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-04-14 22:54:03 +02:00
StephanRichter f40692dd3d Merge branch 'accounting' into dev
Build Docker Image / Docker-Build (push) Successful in 2m23s
Build Docker Image / Clean-Registry (push) Successful in -16s
2026-04-14 22:49:23 +02:00
StephanRichter 1c91699bf5 Merge branch 'module/wiki' into dev
Build Docker Image / Docker-Build (push) Successful in 3m18s
Build Docker Image / Clean-Registry (push) Successful in -15s
2026-04-09 09:12:22 +02:00
StephanRichter 9f5e1e0853 Merge branch 'main' into dev 2026-04-01 18:14:24 +02:00
StephanRichter 55dfea65b0 Merge branch 'bugfix/wiki-css' into dev
Build Docker Image / Docker-Build (push) Successful in 2m34s
Build Docker Image / Clean-Registry (push) Successful in -12s
2026-03-30 23:59:17 +02:00
17 changed files with 40 additions and 179 deletions
+2
View File
@@ -53,8 +53,10 @@ jobs:
run: | run: |
TAGS="$(curl -s -u "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_PASS }}" https://${{ secrets.REGISTRY_PATH }}/v2/umbrella/tags/list | jq -r ".tags[]")" TAGS="$(curl -s -u "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_PASS }}" https://${{ secrets.REGISTRY_PATH }}/v2/umbrella/tags/list | jq -r ".tags[]")"
COUNT=$(echo "$TAGS" | wc -l) COUNT=$(echo "$TAGS" | wc -l)
echo found $COUNT tags: $TAGS
if [ $COUNT -gt 10 ]; then if [ $COUNT -gt 10 ]; then
REMAIN=$((COUNT - 10)) REMAIN=$((COUNT - 10))
echo $REMAIN tags will be kept!
echo "$TAGS" | head -n $REMAIN > /tmp/old_tags echo "$TAGS" | head -n $REMAIN > /tmp/old_tags
else else
echo less than 10 tags, skipping cleanup echo less than 10 tags, skipping cleanup
@@ -5,14 +5,11 @@ import de.srsoftware.umbrella.core.model.Account;
import de.srsoftware.umbrella.core.model.Transaction; import de.srsoftware.umbrella.core.model.Transaction;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
public interface AccountDb { public interface AccountDb {
void dropTransactionTag(long transactionId, String tag); void dropTransactionTag(long transactionId, String tag);
Optional<Transaction> lastTransaction(long accountId, String source, String dest, double amount);
Collection<Account> listAccounts(long userId); Collection<Account> listAccounts(long userId);
Collection<String> listTags(long accountId, String source, String destination); Collection<String> listTags(long accountId, String source, String destination);
@@ -119,7 +119,7 @@ public class AccountingModule extends BaseHandler implements AccountingService {
var head = path.pop(); var head = path.pop();
return switch (head) { return switch (head) {
case null -> postEntry(user.get(),ex); case null -> postEntry(user.get(),ex);
case DESTINATIONS -> postSearchDestinations(user.get(),ex); case DESTINATIONS -> postSearchDestinations(user.get(),ex);
case PURPOSES -> postSearchPurposes(user.get(),ex); case PURPOSES -> postSearchPurposes(user.get(),ex);
case SOURCES -> postSearchSources(user.get(),ex); case SOURCES -> postSearchSources(user.get(),ex);
default -> { default -> {
@@ -295,22 +295,6 @@ public class AccountingModule extends BaseHandler implements AccountingService {
return sendContent(ex,newAccount != null ? newAccount : transaction); return sendContent(ex,newAccount != null ? newAccount : transaction);
} }
public boolean postGetLastTransaction(long accountId, UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
if (!json.has(Field.SOURCE)) throw missingField(Field.SOURCE);
if (!(json.get(Field.SOURCE) instanceof JSONObject src)) throw invalidField(Field.SOURCE,JSON);
var source = src.get(src.has(Field.ID) ? Field.ID : Field.DISPLAY).toString();
if (!json.has(Field.DESTINATION)) throw missingField(Field.DESTINATION);
if (!(json.get(Field.DESTINATION) instanceof JSONObject dst)) throw invalidField(Field.SOURCE,JSON);
var dest = dst.get(dst.has(Field.ID) ? Field.ID : Field.DISPLAY).toString();
if (!json.has(Field.AMOUNT)) throw missingField(Field.AMOUNT);
if (!(json.get(Field.AMOUNT) instanceof Number amt)) throw invalidField(Field.AMOUNT,Text.NUMBER);
var amount = amt.doubleValue();
var transaction = accountDb.lastTransaction(accountId, source, dest, amount);
return transaction.isPresent() ? sendContent(ex,transaction.get()) : notFound(ex);
}
public boolean postSearchDestinations(UmbrellaUser user, HttpExchange ex) throws IOException { public boolean postSearchDestinations(UmbrellaUser user, HttpExchange ex) throws IOException {
return sendContent(ex,searchOptions(user, Field.DESTINATION, body(ex))); return sendContent(ex,searchOptions(user, Field.DESTINATION, body(ex)));
} }
@@ -343,9 +327,7 @@ public class AccountingModule extends BaseHandler implements AccountingService {
} }
private boolean postToAccount(long accountId, Path path, UmbrellaUser user, HttpExchange ex) throws IOException { private boolean postToAccount(long accountId, Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
var head = path.pop(); return switch (path.pop()) {
return switch (head) {
case PURPOSES -> postGetLastTransaction(accountId,user,ex);
case TAGS -> postSearchTags(accountId,user,ex); case TAGS -> postSearchTags(accountId,user,ex);
case null, default -> super.doPost(path,ex); case null, default -> super.doPost(path,ex);
}; };
@@ -2,7 +2,6 @@
package de.srsoftware.umbrella.accounting; package de.srsoftware.umbrella.accounting;
import static de.srsoftware.tools.NotImplemented.notImplemented; import static de.srsoftware.tools.NotImplemented.notImplemented;
import static de.srsoftware.tools.Optionals.nullable;
import static de.srsoftware.tools.jdbc.Condition.*; import static de.srsoftware.tools.jdbc.Condition.*;
import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.tools.jdbc.Query.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
@@ -131,29 +130,6 @@ public class SqliteDb extends BaseDb implements AccountDb {
} }
} }
@Override
public Optional<Transaction> lastTransaction(long accountId, String source, String dest, double amount) {
try {
var rs = select(ALL).from(TABLE_TRANSACTIONS)
.where(ACCOUNT,equal(accountId)).where(SOURCE,equal(source)).where(DESTINATION,equal(dest)).where(AMOUNT,equal(amount))
.sort(ID+" DESC")
.limit(1)
.exec(db);
Transaction ta = null;
if (rs.next()) ta = Transaction.of(rs);
rs.close();
if (ta != null){
var tags = ta.tags();
rs = select(TAG).from(TABLE_TAGS_TRANSACTIONS).leftJoin(TAG_ID,TABLE_TAGS,ID).where(TRANSACTION_ID,equal(ta.id())).exec(db);
while (rs.next()) tags.add(rs.getString(1));
rs.close();
}
return nullable(ta);
} catch (SQLException e) {
throw failedToSearchDb(t(Text.ACCOUNTING));
}
}
@Override @Override
public HashSet<Account> listAccounts(long userId) { public HashSet<Account> listAccounts(long userId) {
try { try {
@@ -295,7 +271,7 @@ public class SqliteDb extends BaseDb implements AccountDb {
} }
} else if (transaction.isDirty()) { } else if (transaction.isDirty()) {
try { try {
if (transaction.amount() == 0 || transaction.source().isEmpty() || transaction.destination().isEmpty()) { if (transaction.amount() == 0) {
delete().from(TABLE_TRANSACTIONS).where(Field.ID, equal(transaction.id())).where(ACCOUNT, equal(transaction.accountId())).execute(db); delete().from(TABLE_TRANSACTIONS).where(Field.ID, equal(transaction.id())).where(ACCOUNT, equal(transaction.accountId())).execute(db);
} else { } else {
replaceInto(TABLE_TRANSACTIONS, Field.ID, Field.ACCOUNT, Field.TIMESTAMP, Field.SOURCE, Field.DESTINATION, Field.AMOUNT, Field.DESCRIPTION) replaceInto(TABLE_TRANSACTIONS, Field.ID, Field.ACCOUNT, Field.TIMESTAMP, Field.SOURCE, Field.DESTINATION, Field.AMOUNT, Field.DESCRIPTION)
@@ -6,9 +6,6 @@ import de.srsoftware.umbrella.core.constants.Field;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static de.srsoftware.tools.Optionals.isSet;
import static de.srsoftware.tools.Optionals.nullIfEmpty;
public class IdOrString implements Mappable { public class IdOrString implements Mappable {
private final Long id; private final Long id;
private final String value; private final String value;
@@ -55,10 +52,6 @@ public class IdOrString implements Mappable {
return map; return map;
} }
public boolean isEmpty() {
return id == null && !isSet(value);
}
@Override @Override
public String toString() { public String toString() {
return value; return value;
@@ -67,4 +60,4 @@ public class IdOrString implements Mappable {
public String value(){ public String value(){
return value; return value;
} }
} }
+22 -52
View File
@@ -14,10 +14,9 @@
const ignore = ['ArrowLeft','ArrowRight']; const ignore = ['ArrowLeft','ArrowRight'];
//let candidate = $state({ display : '' }); //let candidate = $state({ display : '' });
let selected = $state(null); let selected = $state([]);
let candidates = $state([]); let candidates = $state([]);
let timer = null; let timer = null;
let list_elem;
async function dummyGetCandidates(text){ async function dummyGetCandidates(text){
console.warn(`getCandidates(${text}) not overridden!`); console.warn(`getCandidates(${text}) not overridden!`);
@@ -54,43 +53,41 @@
const idx = select.value; const idx = select.value;
candidate = candidates[idx]; candidate = candidates[idx];
candidates = []; candidates = [];
selected = null; selected = [];
onSelect(candidate); onSelect(candidate);
} }
async function fetchCandidates(){ async function fetchCandidates(){
candidates = candidate.display ? await getCandidates(candidate.display) : []; candidates = await getCandidates(candidate.display);
selected = null; if (selected>candidates.length) selected = candidates.length;
} }
async function onkeyup(ev){ async function onkeyup(ev){
if (ignore.includes(ev.key)) return; if (ignore.includes(ev.key)) return;
if (ev.key == 'ArrowDown'){ if (ev.key == 'ArrowDown'){
ev.preventDefault(); ev.preventDefault();
selected = selected == null ? 0: selected +1; selected = selected.length < 1 ? [0] : [selected[0]+1]
if (selected >= candidates.length) selected = 0; if (selected[0] >= candidates.length) selected = [0];
scrollTo(selected);
return false; return false;
} }
if (ev.key == 'ArrowUp'){ if (ev.key == 'ArrowUp'){
ev.preventDefault(); ev.preventDefault();
selected = selected == null ? candidates.length -1 : selected -1; selected = selected.length < 1 ? [-1] : [selected[0]-1]
if (selected < 0) selected = candidates.length -1; if (selected[0] < 0) selected = [candidates.length-1];
scrollTo(selected);
return false; return false;
} }
if (ev.key == 'Enter'|| ev.key == 'Tab'){ if (ev.key == 'Enter'|| ev.key == 'Tab'){
ev.preventDefault(); ev.preventDefault();
if (selected != null && selected < candidates.length) { if (selected.length>0) {
candidate = candidates[selected]; candidate = candidates[selected[0]];
candidates = []; candidates = [];
selected = null; selected = [];
onSelect(candidate); onSelect(candidate);
return false; return false;
} }
if (ev.key == 'Enter') { if (ev.key == 'Enter') {
candidates = []; candidates = [];
selected = null; selected = [];
if (onCommit(candidate)) candidate = { display : '' }; if (onCommit(candidate)) candidate = { display : '' };
} }
return false; return false;
@@ -98,7 +95,7 @@
if (ev.key == 'Escape'){ if (ev.key == 'Escape'){
ev.preventDefault(); ev.preventDefault();
candidates = []; candidates = [];
selected = null; selected = [];
return false; return false;
} }
@@ -107,50 +104,23 @@
timer = setTimeout(fetchCandidates,400); timer = setTimeout(fetchCandidates,400);
return false; return false;
} }
function select(index){
candidate = candidates[index];
selected = null;
candidates = [];
onSelect(candidate);
}
function scrollTo(index){
let list_elements = list_elem.children;
if (list_elements) list_elements[index].scrollIntoView({block:'center'});
}
</script> </script>
<style> <style>
span { position : relative } span { position : relative }
ul { select { position : absolute; top: 30px; left: 3px; }
position : absolute;
top: 30px;
left: 3px;
background: black;
color: orange;
border: 1px solid orange;
border-radius: 5px;
z-index: 50;
list-style: none;
padding: 4px;
margin: 0;
min-width: 400px;
max-height: 200px;
overflow: scroll;
}
.highlight { background: orange; color: black; }
select { background: black; color: orange; border: 1px solid orange; border-radius: 5px; z-index: 50; }
option:checked { background: orange; color: black; }
</style> </style>
<span class="autocomplete"> <span>
<input type="text" bind:value={candidate.display} {onkeyup} autofocus={autofocus} {id} /> <input type="text" bind:value={candidate.display} {onkeyup} autofocus={autofocus} />
{#if candidates && candidates.length > 0} {#if candidates && candidates.length > 0}
<ul bind:this={list_elem} class="suggestions"> <select bind:value={selected} {ondblclick} multiple tabindex="-1" class="autocomplete" size={Math.min(candidates.length,10)}>
{#each candidates as candidate,i} {#each candidates as candidate,i}
<li class="option {selected==i?'highlight':''}" onclick={e => select(i)} ondblclick={e => select(i)}>{candidate.display}</li> <option value={i}>{candidate.display}</option>
{/each} {/each}
</ul> </select>
{/if} {/if}
</span> </span>
@@ -155,8 +155,5 @@
{/if} {/if}
{:else} {:else}
<Display classes={{editable}} markdown={value} {onclick} {oncontextmenu} title={t('right_click_to_edit')} wrapper={type} /> <Display classes={{editable}} markdown={value} {onclick} {oncontextmenu} title={t('right_click_to_edit')} wrapper={type} />
{#if !value.display}
<button onclick={oncontextmenu}>{t('add_object',{object:t('content')})}</button>
{/if}
{/if} {/if}
</div> </div>
@@ -27,7 +27,6 @@
if (!transaction.destination.id) sums[0] += transaction.amount; if (!transaction.destination.id) sums[0] += transaction.amount;
if (!transaction.source.id) sums[0] -= transaction.amount; if (!transaction.source.id) sums[0] -= transaction.amount;
} }
window.setTimeout(scrollToBottom,100);
return sums; return sums;
} }
@@ -66,10 +65,6 @@
load(); load();
} }
function scrollToBottom(){
window.scrollTo(0, document.body.scrollHeight);
}
onMount(load); onMount(load);
</script> </script>
@@ -77,12 +72,6 @@
.amount{ text-align: right } .amount{ text-align: right }
</style> </style>
<svelte:head>
{#if account}
<title>Umbrella {account.name}</title>
{/if}
</svelte:head>
{#if filter.length > 0} {#if filter.length > 0}
<fieldset> <fieldset>
<legend>{t('filter by tags')}</legend> <legend>{t('filter by tags')}</legend>
@@ -42,10 +42,7 @@
} }
function focusOnEnter(ev,id){ function focusOnEnter(ev,id){
if (ev.key == 'Enter') { if (ev.key == 'Enter') document.getElementById(id).focus();
proposePurpose();
document.getElementById(id).focus();
}
} }
async function getAccountTags(text){ async function getAccountTags(text){
@@ -96,20 +93,6 @@
} }
} }
async function proposePurpose(){
const source = entry.source;
const destination = entry.destination;
const amount = entry.amount;
const url = api(`accounting/${account.id}/purposes`);
const res = await post(url,{source,destination,amount});
if (res.ok) {
yikes();
var lastTransaction = await res.json();
entry.purpose = { display: lastTransaction.purpose};
entry.tags = lastTransaction.tags;
} else error(res);
}
async function save(){ async function save(){
let data = { let data = {
...entry, ...entry,
@@ -123,7 +106,7 @@
router.navigate('/accounting'); router.navigate('/accounting');
return; return;
} }
//entry.tags = []; entry.tags = [];
onSave(); onSave();
document.getElementById('date-input').focus(); document.getElementById('date-input').focus();
} else error(res); } else error(res);
@@ -186,4 +169,4 @@
<span> <span>
<button onclick={save}>{t('save')}</button> <button onclick={save}>{t('save')}</button>
</span> </span>
</fieldset> </fieldset>
@@ -32,10 +32,6 @@
onMount(load); onMount(load);
</script> </script>
<svelte:head>
<title>Umbrella {t('accounts')}</title>
</svelte:head>
<fieldset> <fieldset>
<span></span> <span></span>
+1 -2
View File
@@ -200,10 +200,9 @@
<LineEditor bind:value={project.name} editable={true} onSet={val => update({name:val})} /> <LineEditor bind:value={project.name} editable={true} onSet={val => update({name:val})} />
</div> </div>
<div> <div>
{t('options')} <button onclick={kanban}>{t('show_kanban')}</button>
</div> </div>
<div> <div>
<button onclick={kanban}><span class="symbol"></span> {t('show_kanban')}</button>
<button onclick={toggleSettings}><span class="symbol"></span> {t('settings')}</button> <button onclick={toggleSettings}><span class="symbol"></span> {t('settings')}</button>
</div> </div>
<div>{t('state')}</div> <div>{t('state')}</div>
@@ -13,8 +13,6 @@ import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.messagebus.events.Event; import de.srsoftware.umbrella.messagebus.events.Event;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
public class SqliteDb extends BaseDb implements JournalDb{ public class SqliteDb extends BaseDb implements JournalDb{
public SqliteDb(Connection connection) { public SqliteDb(Connection connection) {
@@ -35,14 +33,13 @@ public class SqliteDb extends BaseDb implements JournalDb{
var sql = """ var sql = """
CREATE TABLE IF NOT EXISTS {0} ( CREATE TABLE IF NOT EXISTS {0} (
{1} INTEGER PRIMARY KEY, {1} INTEGER PRIMARY KEY,
{2} LONG NOT NULL, {2} INTEGER,
{3} INTEGER, {3} VARCHAR(255) NOT NULL,
{4} VARCHAR(255) NOT NULL, {4} VARCHAR(16) NOT NULL,
{5} VARCHAR(16) NOT NULL, {5} TEXT
{6} TEXT
); );
"""; """;
sql = format(sql,TABLE_JOURNAL,ID,TIMESTAMP,USER_ID,MODULE,ACTION,DESCRIPTION); sql = format(sql,TABLE_JOURNAL,ID,USER_ID,MODULE,ACTION,DESCRIPTION);
try { try {
db.prepareStatement(sql).execute(); db.prepareStatement(sql).execute();
} catch (SQLException e) { } catch (SQLException e) {
@@ -53,9 +50,8 @@ public class SqliteDb extends BaseDb implements JournalDb{
@Override @Override
public void logEvent(Event<?> event) { public void logEvent(Event<?> event) {
try { try {
var timestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); insertInto(TABLE_JOURNAL,USER_ID,MODULE,ACTION,DESCRIPTION)
insertInto(TABLE_JOURNAL,TIMESTAMP,USER_ID,MODULE,ACTION,DESCRIPTION) .values(event.initiator().id(), event.module(), event.eventType(), event.describe())
.values(timestamp,event.initiator().id(), event.module(), event.eventType(), event.describe())
.execute(db).close(); .execute(db).close();
} catch (SQLException e) { } catch (SQLException e) {
throw databaseException(ERROR_WRITE_EVENT,event.eventType(),event.initiator().name()); throw databaseException(ERROR_WRITE_EVENT,event.eventType(),event.initiator().name());
-3
View File
@@ -192,8 +192,6 @@
"items": "Artikel", "items": "Artikel",
"join_objects" : "{objects} zusammenführen", "join_objects" : "{objects} zusammenführen",
"kanban": "Kanban",
"key": "Suchbegriff", "key": "Suchbegriff",
"language": "Sprache", "language": "Sprache",
@@ -309,7 +307,6 @@
"project ({id})": "Projekt ({id})", "project ({id})": "Projekt ({id})",
"Project '{project}' was edited": "Projekt '{project}' wurde bearbeitet", "Project '{project}' was edited": "Projekt '{project}' wurde bearbeitet",
"projects": "Projekte", "projects": "Projekte",
"Projects": "Projekte",
"properties": "Eigenschaften", "properties": "Eigenschaften",
"property": "Eigenschaft", "property": "Eigenschaft",
"purpose": "Zweck", "purpose": "Zweck",
-3
View File
@@ -192,8 +192,6 @@
"items": "items", "items": "items",
"join_objects" : "join {objects}", "join_objects" : "join {objects}",
"kanban": "Kanban",
"key": "search term", "key": "search term",
"language": "language", "language": "language",
@@ -309,7 +307,6 @@
"project ({id})": "project ({id})", "project ({id})": "project ({id})",
"Project '{project}' was edited": "Project '{project}' was edited", "Project '{project}' was edited": "Project '{project}' was edited",
"projects": "projects", "projects": "projects",
"Projects": "projects",
"properties": "properties", "properties": "properties",
"property": "property", "property": "property",
"purpose": "purpose", "purpose": "purpose",
+1 -6
View File
@@ -542,8 +542,7 @@ select.autocomplete{
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.grid2, .grid2{
.grid3{
display: grid; display: grid;
grid-template-columns: auto; grid-template-columns: auto;
} }
@@ -583,10 +582,6 @@ select.autocomplete{
#app nav.expanded .timetracking{ #app nav.expanded .timetracking{
grid-column-end: span 2; grid-column-end: span 2;
} }
.autocomplete .suggestions > *{
font-size: 1.5em;
}
} }
fieldset.vcard{ fieldset.vcard{
@@ -711,10 +711,6 @@ select.autocomplete{
#app nav.expanded .timetracking{ #app nav.expanded .timetracking{
grid-column-end: span 2; grid-column-end: span 2;
} }
.autocomplete .suggestions > *{
font-size: 1.5em;
}
} }
fieldset.vcard{ fieldset.vcard{
@@ -701,10 +701,6 @@ select.autocomplete{
#app nav.expanded .timetracking{ #app nav.expanded .timetracking{
grid-column-end: span 2; grid-column-end: span 2;
} }
.autocomplete .suggestions > *{
font-size: 1.5em;
}
} }
fieldset.vcard{ fieldset.vcard{