From e0c05506c3da97efebdcc58116744d26059f5eb8 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Thu, 9 Oct 2025 20:34:38 +0200 Subject: [PATCH] preparing storing of new contact --- .../srsoftware/umbrella/contact/SqliteDb.java | 12 +++++- frontend/src/routes/contact/Card.svelte | 22 ++++++++++ frontend/src/routes/contact/Index.svelte | 21 +++++++++ frontend/src/routes/contact/Number.svelte | 43 +++++++++++++++++++ frontend/src/routes/contact/URL.svelte | 18 ++++++++ frontend/src/vcard.js | 11 +++++ translations/src/main/resources/de.json | 3 ++ 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 frontend/src/routes/contact/Number.svelte create mode 100644 frontend/src/routes/contact/URL.svelte diff --git a/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java b/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java index 9228966..cdc0839 100644 --- a/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java +++ b/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java @@ -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) { diff --git a/frontend/src/routes/contact/Card.svelte b/frontend/src/routes/contact/Card.svelte index 2fe6cae..ee8a3be 100644 --- a/frontend/src/routes/contact/Card.svelte +++ b/frontend/src/routes/contact/Card.svelte @@ -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 @@
+ {#if contact.id} {t('contact_number',{number:contact.id})} + {:else} + {t('new_contact')} + {/if} @@ -81,6 +89,13 @@ {/each} + + + + + +
+ {#each numbers as code} + + {/each} +
{#each emails as code} @@ -95,6 +110,13 @@ {/each}
+ {#each urls as code} + + {/each} +
\ No newline at end of file diff --git a/frontend/src/routes/contact/Index.svelte b/frontend/src/routes/contact/Index.svelte index 3be6dbe..0e4d6d7 100644 --- a/frontend/src/routes/contact/Index.svelte +++ b/frontend/src/routes/contact/Index.svelte @@ -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); } diff --git a/frontend/src/routes/contact/Number.svelte b/frontend/src/routes/contact/Number.svelte new file mode 100644 index 0000000..53114e4 --- /dev/null +++ b/frontend/src/routes/contact/Number.svelte @@ -0,0 +1,43 @@ + + +{#if value} + + + +
+{/if} \ No newline at end of file diff --git a/frontend/src/routes/contact/URL.svelte b/frontend/src/routes/contact/URL.svelte new file mode 100644 index 0000000..e258705 --- /dev/null +++ b/frontend/src/routes/contact/URL.svelte @@ -0,0 +1,18 @@ + + +{#if value} + +{/if} \ No newline at end of file diff --git a/frontend/src/vcard.js b/frontend/src/vcard.js index ab9e86e..a71b102 100644 --- a/frontend/src/vcard.js +++ b/frontend/src/vcard.js @@ -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 = { diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 46e64e2..8e1c86e 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -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",