working on project creation
This commit is contained in:
@@ -71,6 +71,7 @@ public class Application {
|
|||||||
var webHandler = new WebHandler();
|
var webHandler = new WebHandler();
|
||||||
|
|
||||||
documentApi .bindPath("/api/document") .on(server);
|
documentApi .bindPath("/api/document") .on(server);
|
||||||
|
companyModule .bindPath("/api/company") .on(server);
|
||||||
itemApi .bindPath("/api/items") .on(server);
|
itemApi .bindPath("/api/items") .on(server);
|
||||||
markdownApi .bindPath("/api/markdown") .on(server);
|
markdownApi .bindPath("/api/markdown") .on(server);
|
||||||
messageApi .bindPath("/api/messages") .on(server);
|
messageApi .bindPath("/api/messages") .on(server);
|
||||||
|
|||||||
@@ -3,19 +3,28 @@ package de.srsoftware.umbrella.company;
|
|||||||
|
|
||||||
import static de.srsoftware.umbrella.company.Constants.CONFIG_DATABASE;
|
import static de.srsoftware.umbrella.company.Constants.CONFIG_DATABASE;
|
||||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||||
|
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
import de.srsoftware.configuration.Configuration;
|
||||||
|
import de.srsoftware.tools.Path;
|
||||||
|
import de.srsoftware.tools.SessionToken;
|
||||||
import de.srsoftware.umbrella.company.api.CompanyDb;
|
import de.srsoftware.umbrella.company.api.CompanyDb;
|
||||||
|
import de.srsoftware.umbrella.core.BaseHandler;
|
||||||
import de.srsoftware.umbrella.core.api.CompanyService;
|
import de.srsoftware.umbrella.core.api.CompanyService;
|
||||||
import de.srsoftware.umbrella.core.api.UserService;
|
import de.srsoftware.umbrella.core.api.UserService;
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.Company;
|
import de.srsoftware.umbrella.core.model.Company;
|
||||||
|
import de.srsoftware.umbrella.core.model.Token;
|
||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CompanyModule implements CompanyService {
|
public class CompanyModule extends BaseHandler implements CompanyService {
|
||||||
|
|
||||||
private final UserService users;
|
private final UserService users;
|
||||||
private final CompanyDb companyDb;
|
private final CompanyDb companyDb;
|
||||||
@@ -26,11 +35,36 @@ public class CompanyModule implements CompanyService {
|
|||||||
users = userService;
|
users = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doGet(Path path, HttpExchange ex) throws IOException {
|
||||||
|
addCors(ex);
|
||||||
|
try {
|
||||||
|
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
||||||
|
var user = users.loadUser(token);
|
||||||
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
|
var head = path.pop();
|
||||||
|
return switch (head){
|
||||||
|
case LIST -> getCompanyList(user.get(),ex);
|
||||||
|
case null,
|
||||||
|
default -> super.doGet(path,ex);
|
||||||
|
};
|
||||||
|
} catch (UmbrellaException e) {
|
||||||
|
return send(ex,e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Company get(long companyId) throws UmbrellaException {
|
public Company get(long companyId) throws UmbrellaException {
|
||||||
return companyDb.load(companyId);
|
return companyDb.load(companyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getCompanyList(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException {
|
||||||
|
var list = listCompaniesOf(user).stream().map(Company::toMap);
|
||||||
|
return sendContent(ex,list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<UmbrellaUser> getMembers(long companyId) throws UmbrellaException {
|
public Collection<UmbrellaUser> getMembers(long companyId) throws UmbrellaException {
|
||||||
var members = new HashSet<UmbrellaUser>();
|
var members = new HashSet<UmbrellaUser>();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public record Project(long id, String name, String description, Status status, long companyId, boolean showClosed) implements Mappable {
|
public record Project(long id, String name, String description, Status status, Long companyId, boolean showClosed) implements Mappable {
|
||||||
public enum Status{
|
public enum Status{
|
||||||
Open(10),
|
Open(10),
|
||||||
Started(20),
|
Started(20),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import Messages from "./routes/message/Messages.svelte";
|
import Messages from "./routes/message/Messages.svelte";
|
||||||
import Menu from "./Components/Menu.svelte";
|
import Menu from "./Components/Menu.svelte";
|
||||||
import ProjectList from "./routes/project/List.svelte";
|
import ProjectList from "./routes/project/List.svelte";
|
||||||
|
import ProjectAdd from "./routes/project/Create.svelte";
|
||||||
import ResetPw from "./routes/user/ResetPw.svelte";
|
import ResetPw from "./routes/user/ResetPw.svelte";
|
||||||
import Search from "./routes/search/Search.svelte";
|
import Search from "./routes/search/Search.svelte";
|
||||||
import SendDoc from "./routes/document/Send.svelte";
|
import SendDoc from "./routes/document/Send.svelte";
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
<Route path="/document/:id/view" component={ViewDoc} />
|
<Route path="/document/:id/view" component={ViewDoc} />
|
||||||
<Route path="/message/settings" component={Messages} />
|
<Route path="/message/settings" component={Messages} />
|
||||||
<Route path="/project" component={ProjectList} />
|
<Route path="/project" component={ProjectList} />
|
||||||
|
<Route path="/project/add" component={ProjectAdd} />
|
||||||
<Route path="/search" component={Search} />
|
<Route path="/search" component={Search} />
|
||||||
<Route path="/user" component={User} />
|
<Route path="/user" component={User} />
|
||||||
<Route path="/user/create" component={EditUser} />
|
<Route path="/user/create" component={EditUser} />
|
||||||
|
|||||||
37
frontend/src/Components/CompanySelector.svelte
Normal file
37
frontend/src/Components/CompanySelector.svelte
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script>
|
||||||
|
import {onMount} from 'svelte';
|
||||||
|
import {t} from '../translations.svelte.js';
|
||||||
|
let { caption, onselect = (company) => console.log('selected '+company.name) } = $props();
|
||||||
|
let message = t('loading');
|
||||||
|
let companies = $state(null);
|
||||||
|
let value = 0;
|
||||||
|
|
||||||
|
async function loadCompanies(){
|
||||||
|
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/company/list`;
|
||||||
|
var resp = await fetch(url,{ credentials: 'include'});
|
||||||
|
if (resp.ok){
|
||||||
|
companies = await resp.json();
|
||||||
|
} else {
|
||||||
|
message = await resp.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(){
|
||||||
|
onselect(companies[value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(loadCompanies)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if companies}
|
||||||
|
<select onchange={select} bind:value>
|
||||||
|
<option value={0}>{caption}</option>
|
||||||
|
{#each companies as company,idx}
|
||||||
|
<option value={idx}>{company.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{:else}
|
||||||
|
<span>{message}</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
@@ -2,18 +2,75 @@
|
|||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte.js';
|
||||||
|
|
||||||
|
import CompanySelector from '../../Components/CompanySelector.svelte';
|
||||||
|
import Settings from './Settings.svelte';
|
||||||
|
let showSettings = $state(false);
|
||||||
|
let ready = $derived(!!project.name.trim())
|
||||||
|
let error = $state(null);
|
||||||
|
|
||||||
|
let project = $state({
|
||||||
|
name:'',
|
||||||
|
description:'',
|
||||||
|
settings:{
|
||||||
|
show_closed:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onsubmit(ev){
|
||||||
|
ev.preventDefault();
|
||||||
|
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project`;
|
||||||
|
var resp = await fetch(url,{
|
||||||
|
credentials: 'include',
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(project)
|
||||||
|
});
|
||||||
|
if (resp.ok){
|
||||||
|
router.navigate('/project');
|
||||||
|
} else {
|
||||||
|
error = await resp.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onselect(company){
|
||||||
|
project.company_id = company.id;
|
||||||
|
console.log(project);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label{ display: block }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<span class="error">{error}</span>
|
||||||
|
{/if}
|
||||||
|
<form {onsubmit}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
{t('create_new_project')}
|
{t('create_new_project')}
|
||||||
</legend>
|
</legend>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t('basic_data')}</legend>
|
<legend>{t('basic_data')}</legend>
|
||||||
<span class="warn">Company Selector</span>
|
|
||||||
<label>
|
<label>
|
||||||
<input type="text" />
|
<CompanySelector caption={t('no_company')} {onselect} />
|
||||||
|
{t('company_optional')}
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="text" bind:value={project.name}/>
|
||||||
{t('Name')}
|
{t('Name')}
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<textarea bind:value={project.description}></textarea>
|
||||||
|
{t('description')}
|
||||||
|
</label>
|
||||||
|
{#if !showSettings}
|
||||||
|
<button onclick={() => showSettings = true}>{t('extended_settings')}</button>
|
||||||
|
{/if}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{#if showSettings}
|
||||||
|
<Settings bind:settings={project.settings}/>
|
||||||
|
{/if}
|
||||||
|
<button type="submit" disabled={!ready}>{t('create')}</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
</form>
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte.js';
|
||||||
|
|
||||||
|
const router = useTinyRouter();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
{t('projects')}
|
{t('projects')}
|
||||||
<button>{t('create_new')}</button>
|
<button onclick={() => router.navigate('/project/add')}>{t('create_new')}</button>
|
||||||
</legend>
|
</legend>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
13
frontend/src/routes/project/Settings.svelte
Normal file
13
frontend/src/routes/project/Settings.svelte
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
import {t} from '../../translations.svelte.js';
|
||||||
|
|
||||||
|
let { settings = $bindable() } = $props();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{t('settings')}</legend>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={settings.show_closed} />
|
||||||
|
{t('display_closed_tasks')}
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
@@ -7,4 +7,6 @@ import java.util.Collection;
|
|||||||
|
|
||||||
public interface ProjectDb {
|
public interface ProjectDb {
|
||||||
Collection<Project> list(long companyId, boolean includeClosed) throws UmbrellaException;
|
Collection<Project> list(long companyId, boolean includeClosed) throws UmbrellaException;
|
||||||
|
|
||||||
|
Project save(Project prj);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ package de.srsoftware.umbrella.project;
|
|||||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.Paths.LIST;
|
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
|
||||||
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
|
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
@@ -21,6 +21,8 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|||||||
import de.srsoftware.umbrella.core.model.Project;
|
import de.srsoftware.umbrella.core.model.Project;
|
||||||
import de.srsoftware.umbrella.core.model.Token;
|
import de.srsoftware.umbrella.core.model.Token;
|
||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -54,6 +56,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case LIST -> listItems(ex,user.get());
|
case LIST -> listItems(ex,user.get());
|
||||||
|
case null -> postProject(ex,user.get());
|
||||||
default -> super.doGet(path,ex);
|
default -> super.doGet(path,ex);
|
||||||
};
|
};
|
||||||
} catch (UmbrellaException e){
|
} catch (UmbrellaException e){
|
||||||
@@ -61,10 +64,6 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Project> listProjects(long companyId, boolean includeClosed) throws UmbrellaException {
|
|
||||||
return projectDb.list(companyId, includeClosed).stream().sorted(comparing(Project::name)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean listItems(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
private boolean listItems(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
||||||
var json = json(ex);
|
var json = json(ex);
|
||||||
if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingFieldException(COMPANY_ID);
|
if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingFieldException(COMPANY_ID);
|
||||||
@@ -77,4 +76,26 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
.map(HashMap::new);
|
.map(HashMap::new);
|
||||||
return sendContent(ex,items);
|
return sendContent(ex,items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<Project> listProjects(long companyId, boolean includeClosed) throws UmbrellaException {
|
||||||
|
return projectDb.list(companyId, includeClosed).stream().sorted(comparing(Project::name)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean postProject(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
||||||
|
var json = json(ex);
|
||||||
|
if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingFieldException(NAME);
|
||||||
|
var description = json.has(DESCRIPTION) && json.get(DESCRIPTION) instanceof String d ? d : null;
|
||||||
|
Long companyId = null;
|
||||||
|
if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number number){
|
||||||
|
if (!companies.membership(number.longValue(), user.id())) throw forbidden("You are not a member of company {0}!",number);
|
||||||
|
companyId = number.longValue();
|
||||||
|
}
|
||||||
|
var showClosed = false;
|
||||||
|
if (json.has(SETTINGS) && json.get(SETTINGS) instanceof JSONObject settingsJson){
|
||||||
|
showClosed = settingsJson.has(SHOW_CLOSED) && settingsJson.get(SHOW_CLOSED) == TRUE;
|
||||||
|
}
|
||||||
|
var prj = new Project(0,name,description,Project.Status.Open,companyId,showClosed);
|
||||||
|
projectDb.save(prj);
|
||||||
|
return notFound(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -138,4 +138,12 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
|
|||||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database");
|
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Project save(Project prj) {
|
||||||
|
try {
|
||||||
|
insertInto(TABLE_PROJECTS);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
"add_position": "hinzufügen",
|
"add_position": "hinzufügen",
|
||||||
"advertisement" : "Umbrella ist ein Produkt von {0}.",
|
"advertisement" : "Umbrella ist ein Produkt von {0}.",
|
||||||
"amount": "Menge",
|
"amount": "Menge",
|
||||||
|
|
||||||
"bank_account": "Bankverbindung",
|
"bank_account": "Bankverbindung",
|
||||||
"base_url": "Basis-URL",
|
"base_url": "Basis-URL",
|
||||||
|
"basic_data": "Basis-Daten",
|
||||||
"bookmark": "Lesezeichen",
|
"bookmark": "Lesezeichen",
|
||||||
"by": "von",
|
"by": "von",
|
||||||
|
|
||||||
"client_id": "Client-ID",
|
"client_id": "Client-ID",
|
||||||
"client_secret": "Client-Geheimnis",
|
"client_secret": "Client-Geheimnis",
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
@@ -17,10 +20,13 @@
|
|||||||
"connected_services": "verbundene Login-Services",
|
"connected_services": "verbundene Login-Services",
|
||||||
"confirm_deletion": "Soll '{pos}' wirklich gelöscht werden?",
|
"confirm_deletion": "Soll '{pos}' wirklich gelöscht werden?",
|
||||||
"company": "Firma",
|
"company": "Firma",
|
||||||
|
"company_optional": "Firma (optional)",
|
||||||
"contact": "Kontakte",
|
"contact": "Kontakte",
|
||||||
"contained_tax": "enthaltene Steuer",
|
"contained_tax": "enthaltene Steuer",
|
||||||
"content": "Inhalt",
|
"content": "Inhalt",
|
||||||
|
"create": "anlegen",
|
||||||
"create_new_document": "neues Dokument",
|
"create_new_document": "neues Dokument",
|
||||||
|
"create_new_project": "neues Projekt anlegen",
|
||||||
"create_new_user": "Neuen Benutzer anlegen",
|
"create_new_user": "Neuen Benutzer anlegen",
|
||||||
"CREATE_USERS": "Nutzer anlegen",
|
"CREATE_USERS": "Nutzer anlegen",
|
||||||
"create_pdf": "PDF erzeugen",
|
"create_pdf": "PDF erzeugen",
|
||||||
@@ -35,10 +41,12 @@
|
|||||||
"DELETE_USERS": "Nutzer löschen",
|
"DELETE_USERS": "Nutzer löschen",
|
||||||
"delivery_date": "Lieferdatum",
|
"delivery_date": "Lieferdatum",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
|
"display_closed_tasks": "abgeschlossene Aufgaben anzeigen",
|
||||||
"document": "Dokumente",
|
"document": "Dokumente",
|
||||||
"documents": "Dokumente",
|
"documents": "Dokumente",
|
||||||
"do_login" : "anmelden",
|
"do_login" : "anmelden",
|
||||||
"do_send" : "versenden",
|
"do_send" : "versenden",
|
||||||
|
|
||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"editing": "Nutzer {0} bearbeiten",
|
"editing": "Nutzer {0} bearbeiten",
|
||||||
"edit_password": "Passwort ändern",
|
"edit_password": "Passwort ändern",
|
||||||
@@ -47,6 +55,7 @@
|
|||||||
"email_or_username": "Email oder Nutzername",
|
"email_or_username": "Email oder Nutzername",
|
||||||
"estimated_time": "geschätzte Zeit",
|
"estimated_time": "geschätzte Zeit",
|
||||||
"estimated_times": "geschätzte Zeiten",
|
"estimated_times": "geschätzte Zeiten",
|
||||||
|
"extended_settings": "erweiterte Einstellungen",
|
||||||
|
|
||||||
"failed": "fehlgeschlagen",
|
"failed": "fehlgeschlagen",
|
||||||
"files": "Dateien",
|
"files": "Dateien",
|
||||||
@@ -93,6 +102,7 @@
|
|||||||
"net_sum": "Netto-Summe",
|
"net_sum": "Netto-Summe",
|
||||||
"new_password": "neues Passwort",
|
"new_password": "neues Passwort",
|
||||||
"new_document_from": "{2} / neues {0}s-Dokument von {1}",
|
"new_document_from": "{2} / neues {0}s-Dokument von {1}",
|
||||||
|
"no_company": "keine Firma",
|
||||||
"notes": "Notizen",
|
"notes": "Notizen",
|
||||||
"number": "Nummer",
|
"number": "Nummer",
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user