preparing storing of new contact

This commit is contained in:
2025-10-09 20:34:38 +02:00
parent b0364ec77e
commit e0c05506c3
7 changed files with 129 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ package de.srsoftware.umbrella.contact;
import static de.srsoftware.tools.jdbc.Condition.equal;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.tools.jdbc.Query.insertInto;
import static de.srsoftware.tools.jdbc.Query.select;
import static de.srsoftware.umbrella.contact.Constants.*;
import static de.srsoftware.umbrella.core.Constants.*;
@@ -92,7 +93,16 @@ public class SqliteDb extends BaseDb implements ContactDb{
@Override
public Contact save(Contact contact) {
if (contact.id() == 0){ // new contact
throw UmbrellaException.unprocessable("Storing of new contacts not implemented");
try {
var rs = insertInto(TABLE_CONTACTS,DATA).values(contact.vcard()).execute(db).getGeneratedKeys();
Long id = null;
if (rs.next()) id = rs.getLong(1);
rs.close();
if (id != null) return new Contact(id,contact.vcard());
throw databaseException("Failed to store new vcard");
} catch (SQLException e) {
throw databaseException("Failed to store new vcard",e);
}
} else try { // update
Query.update(TABLE_CONTACTS).set(DATA).where(ID,equal(contact.id())).prepare(db).apply(contact.vcard()).execute();
} catch (SQLException e) {

View File

@@ -9,7 +9,9 @@
import ExtraField from './ExtraField.svelte';
import FN from './FN.svelte';
import Name from './Name.svelte';
import Number from './Number.svelte';
import Org from './Org.svelte';
import URL from './URL.svelte';
let { contact } = $props();
@@ -18,7 +20,9 @@
let emails = $derived(contact.vcard.match(/^EMAIL.*:.+$/gm));
let extra_fields = $derived(contact.vcard.match(/^X-.*:.+/gm));
let fns = $derived(contact.vcard.match(/^FN.*:.+$/gm));
let numbers = $derived(contact.vcard.match(/^TEL.*:.+$/gm));
let orgs = $derived(contact.vcard.match(/^ORG.*:.+$/gm));
let urls = $derived(contact.vcard.match(/^URL.*:.+$/gm));
async function patch(from,to){
if (from == to) return;
@@ -44,7 +48,11 @@
<fieldset class="vcard">
<legend>
{#if contact.id}
{t('contact_number',{number:contact.id})}
{:else}
{t('new_contact')}
{/if}
<button class="symbol" onclick={toggleCode}></button>
</legend>
<table>
@@ -81,6 +89,13 @@
{/each}
</td>
</tr>
<tr>
<td>
{#each numbers as code}
<Number {code} {patch} />
{/each}
</td>
</tr>
<tr>
<td>
{#each emails as code}
@@ -95,6 +110,13 @@
{/each}
</td>
</tr>
<tr>
<td>
{#each urls as code}
<URL {code} {patch} />
{/each}
</td>
</tr>
</tbody>
</table>
</fieldset>

View File

@@ -15,6 +15,27 @@
yikes();
var data = await res.json();
contacts = Object.values(data).sort(byName);
contacts.unshift({
id: 0,
vcard: `BEGIN:VCARD
VERSION:3.0
PRODID:Umbrella Contact manager by SRSoftware
FN:${t('formatted_name')}
N:${t('family_name')};${t('given_name')};;;
ORG:${t('organization')}
EMAIL;TYPE=HOME:${t('email')}
EMAIL;TYPE=WORK:${t('email')}
ADR;TYPE=HOME:;;${t('street')};${t('locality')};${t('region')};${t('post_code')};${t('country')}
ADR;TYPE=WORK:;;${t('street')};${t('locality')};${t('region')};${t('post_code')};${t('country')}
TEL;TYPE=CELL;:${t('phone_cell')}
TEL;TYPE=HOME;:${t('phone_home')}
TEL;TYPE=WORK;:${t('phone_work')}
X-TAX-NUMBER:${t('tax_id')}
X-BANK-ACCOUNT:${t('bank_account')}\\nIBAN:XXXX\\nBIC:XXXX
X-COURT:${t('local_court')}
URL:https://example.com
END:VCARD`
});
} else {
error(res);
}

View File

@@ -0,0 +1,43 @@
<script>
import LineEditor from '../../Components/LineEditor.svelte';
import { number } from '../../vcard.js';
let { code, patch = (from, to) => true } = $props();
let cell = $derived(code.toLowerCase().includes('type=cell'));
let home = $derived(code.toLowerCase().includes('type=home'));
let work = $derived(code.toLowerCase().includes('type=work'));
let value = $derived(number(code));
function onSet(newVal){
const newCode = code.replace(value,newVal);
return patch(code,newCode);
}
function toggleCell(){
toggleType('CELL');
}
function toggleHome(){
toggleType('HOME');
}
function toggleType(key){
key = key.toUpperCase();
if (code.toUpperCase().includes(';TYPE='+key)) {
const regex = new RegExp(';TYPE='+key, "ig");
patch(code,code.replace(regex,''));
} else patch(code,code.replace('TEL','TEL;TYPE='+key));
}
function toggleWork(){
toggleType('WORK');
}
</script>
{#if value}
<span class="symbol {cell?'':'inactive'}" onclick={toggleCell} ></span>
<span class="symbol {home?'':'inactive'}" onclick={toggleHome}></span>
<span class="symbol {work?'':'inactive'}" onclick={toggleWork} ></span>
<LineEditor type="span" editable={true} {value} {onSet} /><br/>
{/if}

View File

@@ -0,0 +1,18 @@
<script>
import LineEditor from '../../Components/LineEditor.svelte';
import { url } from '../../vcard.js';
import { t } from '../../translations.svelte';
let { code, patch = (from, to) => true } = $props();
let value = $derived(url(code));
function onSet(newVal){
const newCode = code.replace(value,newVal);
return patch(code,newCode);
}
</script>
{#if value}
<LineEditor type="span" editable={true} {value} {onSet} title={t('url')}/>
{/if}

View File

@@ -37,11 +37,22 @@ export function fn(vcard){
return match ? match[1].trim() : '';
}
export function number(vcard){
const match = vcard.match(/^TEL.*:(.+)$/m);
return match ? match[1].trim() : '';
}
export function org(vcard){
const match = vcard.match(/^ORG:(.+)$/m);
return match ? match[1].trim() : '';
}
export function url(vcard){
const match = vcard.match(/^URL:(.+)$/m);
return match ? match[1].trim() : '';
}
export function name(vcard){
const match = vcard.match(/^N:(.+)$/m);
let name = {

View File

@@ -91,6 +91,7 @@
"failed": "fehlgeschlagen",
"failed_login_attempts" : "Account nach {attempts} fehlgeschlagenen Logins gesperrt bis {release_time}",
"family_name": "Familenname",
"file": "Datei",
"files": "Dateien",
"filter": "Filter",
@@ -100,6 +101,7 @@
"formatted_name": "Anzeigename",
"fulltext": "Volltextsuche",
"given_name": "Vorname",
"go": "los!",
"go_to_url_to_reset_password": "Um ein neues Passwort zu erhalten, öffnen Sie bitte den folgenden Link: {url}",
"gross_sum": "Brutto-Summe",
@@ -167,6 +169,7 @@
"name": "Name",
"net_price": "Nettopreis",
"net_sum": "Netto-Summe",
"new_contact": "neuer Kontakt",
"new_password": "neues Passwort",
"new_document_from": "{number} / neues {type}s-Dokument von {sender}",
"no_company": "keine Firma",