refactored translations, preparing sending of document
This commit is contained in:
@@ -45,7 +45,7 @@ subprojects {
|
||||
implementation("de.srsoftware:tools.http:6.0.4")
|
||||
implementation("de.srsoftware:tools.logging:1.3.2")
|
||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
||||
implementation("de.srsoftware:tools.util:2.0.3")
|
||||
implementation("de.srsoftware:tools.util:2.0.4")
|
||||
implementation("org.json:json:20240303")
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ public abstract class BaseHandler extends PathHandler {
|
||||
headers.add("Access-Control-Allow-Headers", "Content-Type");
|
||||
headers.add("Access-Control-Allow-Credentials", "true");
|
||||
headers.add("Access-Control-Allow-Methods","DELETE, GET, POST, PATCH");
|
||||
headers.add("Access-Control-Expose-Headers","Content-Disposition");
|
||||
}
|
||||
return ex;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.core;
|
||||
|
||||
|
||||
public class Tuple<A,B>{
|
||||
public final A a;
|
||||
public final B b;
|
||||
|
||||
public Tuple(A a, B b){
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public static <A, B> Tuple<A,B> of(A a, B b) {
|
||||
return new Tuple<>(a,b);
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ import de.srsoftware.document.zugferd.data.Currency;
|
||||
import de.srsoftware.tools.Pair;
|
||||
import de.srsoftware.tools.Path;
|
||||
import de.srsoftware.tools.SessionToken;
|
||||
import de.srsoftware.tools.Tuple;
|
||||
import de.srsoftware.umbrella.core.BaseHandler;
|
||||
import de.srsoftware.umbrella.core.Tuple;
|
||||
import de.srsoftware.umbrella.core.api.CompanyService;
|
||||
import de.srsoftware.umbrella.core.api.Translator;
|
||||
import de.srsoftware.umbrella.core.api.UserService;
|
||||
@@ -199,20 +199,25 @@ public class DocumentApi extends BaseHandler {
|
||||
case TEMPLATES -> postTemplateList(ex,user.get());
|
||||
case null -> postDocument(ex,user.get());
|
||||
default -> {
|
||||
var docId = 0L;
|
||||
try {
|
||||
docId = Long.parseLong(head);
|
||||
} catch (NumberFormatException ignored) {
|
||||
yield super.doPost(path,ex);
|
||||
}
|
||||
yield postToDocument(ex,path,user.get(),docId);
|
||||
var docId = Long.parseLong(head);
|
||||
yield switch (path.pop()){
|
||||
case null -> postToDocument(ex,path,user.get(),docId);
|
||||
case PATH_SEND -> sendDocument(ex,path,user.get(),docId);
|
||||
default -> super.doPost(path,ex);
|
||||
};
|
||||
}
|
||||
};
|
||||
} catch (NumberFormatException ignored) {
|
||||
return super.doPost(path,ex);
|
||||
} catch (UmbrellaException e) {
|
||||
return send(ex,e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendDocument(HttpExchange ex, Path path, UmbrellaUser umbrellaUser, long docId) throws IOException {
|
||||
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED,ex);
|
||||
}
|
||||
|
||||
private boolean getCompanies(HttpExchange ex, UmbrellaUser user, Token token) throws IOException, UmbrellaException {
|
||||
return sendContent(ex,companies.listCompaniesOf(user).stream().map(Company::toMap));
|
||||
}
|
||||
@@ -350,7 +355,7 @@ public class DocumentApi extends BaseHandler {
|
||||
.filter(filter)
|
||||
.findAny();
|
||||
if (optDoc.isEmpty()) throw UmbrellaException.notFound("Cannot render {0} {1}: Missing template \"{2}\"",type,document.number(),template);
|
||||
Function<String,String> translate = text -> translator.translate(user.language(),text);
|
||||
Function<String,String> translate = text -> translator.translate(user.language(),"document."+text.replaceAll(" ","_"));
|
||||
var pdfData = new HashMap<String,Object>();
|
||||
pdfData.put(FIELD_DOCUMENT,document.renderToMap());
|
||||
pdfData.put("translate",translate);
|
||||
|
||||
@@ -13,6 +13,7 @@ import de.srsoftware.configuration.JsonConfig;
|
||||
import de.srsoftware.document.api.*;
|
||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -77,8 +78,11 @@ public abstract class TemplateDoc implements Document {
|
||||
}
|
||||
}
|
||||
tokens = findTokensIn(source);
|
||||
var opt = data.get("translate");
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<String,String> translate = opt instanceof Function<?,?> fun ? (Function<String,String>) fun : s -> s;
|
||||
for (var token : tokens){
|
||||
var value = config.get("translations."+token).map(Object::toString).orElse(token).trim();
|
||||
var value = translate.apply(token);
|
||||
if (MIME_HTML.equals(mimeType())) value = value.replace("\n","<span class=\"break\"></span>\n");
|
||||
source = source.replace("<? "+token+" ?>",value);
|
||||
}
|
||||
|
||||
@@ -132,25 +132,25 @@
|
||||
<div class="sender data">
|
||||
<? document.sender.name ?><br/>
|
||||
<br/>
|
||||
<? Delivery date ?>: <? document.delivery ?>
|
||||
<? delivery date ?>: <? document.delivery ?>
|
||||
</div>
|
||||
</header>
|
||||
<h1><? document.type ?></h1>
|
||||
<h2>
|
||||
<span class="left"><? document.type ?> <? document.number ?></span>
|
||||
<span class="center"><? customer number ?>: <? document.customer.id ?></span>
|
||||
<span class="center"><? customer id ?>: <? document.customer.id ?></span>
|
||||
<span class="right"><? document.date ?></span>
|
||||
</h2>
|
||||
<hr/>
|
||||
<div class="head"><? document.head.rendered ?></div>
|
||||
<table>
|
||||
<tr>
|
||||
<th><? Position ?></th>
|
||||
<th><? Description ?></th>
|
||||
<th><? Price per unit ?></th>
|
||||
<th><? Amount ?></th>
|
||||
<th><? Price ?></th>
|
||||
<th><? Tax rate ?></th>
|
||||
<th><? position ?></th>
|
||||
<th><? description ?></th>
|
||||
<th><? price per unit ?></th>
|
||||
<th><? amount ?></th>
|
||||
<th><? price ?></th>
|
||||
<th><? tax rate ?></th>
|
||||
</tr>
|
||||
|
||||
<!-- positions -->
|
||||
@@ -171,7 +171,7 @@
|
||||
<!-- positions -->
|
||||
|
||||
<tr>
|
||||
<th class="right" colspan="5"><? Net sum ?>:</th>
|
||||
<th class="right" colspan="5"><? net sum ?>:</th>
|
||||
<th class="right"><? document.net_sum ?></th>
|
||||
</tr>
|
||||
|
||||
@@ -181,13 +181,13 @@
|
||||
<td></td>
|
||||
<td><? net_sum ?> + <? rate ?> =</td>
|
||||
<td><? gross_sum ?></td>
|
||||
<td colspan="2"><? contained tax: ?></td>
|
||||
<td colspan="2"><? contained tax ?>:</td>
|
||||
<td><? amount ?></td>
|
||||
</tr>
|
||||
|
||||
<!-- tax list -->
|
||||
<tr>
|
||||
<th class="right" colspan="5"><? Gross sum ?>:</th>
|
||||
<th class="right" colspan="5"><? gross sum ?>:</th>
|
||||
<th class="right"><? document.gross_sum ?></th>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -196,12 +196,12 @@
|
||||
<tr>
|
||||
<td><? document.sender.name ?></td>
|
||||
<td>
|
||||
<? Tax id ?>: <? document.sender.tax_id ?><br/>
|
||||
<? tax id ?>: <? document.sender.tax_id ?><br/>
|
||||
<? local court ?>: <? document.sender.court ?><br/><br/>
|
||||
<div class="ad"><? document.type ?> <? created with ?> <a href="https://umbrella.srsoftware.de">Umbrella</a> <? by ?> <a href="https://srsoftware.de">SRSoftware</a></div>
|
||||
</td>
|
||||
<td>
|
||||
<? Bank account ?>:<br/>
|
||||
<? bank account ?>:<br/>
|
||||
<? document.sender.bank_account ?>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -216,7 +216,7 @@
|
||||
<div class="ad"><? document.type ?> <? created with ?> <a href="https://umbrella.srsoftware.de">Umbrella</a> <? by ?> <a href="https://srsoftware.de">SRSoftware</a></div>
|
||||
</div>
|
||||
<div class="bank_account">
|
||||
<? Bank account ?>:<br/>
|
||||
<? bank account ?>:<br/>
|
||||
<? document.sender.bank_account ?>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import Menu from "./Components/Menu.svelte";
|
||||
import ResetPw from "./routes/user/ResetPw.svelte";
|
||||
import Search from "./routes/search/Search.svelte";
|
||||
import SendDoc from "./routes/document/Send.svelte";
|
||||
import User from "./routes/user/User.svelte";
|
||||
import ViewDoc from "./routes/document/View.svelte";
|
||||
|
||||
@@ -40,6 +41,7 @@
|
||||
<Route path="/" component={User} />
|
||||
<Route path="/document" component={DocList} />
|
||||
<Route path="/document/add" component={AddDoc} />
|
||||
<Route path="/document/:id/send" component={SendDoc} />
|
||||
<Route path="/document/:id/view" component={ViewDoc} />
|
||||
<Route path="/message/settings" component={Messages} />
|
||||
<Route path="/search" component={Search} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {onMount} from 'svelte';
|
||||
import {t} from '../translations.svelte.js';
|
||||
let { caption, onselect = (contact) => console.log('selected '+contact.FN||contact.ORG) } = $props();
|
||||
let message = t('contacts.loading');
|
||||
let message = t('loading');
|
||||
let contacts = $state(null);
|
||||
let value = 0;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div title={'task_'+task.id}>
|
||||
{#if task.estimated_time}
|
||||
<span class="estimate" onclick={() => onSelect(task)}>
|
||||
{task.estimated_time} {t(task.estimated_time != 1 ? 'task.hours' : 'task.hour')}
|
||||
{task.estimated_time} {t(task.estimated_time != 1 ? 'hours' : 'hour')}
|
||||
</span>
|
||||
{/if}
|
||||
{task.name}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
{@html t('footer.message','<a href="https://srsoftware.de">SRSoftware</a>')}
|
||||
{@html t('advertisement','<a href="https://srsoftware.de">SRSoftware</a>')}
|
||||
</footer>
|
||||
@@ -1,6 +1,4 @@
|
||||
<script>
|
||||
import { t } from '../translations.svelte.js';
|
||||
|
||||
let { item, onclick } = $props();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -65,21 +65,21 @@
|
||||
|
||||
<form onsubmit={doLogin}>
|
||||
<fieldset>
|
||||
<legend>{t('login.Login')}</legend>
|
||||
<legend>{t('login')}</legend>
|
||||
<label>
|
||||
<input type="text" bind:value={credentials.username} required use:init />
|
||||
<span>{t('login.Email_or_Username')}</span>
|
||||
<span>{t('email_or_username')}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" bind:value={credentials.password} required />
|
||||
<span>{t('login.Password')}</span>
|
||||
<span>{t('password')}</span>
|
||||
</label>
|
||||
<button>{t('login.do_login')}</button>
|
||||
<a onclick={resetPW}>{t('login.forgot_pass')}</a>
|
||||
<button>{t('do_login')}</button>
|
||||
<a onclick={resetPW}>{t('forgot_pass')}</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset>
|
||||
<legend>{t('login.OIDC_Login')}</legend>
|
||||
<legend>{t('oidc_Login')}</legend>
|
||||
{#each services as service,i}
|
||||
<button onclick={() => redirectTo(service)}>{service}</button>
|
||||
{/each}
|
||||
|
||||
@@ -13,7 +13,7 @@ async function fetchModules(){
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
if (resp.ok){
|
||||
const arr = await resp.json();
|
||||
for (let entry of arr) modules.push({name:t('menu.'+entry.module),url:entry.url});
|
||||
for (let entry of arr) modules.push({name:t(entry.module),url:entry.url});
|
||||
} else {
|
||||
console.log('error');
|
||||
}
|
||||
@@ -28,13 +28,13 @@ onMount(fetchModules);
|
||||
</style>
|
||||
|
||||
<nav>
|
||||
<a onclick={() => router.navigate('/user')}>{t('menu.users')}</a>
|
||||
<a onclick={() => router.navigate('/document')}>{t('menu.documents')}</a>
|
||||
<a href="https://svelte.dev/tutorial/svelte/state" target="_blank">{t('menu.tutorial')}</a>
|
||||
<a onclick={() => router.navigate('/user')}>{t('users')}</a>
|
||||
<a onclick={() => router.navigate('/document')}>{t('documents')}</a>
|
||||
<a href="https://svelte.dev/tutorial/svelte/state" target="_blank">{t('tutorial')}</a>
|
||||
{#each modules as module,i}
|
||||
<a href={module.url}>{module.name}</a>
|
||||
{/each}
|
||||
{#if user.name }
|
||||
<a onclick={logout}>{t('menu.logout')}</a>
|
||||
<a onclick={logout}>{t('logout')}</a>
|
||||
{/if}
|
||||
</nav>
|
||||
@@ -43,7 +43,7 @@
|
||||
var resp = await fetch(url,{ credentials: 'include'});
|
||||
if (resp.ok){
|
||||
const types = await resp.json();
|
||||
docType = t('document.type_'+types[document.type]);
|
||||
docType = t('type_'+types[document.type]);
|
||||
} else {
|
||||
error = await resp.text();
|
||||
}
|
||||
@@ -97,49 +97,49 @@
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
{#if docType}
|
||||
<legend>{t('document.add_new',docType)}</legend>
|
||||
<legend>{t('add_new',docType)}</legend>
|
||||
{/if}
|
||||
{#if company}
|
||||
Company: {company.name}
|
||||
<fieldset>
|
||||
<legend>{t('document.customer')}</legend>
|
||||
<ContactSelector caption={t('document.select_customer')} onselect={contactSelected} />
|
||||
<legend>{t('customer')}</legend>
|
||||
<ContactSelector caption={t('select_customer')} onselect={contactSelected} />
|
||||
<label>
|
||||
<textarea bind:value={document.customer.name}></textarea>
|
||||
{t('document.customer_address')}
|
||||
{t('customer_address')}
|
||||
</label>
|
||||
<label>
|
||||
<input bind:value={document.customer.tax_id} />
|
||||
{t('document.tax_id')}
|
||||
{t('tax_id')}
|
||||
</label>
|
||||
<label>
|
||||
<input bind:value={document.customer.id} />
|
||||
{t('document.customer_id')}
|
||||
{t('customer_id')}
|
||||
</label>
|
||||
<label>
|
||||
<input bind:value={document.customer.email} />
|
||||
{t('document.email')}
|
||||
{t('email')}
|
||||
</label>
|
||||
</fieldset>
|
||||
{/if}
|
||||
<fieldset>
|
||||
<legend>{t('document.sender')}</legend>
|
||||
<legend>{t('sender')}</legend>
|
||||
<label>
|
||||
<textarea bind:value={document.sender.name}></textarea>
|
||||
{t('document.sender_name')}
|
||||
{t('sender_name')}
|
||||
</label>
|
||||
<label>
|
||||
<input bind:value={document.sender.tax_id} />
|
||||
{t('document.sender_tax_id')}
|
||||
{t('sender_tax_id')}
|
||||
</label>
|
||||
<label>
|
||||
<textarea bind:value={document.sender.bank_account}></textarea>
|
||||
{t('document.sender_bank_account')}
|
||||
{t('sender_bank_account')}
|
||||
</label>
|
||||
<label>
|
||||
<input bind:value={document.sender.court} />
|
||||
{t('document.sender_local_court')}
|
||||
{t('sender_local_court')}
|
||||
</label>
|
||||
</fieldset>
|
||||
<button onclick={submit}>{t('document.create_new')}</button>
|
||||
<button onclick={submit}>{t('create_new')}</button>
|
||||
</fieldset>
|
||||
@@ -35,7 +35,7 @@
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<h1>{t('task.estimated_times')}</h1>
|
||||
<h1>{t('estimated_times')}</h1>
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h1>{t('items.items')}</h1>
|
||||
<h1>{t('items')}</h1>
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
async function deleteDoc(ev,doc){
|
||||
if (confirm(t('document.really_delete',doc.number))){
|
||||
if (confirm(t('really_delete',doc.number))){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/document/${doc.id}`;
|
||||
const resp = await fetch(url,{
|
||||
credentials: 'include',
|
||||
@@ -63,12 +63,12 @@
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<legend>{selected_company ? t( 'document.list_of',selected_company.name) : t('document.list')}</legend>
|
||||
<legend>{selected_company ? t( 'list_of',selected_company.name) : t('document.list')}</legend>
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
{/if}
|
||||
<div>
|
||||
{t('document.select_company')}
|
||||
{t('select_company')}
|
||||
{#each Object.entries(companies) as [id,company]}
|
||||
<button onclick={() => load(company)}>{company.name}</button>
|
||||
{/each}
|
||||
@@ -77,15 +77,15 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('document.number')}</th>
|
||||
<th>{t('document.date')}</th>
|
||||
<th>{t('document.customer')}</th>
|
||||
<th>{t('document.gross_sum')}</th>
|
||||
<th>{t('document.type')}</th>
|
||||
<th>{t('document.state')}</th>
|
||||
<th>{t('number')}</th>
|
||||
<th>{t('date')}</th>
|
||||
<th>{t('customer')}</th>
|
||||
<th>{t('gross_sum')}</th>
|
||||
<th>{t('type')}</th>
|
||||
<th>{t('state')}</th>
|
||||
<th>
|
||||
{t('document.actions')}
|
||||
<TypeSelector caption={t('document.create_new')} bind:value={docType} onchange={createDoc} />
|
||||
{t('actions')}
|
||||
<TypeSelector caption={t('create_new_document')} bind:value={docType} onchange={createDoc} />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -96,11 +96,11 @@
|
||||
<td onclick={() => show(id)}>{document.date}</td>
|
||||
<td onclick={() => show(id)}>{document.customer.name.split('\n')[0]}</td>
|
||||
<td onclick={() => show(id)}>{document.sum/100 + document.currency}</td>
|
||||
<td onclick={() => show(id)}>{t('document.type_'+document.type)}</td>
|
||||
<td onclick={() => show(id)}>{t('document.state_'+document.state.name)}</td>
|
||||
<td onclick={() => show(id)}>{t('type_'+document.type)}</td>
|
||||
<td onclick={() => show(id)}>{t('state_'+document.state.name)}</td>
|
||||
<td>
|
||||
{#if document.state.id == 1}
|
||||
<button onclick={(ev) => deleteDoc(ev,document)}>{t('document.delete')}</button>
|
||||
<button onclick={(ev) => deleteDoc(ev,document)}>{t('delete')}</button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../../translations.svelte.js';
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
||||
import PriceEditor from '../../Components/PriceEditor.svelte';
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
|
||||
async function drop(number){
|
||||
let confirmed = confirm(t('document.confirm_deletion').replace('{pos}',document.positions[number].item));
|
||||
let confirmed = confirm(t('confirm_deletion').replace('{pos}',document.positions[number].item));
|
||||
if (!confirmed) return;
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/document/${document.id}/position`;
|
||||
const resp = await fetch(url,{
|
||||
@@ -50,14 +50,14 @@
|
||||
<table class="positions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('document.pos')}</th>
|
||||
<th>{t('document.code')}</th>
|
||||
<th>{t('document.title_or_desc')}</th>
|
||||
<th>{t('document.amount')}</th>
|
||||
<th>{t('document.unit')}</th>
|
||||
<th>{t('document.unit_price')}</th>
|
||||
<th>{t('document.net_price')}</th>
|
||||
<th>{t('document.tax_rate')}</th>
|
||||
<th>{t('pos')}</th>
|
||||
<th>{t('code')}</th>
|
||||
<th>{t('title_or_desc')}</th>
|
||||
<th>{t('amount')}</th>
|
||||
<th>{t('unit')}</th>
|
||||
<th>{t('unit_price')}</th>
|
||||
<th>{t('net_price')}</th>
|
||||
<th>{t('tax_rate')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -66,9 +66,9 @@
|
||||
{/each}
|
||||
<tr class="sums">
|
||||
<td colspan="2"></td>
|
||||
<td>{t('document.net_sum')}</td>
|
||||
<td>{t('net_sum')}</td>
|
||||
<td>{document.net_sum/100} {document.currency}</td>
|
||||
<td colspan="2">{t('document.gros_sum')}</td>
|
||||
<td colspan="2">{t('gros_sum')}</td>
|
||||
<td>{document.gross_sum/100} {document.currency}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
title:estimate.name,
|
||||
description:estimate.description.source,
|
||||
amount:estimate.estimated_time,
|
||||
unit:t('document.hours')
|
||||
unit:t('hours')
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
|
||||
function timeSelected(time){
|
||||
select({
|
||||
item_code:t('document.timetrack'),
|
||||
item_code:t('timetrack'),
|
||||
title:time.subject,
|
||||
description:time.description.source,
|
||||
amount:time.duration,
|
||||
unit:t('document.hours'),
|
||||
unit:t('hours'),
|
||||
time_id:time.id
|
||||
});
|
||||
}
|
||||
@@ -66,10 +66,10 @@
|
||||
|
||||
<div class="position_selector">
|
||||
<span class="tabs">
|
||||
<button onclick={() => source=0}>{t('document.items')}</button>
|
||||
<button onclick={() => source=1}>{t('document.estimated_times')}</button>
|
||||
<button onclick={() => source=2}>{t('document.timetrack')}</button>
|
||||
<button onclick={close}>{t('document.abort')}</button>
|
||||
<button onclick={() => source=0}>{t('items')}</button>
|
||||
<button onclick={() => source=1}>{t('estimated_times')}</button>
|
||||
<button onclick={() => source=2}>{t('timetrack')}</button>
|
||||
<button onclick={close}>{t('abort')}</button>
|
||||
</span>
|
||||
{#if source == 0}
|
||||
<ItemList company_id={doc.company.id} onSelect={itemSelected} />
|
||||
|
||||
24
frontend/src/routes/document/Send.svelte
Normal file
24
frontend/src/routes/document/Send.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../../translations.svelte.js';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
|
||||
const router = useTinyRouter();
|
||||
|
||||
let { id } = $props();
|
||||
let error = $state(null);
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
|
||||
<fieldset>
|
||||
<legend>{t('customer_email')}</legend>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t('content')}</legend>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t('actions')}</legend>
|
||||
</fieldset>
|
||||
@@ -1,9 +1,9 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
import {t} from '../../translations.svelte.js';
|
||||
let { caption = t('document.select_state'), selected = $bindable(0), onchange = (val) => console.log('changed to '+val)} = $props();
|
||||
let { caption = t('select_state'), selected = $bindable(0), onchange = (val) => console.log('changed to '+val)} = $props();
|
||||
|
||||
let message = $state(t('document.loading'));
|
||||
let message = $state(t('loading'));
|
||||
let states = $state(null);
|
||||
|
||||
async function loadStates(){
|
||||
@@ -22,7 +22,7 @@
|
||||
{#if states}
|
||||
<select bind:value={selected} onchange={() => onchange(selected)}>
|
||||
{#each Object.entries(states) as [k,s]}
|
||||
<option value={+k}>{t('document.state_'+s.toLowerCase())}</option>
|
||||
<option value={+k}>{t('state_'+s.toLowerCase())}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {onMount} from 'svelte';
|
||||
import {t} from '../../translations.svelte.js';
|
||||
let { caption, company, value = $bindable(0), onchange = () => console.log('changed')} = $props();
|
||||
let message = t('document.loading');
|
||||
let message = t('loading');
|
||||
let templates = $state(null);
|
||||
|
||||
async function loadTemplates(){
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<select bind:value onchange={onchange}>
|
||||
<option value={0}>{caption}</option>
|
||||
{#each Object.entries(types) as [id,type]}
|
||||
<option value={id}>{t('document.type_'+type)}</option>
|
||||
<option value={id}>{t('type_'+type)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
import PositionSelector from './PositionSelector.svelte';
|
||||
import StateSelector from './StateSelector.svelte';
|
||||
import TemplateSelector from './TemplateSelector.svelte';
|
||||
|
||||
const router = useTinyRouter();
|
||||
|
||||
let { id } = $props();
|
||||
let error = $state(null);
|
||||
let doc = $state(null);
|
||||
let pdfDisabled = $state(false);
|
||||
let sndDisabled = $state(false);
|
||||
let position_select = $state(false);
|
||||
|
||||
let editable = $derived(doc.state == 1);
|
||||
@@ -30,8 +35,8 @@
|
||||
|
||||
async function changeState(newVal){
|
||||
let success = false;
|
||||
if (doc.state == 1 || confirm(t('document.confirm_state'))){
|
||||
success = await submit('state',newVal);
|
||||
if (doc.state == 1 || confirm(t('confirm_state'))){
|
||||
success = await update('state',newVal);
|
||||
}
|
||||
if (success) {
|
||||
doc.state = newVal;
|
||||
@@ -42,7 +47,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function submit(path,newValue){
|
||||
async function update(path,newValue){
|
||||
const parts = path.split('.');
|
||||
if (parts.length<1) return false;
|
||||
let data = newValue;
|
||||
@@ -79,9 +84,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function render(){
|
||||
async function render(ev){
|
||||
pdfDisabled = true;
|
||||
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/document/${doc.id}/pdf`;
|
||||
location.href = url;
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
if (resp.ok){
|
||||
error = null;
|
||||
const blob = await resp.blob();
|
||||
const parts = resp.headers.get("Content-disposition").split(";");
|
||||
const filename = parts[1].split('=')[1].split('"').join('');
|
||||
var fileLink = document.createElement('a');
|
||||
fileLink.href = URL.createObjectURL(blob);
|
||||
fileLink.download = filename;
|
||||
fileLink.click();
|
||||
} else {
|
||||
error = await resp.text();
|
||||
}
|
||||
pdfDisabled = false;
|
||||
}
|
||||
|
||||
onMount(loadDoc);
|
||||
@@ -93,90 +113,90 @@
|
||||
|
||||
{#if doc}
|
||||
<fieldset class="customer">
|
||||
<legend>{t('document.customer')}</legend>
|
||||
<legend>{t('customer')}</legend>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<MultilineEditor bind:value={doc.customer.name} editable={editable} onSet={(val) => submit('customer.name',val)} />
|
||||
<MultilineEditor bind:value={doc.customer.name} editable={editable} onSet={(val) => update('customer.name',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.customer_id')}:</th>
|
||||
<th>{t('customer_id')}:</th>
|
||||
<td>
|
||||
<LineEditor bind:value={doc.customer.id} editable={editable} onSet={(val) => submit('customer.id',val)} />
|
||||
<LineEditor bind:value={doc.customer.id} editable={editable} onSet={(val) => update('customer.id',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.tax_id')}:</th>
|
||||
<th>{t('tax_id')}:</th>
|
||||
<td>
|
||||
<LineEditor bind:value={doc.customer.tax_id} editable={editable} onSet={(val) => submit('customer.tax_id',val)} />
|
||||
<LineEditor bind:value={doc.customer.tax_id} editable={editable} onSet={(val) => update('customer.tax_id',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.email')}:</th>
|
||||
<th>{t('email')}:</th>
|
||||
<td>
|
||||
<LineEditor bind:value={doc.customer.email} editable={editable} onSet={(val) => submit('customer.email',val)} />
|
||||
<LineEditor bind:value={doc.customer.email} editable={editable} onSet={(val) => update('customer.email',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="sender">
|
||||
<legend>{t('document.sender')}</legend>
|
||||
<legend>{t('sender')}</legend>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<MultilineEditor bind:value={doc.sender.name} editable={editable} onSet={(val) => submit('sender.name',val)} />
|
||||
<MultilineEditor bind:value={doc.sender.name} editable={editable} onSet={(val) => update('sender.name',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.court')}:</th>
|
||||
<th>{t('local_court')}:</th>
|
||||
<td>
|
||||
<LineEditor bind:value={doc.sender.court} editable={editable} onSet={(val) => submit('sender.court',val)} />
|
||||
<LineEditor bind:value={doc.sender.court} editable={editable} onSet={(val) => update('sender.court',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.tax_id')}:</th>
|
||||
<th>{t('tax_id')}:</th>
|
||||
<td>
|
||||
<LineEditor bind:value={doc.sender.tax_id} editable={editable} onSet={(val) => submit('sender.tax_id',val)} />
|
||||
<LineEditor bind:value={doc.sender.tax_id} editable={editable} onSet={(val) => update('sender.tax_id',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.bank_account')}:</th>
|
||||
<th>{t('bank_account')}:</th>
|
||||
<td>
|
||||
<MultilineEditor bind:value={doc.sender.bank_account} editable={editable} onSet={(val) => submit('sender.bank_account',val)} />
|
||||
<MultilineEditor bind:value={doc.sender.bank_account} editable={editable} onSet={(val) => update('sender.bank_account',val)} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="invoice_data">
|
||||
<legend>{t('document.type_'+doc.type)}</legend>
|
||||
<legend>{t('type_'+doc.type)}</legend>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('document.number')}:</th>
|
||||
<td><LineEditor bind:value={doc.number} editable={editable} onSet={(val) => submit('number',val)} /></td>
|
||||
<th>{t('number')}:</th>
|
||||
<td><LineEditor bind:value={doc.number} editable={editable} onSet={(val) => update('number',val)} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.state')}:</th>
|
||||
<StateSelector selected={doc.state} onchange={changeState} onSet={(val) => submit('state',val)} />
|
||||
<th>{t('state')}:</th>
|
||||
<StateSelector selected={doc.state} onchange={changeState} onSet={(val) => update('state',val)} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.date')}:</th>
|
||||
<LineEditor bind:value={doc.date} editable={editable} onSet={(val) => submit('date',val)} />
|
||||
<th>{t('date')}:</th>
|
||||
<LineEditor bind:value={doc.date} editable={editable} onSet={(val) => update('date',val)} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.delivery')}:</th>
|
||||
<LineEditor bind:value={doc.delivery} editable={editable} onSet={(val) => submit('delivery',val)} />
|
||||
<th>{t('delivery_date')}:</th>
|
||||
<LineEditor bind:value={doc.delivery} editable={editable} onSet={(val) => update('delivery',val)} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('document.template')}:</th>
|
||||
<th>{t('template')}:</th>
|
||||
<td>
|
||||
{#if editable}
|
||||
<TemplateSelector company={doc.company.id} bind:value={doc.template.id} onchange={() => submit('template_id',doc.template.id)} />
|
||||
<TemplateSelector company={doc.company.id} bind:value={doc.template.id} onchange={() => update('template_id',doc.template.id)} />
|
||||
{:else}
|
||||
{doc.template.name}
|
||||
{/if}
|
||||
@@ -186,30 +206,30 @@
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="clear">
|
||||
<legend>{t('document.head')}</legend>
|
||||
<MarkdownEditor bind:value={doc.head} editable={editable} onSet={(val) => submit('head',val)} />
|
||||
<legend>{t('head')}</legend>
|
||||
<MarkdownEditor bind:value={doc.head} editable={editable} onSet={(val) => update('head',val)} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>
|
||||
{t('document.positions')}
|
||||
{t('positions')}
|
||||
{#if editable}
|
||||
<button onclick={() => position_select = true}>{t('document.add_position')}</button>
|
||||
<button onclick={() => position_select = true}>{t('add_position')}</button>
|
||||
{/if}
|
||||
</legend>
|
||||
<PositionList bind:document={doc} {submit} bind:error={error} />
|
||||
<PositionList bind:document={doc} {update} bind:error={error} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t('document.footer')}</legend>
|
||||
<MarkdownEditor bind:value={doc.footer} editable={editable} onSet={(val) => submit('footer',val)} />
|
||||
<legend>{t('footer')}</legend>
|
||||
<MarkdownEditor bind:value={doc.footer} editable={editable} onSet={(val) => update('footer',val)} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t('document.actions')}</legend>
|
||||
<button onclick={render}>{t('document.create_pdf')}</button>
|
||||
<legend>{t('actions')}</legend>
|
||||
<button onclick={render} disabled={pdfDisabled}>{t('create_pdf')}</button>
|
||||
<button onclick={() => router.navigate(`/document/${doc.id}/send`)} >{t('send_document')}</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>TODO</legend>
|
||||
<ul>
|
||||
<li>Button zum Rendern des Dokuments einbauen</li>
|
||||
<li>Button zum Versenden des Dokuments einbauen</li>
|
||||
<li>Preise in den Company-Einstellungen ändern, wenn für eine Position der Preis geändert wird – siehe <em>documents.db</em>, Tabelle <em>customer_prices</em></li>
|
||||
<li>Preise in der Tabelle neu berechnen, wenn Positionen geändert werden</li>
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<legend>{t('message.messages')}</legend>
|
||||
<legend>{t('messages')}</legend>
|
||||
</fieldset>
|
||||
@@ -16,7 +16,7 @@
|
||||
});
|
||||
if (resp.ok){
|
||||
html = await resp.text();
|
||||
if (!html) html = t('search.nothing_found');
|
||||
if (!html) html = t('nothing_found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,23 @@
|
||||
</script>
|
||||
|
||||
<fieldset class="search">
|
||||
<legend>{t('search.search')}</legend>
|
||||
<legend>{t('search')}</legend>
|
||||
<form onsubmit={doSearch}>
|
||||
<label>
|
||||
{t('search.key')}
|
||||
{t('key')}
|
||||
<input type="text" bind:value={key} />
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={fulltext} />
|
||||
{t('search.fulltext')}
|
||||
{t('fulltext')}
|
||||
</label>
|
||||
<button type="submit">{t('search.go')}</button>
|
||||
<button type="submit">{t('go')}</button>
|
||||
</form>
|
||||
</fieldset>
|
||||
{#if html}
|
||||
<fieldset>
|
||||
<legend>
|
||||
{t('search.results')}
|
||||
{t('results')}
|
||||
</legend>
|
||||
{@html html}
|
||||
</fieldset>
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
|
||||
{#if connections.length>0}
|
||||
<fieldset tabindex="0">
|
||||
<legend>{t('user.connected_services')}</legend>
|
||||
<legend>{t('connected_services')}</legend>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('user.service')}</th>
|
||||
<th>{t('user.foreign_id')}</th>
|
||||
<th>{t('user.actions')}</th>
|
||||
<th>{t('service')}</th>
|
||||
<th>{t('foreign_id')}</th>
|
||||
<th>{t('actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -50,7 +50,7 @@
|
||||
<td>{connection.service_id}</td>
|
||||
<td>{connection.foreign_id}</td>
|
||||
<td>
|
||||
<button onclick={() => unlink(connection)}>{t('user.unlink')}</button>
|
||||
<button onclick={() => unlink(connection)}>{t('unlink')}</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
let oldPass = $state("");
|
||||
let newPass = $state("");
|
||||
let repeat = $state("");
|
||||
let caption = $state(t('user.update'));
|
||||
let caption = $state(t('update'));
|
||||
|
||||
let oldEmpty = $derived(!/\S/.test(oldPass));
|
||||
let newEmpty = $derived(!/\S/.test(newPass));
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
async function submit(){
|
||||
caption = t('user.data_sent');
|
||||
caption = t('data_sent');
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/user/password`;
|
||||
const data = {
|
||||
old: oldPass,
|
||||
@@ -33,9 +33,9 @@
|
||||
});
|
||||
|
||||
if (resp.ok){
|
||||
caption = t('user.saved');
|
||||
caption = t('saved');
|
||||
} else {
|
||||
caption = t('user.failed');
|
||||
caption = t('failed');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -47,27 +47,27 @@
|
||||
</style>
|
||||
|
||||
<fieldset class="overlay">
|
||||
<legend>{t('user.edit_password')}</legend>
|
||||
<legend>{t('edit_password')}</legend>
|
||||
<label>
|
||||
<input type="password" bind:value={oldPass} /> {t('user.old_password')}
|
||||
<input type="password" bind:value={oldPass} /> {t('old_password')}
|
||||
{#if oldEmpty}
|
||||
<span class="error">{t('user.must_not_be_empty')}</span>
|
||||
<span class="error">{t('must_not_be_empty')}</span>
|
||||
{/if}
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" bind:value={newPass} /> {t('user.new_password')}
|
||||
<input type="password" bind:value={newPass} /> {t('new_password')}
|
||||
{#if newEmpty}
|
||||
<span class="error">{t('user.must_not_be_empty')}</span>
|
||||
<span class="error">{t('must_not_be_empty')}</span>
|
||||
{/if}
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" bind:value={repeat} /> {t('user.repeat_new_password')}
|
||||
<input type="password" bind:value={repeat} /> {t('repeat_new_password')}
|
||||
{#if mismatch}
|
||||
<span class="error">{t('user.mismatch')}</span>
|
||||
<span class="error">{t('mismatch')}</span>
|
||||
{/if}
|
||||
</label>
|
||||
<button onclick={submit} disabled={sent||oldEmpty||newEmpty||mismatch}>{caption}</button>
|
||||
<button onclick={abort} disabled={sent}>{t('user.abort')}</button>
|
||||
<button onclick={abort} disabled={sent}>{t('abort')}</button>
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
let { serviceName } = $props();
|
||||
let service = $state({})
|
||||
let caption = $state(t('user.save_service'));
|
||||
let message = $state(t('user.loading_data'));
|
||||
let caption = $state(t('save_service'));
|
||||
let message = $state(t('loading_data'));
|
||||
let router = useTinyRouter();
|
||||
let disabled = $state(false);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
});
|
||||
|
||||
async function update(){
|
||||
caption = t('user.data_sent');
|
||||
caption = t('data_sent');
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/user/oidc/${serviceName}`;
|
||||
const resp = await fetch(url,{
|
||||
credentials: 'include',
|
||||
@@ -31,7 +31,7 @@
|
||||
body: JSON.stringify(service)
|
||||
});
|
||||
if (resp.ok){
|
||||
caption = t('user.saved');
|
||||
caption = t('saved');
|
||||
router.navigate('/user');
|
||||
} else {
|
||||
caption = await resp.text();
|
||||
@@ -41,30 +41,30 @@
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<legend>{t('user.edit_service',serviceName)}</legend>
|
||||
<legend>{t('edit_service',serviceName)}</legend>
|
||||
{#if service.name || !serviceName}
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('user.name')}</th>
|
||||
<th>{t('name')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={service.name} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.client_id')}</th>
|
||||
<th>{t('client_id')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={service.client_id} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.client_secret')}</th>
|
||||
<th>{t('client_secret')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={service.client_secret} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.base_url')}</th>
|
||||
<th>{t('base_url')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={service.url} />
|
||||
</td>
|
||||
@@ -72,7 +72,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<button onclick={update} {disabled}>{caption}</button>
|
||||
<button onclick={() => router.navigate('/user')} {disabled}>{t('user.abort')}</button>
|
||||
<button onclick={() => router.navigate('/user')} {disabled}>{t('abort')}</button>
|
||||
{:else}
|
||||
{message}
|
||||
{/if}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
let editUser = $state(null);
|
||||
let options = $state([]);
|
||||
let sent = $state(false);
|
||||
let caption = $state(t('user.save_user'));
|
||||
let message = $state(t('user.loading_data'));
|
||||
let caption = $state(t('save_user'));
|
||||
let message = $state(t('loading_data'));
|
||||
|
||||
onMount(async () => {
|
||||
let url = `${location.protocol}//${location.host.replace('5173','8080')}/themes.json`;
|
||||
@@ -44,7 +44,7 @@
|
||||
async function save(ev){
|
||||
ev.preventDefault();
|
||||
sent = true;
|
||||
caption = t('user.data_sent');
|
||||
caption = t('data_sent');
|
||||
let method = 'PATCH';
|
||||
let url = null;
|
||||
if (user_id) {
|
||||
@@ -59,54 +59,54 @@
|
||||
body: JSON.stringify(editUser)
|
||||
});
|
||||
if (resp.ok){
|
||||
caption = t('user.saved');
|
||||
caption = t('saved');
|
||||
checkUser();
|
||||
router.navigate('/user');
|
||||
} else {
|
||||
caption = t('user.failed');
|
||||
caption = t('failed');
|
||||
sent = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<legend>{t('user.editing',user_id?user_id:'')}</legend>
|
||||
<legend>{t('editing',user_id?user_id:'')}</legend>
|
||||
{#if editUser}
|
||||
<form onsubmit={save}>
|
||||
<table>
|
||||
<tbody>
|
||||
{#if editUser.id}
|
||||
<tr>
|
||||
<th>{t('user.id')}</th>
|
||||
<th>{t('id')}</th>
|
||||
<td>{editUser.id}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
<tr>
|
||||
<th>{t('user.name')}</th>
|
||||
<th>{t('name')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={editUser.name} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.email')}</th>
|
||||
<th>{t('email')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={editUser.email} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.language')}</th>
|
||||
<th>{t('language')}</th>
|
||||
<td>
|
||||
<input type="text" bind:value={editUser.language} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.password')}</th>
|
||||
<th>{t('password')}</th>
|
||||
<td>
|
||||
<input type="password" bind:value={editUser.password} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.theme')}</th>
|
||||
<th>{t('theme')}</th>
|
||||
<td>
|
||||
<select bind:value={editUser.theme}>
|
||||
{#each options as entry,i}
|
||||
|
||||
@@ -33,19 +33,19 @@
|
||||
|
||||
<fieldset tabindex="0">
|
||||
<legend>
|
||||
{t('user.list')}
|
||||
{t('list')}
|
||||
{#if user.permissions.includes('CREATE_USERS')}
|
||||
<button onclick={() => router.navigate('/user/create')}>{t('user.create_new')}</button>
|
||||
<button onclick={() => router.navigate('/user/create')}>{t('create_new')}</button>
|
||||
{/if}
|
||||
</legend>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('user.id')}</th>
|
||||
<th>{t('user.name')}</th>
|
||||
<th>{t('user.email')}</th>
|
||||
<th>{t('user.language')}</th>
|
||||
<th>{t('user.actions')}</th>
|
||||
<th>{t('id')}</th>
|
||||
<th>{t('name')}</th>
|
||||
<th>{t('email')}</th>
|
||||
<th>{t('language')}</th>
|
||||
<th>{t('actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -57,10 +57,10 @@
|
||||
<td>{u.language}</td>
|
||||
<td>
|
||||
{#if user.permissions.includes('IMPERSONATE')}
|
||||
<button onclick={() => impersonate(u.id)}>{t('user.impersonate')}</button>
|
||||
<button onclick={() => impersonate(u.id)}>{t('impersonate')}</button>
|
||||
{/if}
|
||||
{#if user.permissions.includes('UPDATE_USERS')}
|
||||
<button onclick={() => router.navigate(`/user/${u.id}/edit`)}>{t('user.edit')}</button>
|
||||
<button onclick={() => router.navigate(`/user/${u.id}/edit`)}>{t('edit')}</button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -45,16 +45,16 @@
|
||||
|
||||
<fieldset tabindex="0">
|
||||
<legend>
|
||||
{t('user.login_services')}
|
||||
{t('login_services')}
|
||||
{#if user.permissions.includes('MANAGE_LOGIN_SERVICES')}
|
||||
<button onclick={() => router.navigate('/user/oidc/add')}>{t('user.add_login_service')}</button>
|
||||
<button onclick={() => router.navigate('/user/oidc/add')}>{t('add_login_service')}</button>
|
||||
{/if}
|
||||
</legend>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('user.service')}</th>
|
||||
<th>{t('user.actions')}</th>
|
||||
<th>{t('service')}</th>
|
||||
<th>{t('actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -62,10 +62,10 @@
|
||||
<tr>
|
||||
<td>{service}</td>
|
||||
<td>
|
||||
<button onclick={() => connect(service)}>{t('user.connect_service')}</button>
|
||||
<button onclick={() => connect(service)}>{t('connect_service')}</button>
|
||||
{#if user.permissions.includes('MANAGE_LOGIN_SERVICES')}
|
||||
<button onclick={() => router.navigate(`/user/oidc/edit/${service}`)}>{t('user.edit')}</button>
|
||||
<button onclick={() => drop(service)}>{t('user.delete')}</button>
|
||||
<button onclick={() => router.navigate(`/user/oidc/edit/${service}`)}>{t('edit')}</button>
|
||||
<button onclick={() => drop(service)}>{t('delete')}</button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
const router = useTinyRouter();
|
||||
|
||||
let message = $state(t('user.processing_code'));
|
||||
let message = $state(t('processing_code'));
|
||||
onMount(async () => {
|
||||
let params = new URLSearchParams(location.search);
|
||||
|
||||
|
||||
@@ -11,53 +11,53 @@
|
||||
</script>
|
||||
<fieldset>
|
||||
<legend>
|
||||
{t('user.your_profile')}
|
||||
<button onclick={() => router.navigate(`/user/${user.id}/edit`)}>{t('user.edit')}</button>
|
||||
<button onclick={() => router.navigate(`/message/settings`)}>{t('messages.settings')}</button>
|
||||
{t('your_profile')}
|
||||
<button onclick={() => router.navigate(`/user/${user.id}/edit`)}>{t('edit')}</button>
|
||||
<button onclick={() => router.navigate(`/message/settings`)}>{t('settings')}</button>
|
||||
|
||||
</legend>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('user.id')}</th>
|
||||
<th>{t('id')}</th>
|
||||
<td>{user.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.name')}</th>
|
||||
<th>{t('name')}</th>
|
||||
<td>{user.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.login')}</th>
|
||||
<th>{t('login')}</th>
|
||||
<td>{user.login}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.email')}</th>
|
||||
<th>{t('email')}</th>
|
||||
<td>{user.email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.language')}</th>
|
||||
<th>{t('language')}</th>
|
||||
<td>{user.language}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.theme')}</th>
|
||||
<th>{t('theme')}</th>
|
||||
<td>{user.theme}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.password')}</th>
|
||||
<th>{t('password')}</th>
|
||||
<td>
|
||||
{#if editPassword}
|
||||
<EditPassword bind:editPassword={editPassword} />
|
||||
{:else}
|
||||
<button onclick={() => editPassword = true}>{t('user.edit_password')}</button>
|
||||
<button onclick={() => editPassword = true}>{t('edit_password')}</button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('user.permissions')}</th>
|
||||
<th>{t('permissions')}</th>
|
||||
<td>
|
||||
<ul>
|
||||
{#each user.permissions as permission,i}
|
||||
<li>{t('user.'+permission)}</li>
|
||||
<li>{t(permission)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</td>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
|
||||
let mail = "";
|
||||
let caption = t('user.send_mail');
|
||||
let caption = t('send_mail');
|
||||
let error = null;
|
||||
const router = useTinyRouter();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
body : mail
|
||||
});
|
||||
if (resp.ok) {
|
||||
caption = t('user.data_sent');
|
||||
caption = t('data_sent');
|
||||
} else {
|
||||
caption = await resp.text();
|
||||
}
|
||||
@@ -58,10 +58,10 @@
|
||||
|
||||
<form onsubmit={submit}>
|
||||
<fieldset>
|
||||
<legend>{t('user.reset_pw')}</legend>
|
||||
<legend>{t('reset_pw')}</legend>
|
||||
<label>
|
||||
<input type="email" bind:value={mail}/>
|
||||
{t('user.enter_email')}
|
||||
{t('enter_email')}
|
||||
</label>
|
||||
<button type="submit">{caption}</button>
|
||||
{#if error}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1>{t('user.user_module')}</h1>
|
||||
<h1>{t('user_module')}</h1>
|
||||
|
||||
<Profile />
|
||||
<Services />
|
||||
|
||||
@@ -1,162 +1,166 @@
|
||||
{
|
||||
"contacts": {
|
||||
"loading": "lade…"
|
||||
},
|
||||
"document": {
|
||||
"abort": "abbrechen",
|
||||
"actions": "Aktionen",
|
||||
"add_new": "{0} anlegen",
|
||||
"add_position": "hinzufügen",
|
||||
"amount": "Menge",
|
||||
"bank_account": "Bankverbindung",
|
||||
"code": "Code",
|
||||
"confirm_deletion": "Soll '{pos}' wirklich gelöscht werden?",
|
||||
"court": "Amtsgericht",
|
||||
"create_new": "neues Dokument",
|
||||
"customer": "Kunde",
|
||||
"customer_address": "Adresse",
|
||||
"customer_id": "Kundennummer",
|
||||
"date": "Datum",
|
||||
"delete": "löschen",
|
||||
"email": "E-Mail",
|
||||
"estimated_time": "geschätzte Zeit",
|
||||
"estimated_times": "geschätzte Zeiten",
|
||||
"footer": "Fuß-Text",
|
||||
"gross_sum": "Brutto-Summe",
|
||||
"head": "Kopf-Text",
|
||||
"hours": "Stunden",
|
||||
"items": "Artikel",
|
||||
"list": "Dokumente",
|
||||
"list_of": "Dokumente von {0}",
|
||||
"net_price": "Nettopreis",
|
||||
"number": "Nummer",
|
||||
"pos": "Pos",
|
||||
"positions": "Positionen",
|
||||
"select_company" : "Wählen Sie eine ihrer Firmen:",
|
||||
"select_customer": "Kunde auswählen",
|
||||
"sender": "Absender",
|
||||
"sender_bank_account": "Bankverbindung",
|
||||
"sender_local_court": "Amtsgericht",
|
||||
"sender_name": "Name",
|
||||
"sender_tax_id": "Steuernummer",
|
||||
"state": "Status",
|
||||
"state_declined": "abgelehnt",
|
||||
"state_delayed": "verspätet",
|
||||
"state_error": "Fehler",
|
||||
"state_new":"neu",
|
||||
"state_payed": "bezahlt",
|
||||
"state_sent": "versendet",
|
||||
"tax_id": "Steuernummer",
|
||||
"tax_rate": "Steuersatz",
|
||||
"timetrack": "Zeiterfassung",
|
||||
"title_or_desc": "Titel/Beschreibung",
|
||||
"type": "Dokumententyp",
|
||||
"type_confirmation": "Bestätigung",
|
||||
"type_invoice": "Rechnung",
|
||||
"type_offer": "Angebot",
|
||||
"type_reminder": "Erinnerung",
|
||||
"unit": "Einheit",
|
||||
"unit_price": "Preis/Einheit"
|
||||
},
|
||||
"footer": {
|
||||
"message" : "Umbrella ist ein Produkt von {0}."
|
||||
},
|
||||
"home" : {
|
||||
"Welcome" : "Willkommen, {0}"
|
||||
},
|
||||
"items": {
|
||||
"items": "Artikel"
|
||||
},
|
||||
"login" : {
|
||||
"do_login" : "anmelden",
|
||||
"Email_or_Username": "Email oder Nutzername",
|
||||
"forgot_pass" : "Password vergessen?",
|
||||
"Login" : "Anmeldung",
|
||||
"OIDC_Login" : "Anmeldung mit OIDC",
|
||||
"Password" : "Passwort"
|
||||
},
|
||||
"menu" : {
|
||||
"bookmark": "Lesezeichen",
|
||||
"company": "Firma",
|
||||
"contact": "Kontakte",
|
||||
"document": "Dokumente",
|
||||
"documents": "Dokumente",
|
||||
"files": "Dateien",
|
||||
"items": "Items",
|
||||
"logout": "Abmelden",
|
||||
"message": "Benachrichtigungen",
|
||||
"model": "Modelle",
|
||||
"notes": "Notizen",
|
||||
"project": "Projekte",
|
||||
"stock": "Inventar",
|
||||
"task": "Aufgaben",
|
||||
"time": "Zeiterfassung",
|
||||
"tutorial": "Tutorial",
|
||||
"user": "Benutzer",
|
||||
"users": "Benutzer",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"abort": "abbrechen",
|
||||
"actions": "Aktionen",
|
||||
"add_login_service": "Login-Service anlegen",
|
||||
"add_new": "{0} anlegen",
|
||||
"add_position": "hinzufügen",
|
||||
"advertisement" : "Umbrella ist ein Produkt von {0}.",
|
||||
"amount": "Menge",
|
||||
"bank_account": "Bankverbindung",
|
||||
"base_url": "Basis-URL",
|
||||
"bookmark": "Lesezeichen",
|
||||
"client_id": "Client-ID",
|
||||
"client_secret": "Client-Geheimnis",
|
||||
"code": "Code",
|
||||
"connect_service": "mit Service verbinden",
|
||||
"connected_services": "verbundene Login-Services",
|
||||
"confirm_deletion": "Soll '{pos}' wirklich gelöscht werden?",
|
||||
"company": "Firma",
|
||||
"contact": "Kontakte",
|
||||
"contained_tax": "enthaltene Steuer",
|
||||
"content": "Inhalt",
|
||||
"create_new_document": "neues Dokument",
|
||||
"create_new_user": "Neuen Benutzer anlegen",
|
||||
"CREATE_USERS": "Nutzer anlegen",
|
||||
"create_pdf": "PDF erzeugen",
|
||||
"customer_address": "Adresse",
|
||||
"customer_email": "Emailadresse des Kunden",
|
||||
"customer": "Kunde",
|
||||
"customer_id": "Kundennummer",
|
||||
"data_sent": "Daten übermittelt",
|
||||
"date": "Datum",
|
||||
"delete": "löschen",
|
||||
"DELETE_USERS": "Nutzer löschen",
|
||||
"delivery_date": "Lieferdatum",
|
||||
"description": "Beschreibung",
|
||||
"document": "Dokumente",
|
||||
"documents": "Dokumente",
|
||||
"do_login" : "anmelden",
|
||||
"edit": "Bearbeiten",
|
||||
"editing": "Nutzer {0} bearbeiten",
|
||||
"edit_password": "Passwort ändern",
|
||||
"edit_service": "Login-Service \"{0}\" bearbeiten",
|
||||
"email": "E-Mail",
|
||||
"email_or_username": "Email oder Nutzername",
|
||||
"estimated_time": "geschätzte Zeit",
|
||||
"estimated_times": "geschätzte Zeiten",
|
||||
|
||||
"failed": "fehlgeschlagen",
|
||||
"files": "Dateien",
|
||||
"footer": "Fuß-Text",
|
||||
"foreign_id": "externe Kennung",
|
||||
"forgot_pass" : "Password vergessen?",
|
||||
"fulltext": "Volltextsuche",
|
||||
|
||||
"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",
|
||||
|
||||
"head": "Kopf-Text",
|
||||
"hours": "Stunden",
|
||||
|
||||
"id": "Id",
|
||||
"impersonate": "zu Nutzer wechseln",
|
||||
"IMPERSONATE": "Nutzer wechseln",
|
||||
"invoice": "Rechnung",
|
||||
"items": "Artikel",
|
||||
|
||||
"key": "Suchbegriff",
|
||||
|
||||
"language": "Sprache",
|
||||
"list": "Dokumente",
|
||||
"list_of": "Dokumente von {0}",
|
||||
"LIST_USERS": "Nutzer auflisten",
|
||||
"loading": "lade…",
|
||||
"loading_data": "Daten werden geladen…",
|
||||
"local_court": "Amtsgericht",
|
||||
"login" : "Anmeldung",
|
||||
"login_services": "Login-Services",
|
||||
"logout": "Abmelden",
|
||||
|
||||
"MANAGE_LOGIN_SERVICES": "Login-Services verwalten",
|
||||
"messages": "Benachrichtigungen",
|
||||
"model": "Modelle",
|
||||
"mismatch": "ungleich",
|
||||
"must_not_be_empty": "darf nicht leer sein",
|
||||
|
||||
"name": "Name",
|
||||
"net_price": "Nettopreis",
|
||||
"net_sum": "Netto-Summe",
|
||||
"new_password": "neues Passwort",
|
||||
"notes": "Notizen",
|
||||
"number": "Nummer",
|
||||
|
||||
"oidc_Login" : "Anmeldung mit OIDC",
|
||||
"old_password": "altes Passwort",
|
||||
|
||||
"password" : "Passwort",
|
||||
"permissions": "Berechtigungen",
|
||||
"pos": "Pos",
|
||||
"position": "Position",
|
||||
"positions": "Positionen",
|
||||
"price": "Preis",
|
||||
"processing_code": "Code wird verarbeitet…",
|
||||
"project": "Projekte",
|
||||
|
||||
"repeat_new_password": "Wiederholung",
|
||||
"results": "Ergebnisse",
|
||||
|
||||
"saved": "gespeichert",
|
||||
"save_service": "Service speichern",
|
||||
"save_user": "Nutzer speichern",
|
||||
"search": "Suche",
|
||||
"select_company" : "Wählen Sie eine ihrer Firmen:",
|
||||
"select_customer": "Kunde auswählen",
|
||||
"sender": "Absender",
|
||||
"sender_bank_account": "Bankverbindung",
|
||||
"sender_local_court": "Amtsgericht",
|
||||
"sender_name": "Name",
|
||||
"sender_tax_id": "Steuernummer",
|
||||
"sent_email": "Email gesendet",
|
||||
"service": "Service",
|
||||
"settings" : "Eisntellungen",
|
||||
"state": "Status",
|
||||
"state_declined": "abgelehnt",
|
||||
"state_delayed": "verspätet",
|
||||
"state_error": "Fehler",
|
||||
"state_new":"neu",
|
||||
"state_payed": "bezahlt",
|
||||
"state_sent": "versendet",
|
||||
"status" : {
|
||||
"403": "Zugriff verweigert",
|
||||
"404": "Seite nicht gefunden",
|
||||
"501": "Nicht implementiert"
|
||||
},
|
||||
"task": {
|
||||
"estimated_times": "geschätzte Zeiten"
|
||||
},
|
||||
"user" : {
|
||||
"actions": "Aktionen",
|
||||
"abort": "abbrechen",
|
||||
"add_login_service": "Login-Service anlegen",
|
||||
"base_url": "Basis-URL",
|
||||
"client_id": "Client-ID",
|
||||
"client_secret": "Client-Geheimnis",
|
||||
"connect_service": "mit Service verbinden",
|
||||
"connected_services": "verbundene Login-Services",
|
||||
"create_new": "Neuen Benutzer anlegen",
|
||||
"CREATE_USERS": "Nutzer anlegen",
|
||||
"data_sent": "Daten übermittelt",
|
||||
"delete": "löschen",
|
||||
"DELETE_USERS": "Nutzer löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"editing": "Nutzer {0} bearbeiten",
|
||||
"edit_password": "Passwort ändern",
|
||||
"edit_service": "Login-Service \"{0}\" bearbeiten",
|
||||
"email": "E-Mail",
|
||||
"failed": "fehlgeschlagen",
|
||||
"foreign_id": "externe Kennung",
|
||||
"go_to_url_to_reset_password": "Um ein neues Passwort zu erhalten, öffnen Sie bitte den folgenden Link: {url}",
|
||||
"id": "Id",
|
||||
"impersonate": "zu Nutzer wechseln",
|
||||
"IMPERSONATE": "Nutzer wechseln",
|
||||
"language": "Sprache",
|
||||
"list": "Benutzer-Liste",
|
||||
"LIST_USERS": "Nutzer auflisten",
|
||||
"loading_data": "Daten werden geladen…",
|
||||
"login": "Login",
|
||||
"login_services": "Login-Services",
|
||||
"MANAGE_LOGIN_SERVICES": "Login-Services verwalten",
|
||||
"mismatch": "ungleich",
|
||||
"must_not_be_empty": "darf nicht leer sein",
|
||||
"name": "Name",
|
||||
"new_password": "neues Passwort",
|
||||
"old_password": "altes Passwort",
|
||||
"password": "Passwort",
|
||||
"permissions": "Berechtigungen",
|
||||
"processing_code": "Code wird verarbeitet…",
|
||||
"repeat_new_password": "Wiederholung",
|
||||
"saved": "gespeichert",
|
||||
"save_service": "Service speichern",
|
||||
"save_user": "Nutzer speichern",
|
||||
"sent_email": "Email gesendet",
|
||||
"service": "Service",
|
||||
"settings" : "Eisntellungen",
|
||||
"theme": "Design",
|
||||
"unlink": "Trennen",
|
||||
"update": "aktualisieren",
|
||||
"UPDATE_USERS" : "Nutzer aktualisieren",
|
||||
"user_module" : "Umbrella User-Verwaltung",
|
||||
"your_password_reset_token" : "Ihr Token zum Erstellen eines neuen Passworts",
|
||||
"your_profile": "dein Profil"
|
||||
}
|
||||
"stock": "Inventar",
|
||||
|
||||
"task": "Aufgaben",
|
||||
"tax_id": "Steuernummer",
|
||||
"tax_rate": "Steuersatz",
|
||||
"theme": "Design",
|
||||
"timetrack": "Zeiterfassung",
|
||||
"title_or_desc": "Titel/Beschreibung",
|
||||
"tutorial": "Tutorial",
|
||||
"type": "Dokumententyp",
|
||||
"type_confirmation": "Bestätigung",
|
||||
"type_invoice": "Rechnung",
|
||||
"type_offer": "Angebot",
|
||||
"type_reminder": "Erinnerung",
|
||||
|
||||
"unit": "Einheit",
|
||||
"unit_price": "Preis/Einheit",
|
||||
"unlink": "Trennen",
|
||||
"update": "aktualisieren",
|
||||
"UPDATE_USERS" : "Nutzer aktualisieren",
|
||||
"user_module" : "Umbrella User-Verwaltung",
|
||||
|
||||
"user": "Benutzer",
|
||||
"user_list": "Benutzer-Liste",
|
||||
"users": "Benutzer",
|
||||
|
||||
"welcome" : "Willkommen, {0}",
|
||||
"wiki": "Wiki",
|
||||
|
||||
"your_password_reset_token" : "Ihr Token zum Erstellen eines neuen Passworts",
|
||||
"your_profile": "dein Profil"
|
||||
}
|
||||
Reference in New Issue
Block a user