Migrate Addresses interface patterns

This commit is contained in:
2026-08-03 13:40:34 +02:00
parent 2c421022d4
commit f9a7185ce3
7 changed files with 384 additions and 35 deletions
@@ -2,10 +2,12 @@ import { Download, Edit3, GitMerge, History, Link2, Network, Plus, RefreshCw, Ro
import { useCallback, useEffect, useMemo, useState, type DragEvent as ReactDragEvent, type FormEvent } from "react";
import {
ApiError,
ActionBlockerHint,
Button,
ConfirmDialog,
DataGridPaginationBar,
Dialog,
DocumentationHelpLink,
DismissibleAlert,
ExplorerTree,
fetchAuthGroups,
@@ -20,6 +22,8 @@ import {
StatusBadge,
ToggleSwitch,
hasScope,
useUnsavedChanges,
useUnsavedDraftGuard,
type ApiSettings,
type AuthInfo,
type AuthUpdate,
@@ -102,6 +106,12 @@ import {
type ContactPointQualityDecision,
type ContactPointQualityState
} from "../../api/addresses";
import {
ADDRESS_FIELDS_DOCUMENTATION,
ADDRESS_GOVERNANCE_DOCUMENTATION,
ADDRESSES_DOCUMENTATION,
ADDRESSES_I18N
} from "./interfacePatterns";
type Props = {
settings: ApiSettings;
@@ -905,6 +915,10 @@ function disabledReason(...conditions: Array<[boolean, string]>): string {
return conditions.find(([applies]) => applies)?.[1] ?? "";
}
function formKey(value: unknown): string {
return JSON.stringify(value);
}
export default function AddressBookPage({ settings, auth, onAuthChange }: Props) {
const [books, setBooks] = useState<AddressBook[]>([]);
const [addressLists, setAddressLists] = useState<AddressList[]>([]);
@@ -926,10 +940,13 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
const [notice, setNotice] = useState("");
const [bookDialog, setBookDialog] = useState<BookDialogState | null>(null);
const [bookForm, setBookForm] = useState<BookFormState>(EMPTY_BOOK_FORM);
const [bookFormBaseline, setBookFormBaseline] = useState("");
const [listDialog, setListDialog] = useState<ListDialogState | null>(null);
const [listForm, setListForm] = useState<ListFormState>(EMPTY_LIST_FORM);
const [listFormBaseline, setListFormBaseline] = useState("");
const [contactDialog, setContactDialog] = useState<ContactDialogState | null>(null);
const [contactForm, setContactForm] = useState<ContactFormState>(EMPTY_CONTACT_FORM);
const [contactFormBaseline, setContactFormBaseline] = useState("");
const [governanceContact, setGovernanceContact] = useState<Contact | null>(null);
const [channelRules, setChannelRules] = useState<ContactChannelRule[]>([]);
const [channelRuleForm, setChannelRuleForm] = useState<ChannelRuleFormState>(EMPTY_CHANNEL_RULE_FORM);
@@ -977,6 +994,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
const [conflictDialog, setConflictDialog] = useState<AddressSyncConflict | null>(null);
const [conflictMergeChoices, setConflictMergeChoices] = useState<Record<string, ConflictMergeChoice>>({});
const [confirmState, setConfirmState] = useState<ConfirmState>(null);
const { requestDiscard } = useUnsavedChanges();
const canWriteBooks = hasScope(auth, "addresses:address_book:write");
const canDeleteBooks = hasScope(auth, "addresses:address_book:delete");
@@ -989,6 +1007,33 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
const canWriteGovernance = hasScope(auth, "addresses:governance:write");
const canReadSync = hasScope(auth, "addresses:sync:read");
const canWriteSync = hasScope(auth, "addresses:sync:write");
const bookDraftDirty = Boolean(bookDialog && formKey(bookForm) !== bookFormBaseline);
const listDraftDirty = Boolean(listDialog && formKey(listForm) !== listFormBaseline);
const contactDraftDirty = Boolean(contactDialog && formKey(contactForm) !== contactFormBaseline);
useUnsavedDraftGuard({
dirty: bookDraftDirty,
onSave: () => submitBook(),
onDiscard: () => setBookDialog(null),
title: "i18n:govoplan-addresses.unsaved_book_title",
message: "i18n:govoplan-addresses.unsaved_message"
});
useUnsavedDraftGuard({
dirty: listDraftDirty,
onSave: () => submitList(),
onDiscard: () => setListDialog(null),
title: "i18n:govoplan-addresses.unsaved_list_title",
message: "i18n:govoplan-addresses.unsaved_message"
});
useUnsavedDraftGuard({
dirty: contactDraftDirty,
onSave: () => submitContact(),
onDiscard: () => setContactDialog(null),
title: "i18n:govoplan-addresses.unsaved_contact_title",
message: "i18n:govoplan-addresses.unsaved_message"
});
useEffect(() => {
if ((!cardDavOpen && !ldapOpen) || !canWriteSync) {
@@ -1478,38 +1523,67 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
}, [selectedContactId, visibleContacts]);
function openCreateBookDialog() {
setBookForm({
const next = {
...EMPTY_BOOK_FORM,
group_id: auth.groups[0]?.id ?? ""
});
};
setBookForm(next);
setBookFormBaseline(formKey(next));
setBookDialog({ mode: "create" });
}
function openEditBookDialog(book: AddressBook) {
setBookForm(bookFormFromBook(book));
const next = bookFormFromBook(book);
setBookForm(next);
setBookFormBaseline(formKey(next));
setBookDialog({ mode: "edit", book });
}
function openCreateListDialog() {
setListForm(EMPTY_LIST_FORM);
setListFormBaseline(formKey(EMPTY_LIST_FORM));
setListDialog({ mode: "create" });
}
function openEditListDialog(list: AddressList) {
setListForm(listFormFromList(list));
const next = listFormFromList(list);
setListForm(next);
setListFormBaseline(formKey(next));
setListDialog({ mode: "edit", list });
}
function openCreateContactDialog() {
setContactForm(emptyContactForm());
const next = emptyContactForm();
setContactForm(next);
setContactFormBaseline(formKey(next));
setContactDialog({ mode: "create" });
}
function openEditContactDialog(contact: Contact) {
setContactForm(contactFormFromContact(contact));
const next = contactFormFromContact(contact);
setContactForm(next);
setContactFormBaseline(formKey(next));
setContactDialog({ mode: "edit", contact });
}
function closeBookDialog() {
if (saving) return;
if (bookDraftDirty) requestDiscard(() => setBookDialog(null));
else setBookDialog(null);
}
function closeListDialog() {
if (saving) return;
if (listDraftDirty) requestDiscard(() => setListDialog(null));
else setListDialog(null);
}
function closeContactDialog() {
if (saving) return;
if (contactDraftDirty) requestDiscard(() => setContactDialog(null));
else setContactDialog(null);
}
async function openGovernanceDialog(contact: Contact) {
setGovernanceContact(contact);
setChannelRuleForm(EMPTY_CHANNEL_RULE_FORM);
@@ -1820,8 +1894,9 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
});
}
async function submitBook(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
async function submitBook(event?: FormEvent<HTMLFormElement>): Promise<boolean> {
event?.preventDefault();
if (bookSaveReason) return false;
setSaving(true);
setError("");
setNotice("");
@@ -1838,16 +1913,18 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
}
setBookDialog(null);
await refreshAll();
return true;
} catch (err) {
setError(errorMessage(err));
return false;
} finally {
setSaving(false);
}
}
async function submitList(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!selectedBook) return;
async function submitList(event?: FormEvent<HTMLFormElement>): Promise<boolean> {
event?.preventDefault();
if (!selectedBook || listSaveReason) return false;
setSaving(true);
setError("");
setNotice("");
@@ -1874,16 +1951,18 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
});
await refreshBooks();
await refreshListEntries(savedList.id);
return true;
} catch (err) {
setError(errorMessage(err));
return false;
} finally {
setSaving(false);
}
}
async function submitContact(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!selectedBook) return;
async function submitContact(event?: FormEvent<HTMLFormElement>): Promise<boolean> {
event?.preventDefault();
if (!selectedBook || contactSaveReason) return false;
setSaving(true);
setError("");
setNotice("");
@@ -1927,8 +2006,10 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
setSelectedContactId(savedContact.id);
await refreshBooks();
await refreshContacts(selectedBook.id, query);
return true;
} catch (err) {
setError(errorMessage(err));
return false;
} finally {
setSaving(false);
}
@@ -2899,6 +2980,18 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<div className="workspace-data-page module-entry-page address-book-page address-book-fullscreen">
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
{notice && !error && <DismissibleAlert tone="success" resetKey={notice}>{notice}</DismissibleAlert>}
{!canWriteBooks && !canWriteLists && !canWriteContacts && <ActionBlockerHint
tone="info"
reason={{
summary: "Addresses are read-only",
details: ADDRESSES_I18N.permissionDetails,
requiredAction: ADDRESSES_I18N.permissionAction,
actor: ADDRESSES_I18N.permissionActor,
target: ADDRESSES_I18N.permissionDestination
}}
labels={{ requiredAction: ADDRESSES_I18N.requiredAction, actor: ADDRESSES_I18N.actor, target: ADDRESSES_I18N.destination }}
documentation={ADDRESSES_DOCUMENTATION}
/>}
<LoadingFrame loading={loading} label="Loading address books..." className="address-workspace-frame">
<div className="address-book-workspace">
@@ -2909,6 +3002,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<p>{books.length} book{books.length === 1 ? "" : "s"}</p>
</div>
<div className="button-row compact-actions address-icon-actions">
<DocumentationHelpLink reference={ADDRESSES_DOCUMENTATION} />
{renderSelectedBookActions()}
</div>
</header>
@@ -3008,18 +3102,18 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<Dialog
open={Boolean(bookDialog)}
title={bookDialog?.mode === "edit" ? "Edit address book" : "Add address book"}
onClose={() => setBookDialog(null)}
onClose={closeBookDialog}
closeDisabled={saving}
footerClassName="button-row compact-actions"
footer={
<>
<Button type="button" onClick={() => setBookDialog(null)} disabledReason={dialogCancelReason}>Cancel</Button>
<Button type="button" onClick={closeBookDialog} disabledReason={dialogCancelReason}>Cancel</Button>
<Button type="submit" form="address-book-form" variant="primary" disabledReason={bookSaveReason}><Save size={16} /> Save</Button>
</>
}>
<form id="address-book-form" className="address-dialog-form" onSubmit={(event) => void submitBook(event)}>
<div className="form-grid two">
<FormField label="Scope">
<FormField label="Scope" documentation={ADDRESS_FIELDS_DOCUMENTATION}>
<select value={bookForm.scope_type} disabled={bookDialog?.mode === "edit"} onChange={(event) => setBookForm((current) => ({ ...current, scope_type: event.target.value as AddressBookScope }))}>
<option value="user">Personal</option>
<option value="group" disabled={auth.groups.length === 0}>Group</option>
@@ -3028,7 +3122,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
</select>
</FormField>
{bookForm.scope_type === "group" &&
<FormField label="Group">
<FormField label="Group" documentation={ADDRESS_FIELDS_DOCUMENTATION}>
<select value={bookForm.group_id} disabled={bookDialog?.mode === "edit"} onChange={(event) => setBookForm((current) => ({ ...current, group_id: event.target.value }))}>
{auth.groups.length === 0 && <option value="">No groups available</option>}
{auth.groups.map((group) => <option key={group.id} value={group.id}>{group.name}</option>)}
@@ -3036,7 +3130,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
</FormField>
}
</div>
<FormField label="Name"><input value={bookForm.name} onChange={(event) => setBookForm((current) => ({ ...current, name: event.target.value }))} autoFocus /></FormField>
<FormField label="Name" documentation={ADDRESS_FIELDS_DOCUMENTATION}><input value={bookForm.name} onChange={(event) => setBookForm((current) => ({ ...current, name: event.target.value }))} autoFocus /></FormField>
<FormField label="Description"><textarea value={bookForm.description} onChange={(event) => setBookForm((current) => ({ ...current, description: event.target.value }))} rows={3} /></FormField>
</form>
</Dialog>
@@ -3044,21 +3138,21 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<Dialog
open={Boolean(listDialog)}
title={listDialog?.mode === "edit" ? "Edit address list" : "Add address list"}
onClose={() => setListDialog(null)}
onClose={closeListDialog}
closeDisabled={saving}
footerClassName="button-row compact-actions"
footer={
<>
<Button type="button" onClick={() => setListDialog(null)} disabledReason={dialogCancelReason}>Cancel</Button>
<Button type="button" onClick={closeListDialog} disabledReason={dialogCancelReason}>Cancel</Button>
<Button type="submit" form="address-list-form" variant="primary" disabledReason={listSaveReason}><Save size={16} /> Save</Button>
</>
}>
<form id="address-list-form" className="address-dialog-form" onSubmit={(event) => void submitList(event)}>
<p className="muted">Lists group contacts inside the selected address book.</p>
<FormField label="Address book">
<FormField label="Address book" documentation={ADDRESS_FIELDS_DOCUMENTATION}>
<input value={selectedBook?.name ?? ""} disabled readOnly />
</FormField>
<FormField label="Name">
<FormField label="Name" documentation={ADDRESS_FIELDS_DOCUMENTATION}>
<input value={listForm.name} onChange={(event) => setListForm((current) => ({ ...current, name: event.target.value }))} autoFocus />
</FormField>
<FormField label="Description">
@@ -3070,24 +3164,24 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<Dialog
open={Boolean(contactDialog)}
title={contactDialog?.mode === "edit" ? "Edit contact" : "Add contact"}
onClose={() => setContactDialog(null)}
onClose={closeContactDialog}
closeDisabled={saving}
className="address-contact-dialog"
footerClassName="button-row compact-actions"
footer={
<>
<Button type="button" onClick={() => setContactDialog(null)} disabledReason={dialogCancelReason}>Cancel</Button>
<Button type="button" onClick={closeContactDialog} disabledReason={dialogCancelReason}>Cancel</Button>
<Button type="submit" form="address-contact-form" variant="primary" disabledReason={contactSaveReason}><Save size={16} /> Save</Button>
</>
}>
<form id="address-contact-form" className="address-dialog-form" onSubmit={(event) => void submitContact(event)}>
<div className="form-grid three">
<FormField label="Display name"><input value={contactForm.display_name} onChange={(event) => setContactForm((current) => ({ ...current, display_name: event.target.value }))} autoFocus /></FormField>
<FormField label="Display name" documentation={ADDRESS_FIELDS_DOCUMENTATION}><input value={contactForm.display_name} onChange={(event) => setContactForm((current) => ({ ...current, display_name: event.target.value }))} autoFocus /></FormField>
<FormField label="Given name"><input value={contactForm.given_name} onChange={(event) => setContactForm((current) => ({ ...current, given_name: event.target.value }))} /></FormField>
<FormField label="Family name"><input value={contactForm.family_name} onChange={(event) => setContactForm((current) => ({ ...current, family_name: event.target.value }))} /></FormField>
</div>
<div className="form-grid two">
<FormField label="Organization"><input value={contactForm.organization} onChange={(event) => setContactForm((current) => ({ ...current, organization: event.target.value }))} /></FormField>
<FormField label="Organization" documentation={ADDRESS_FIELDS_DOCUMENTATION}><input value={contactForm.organization} onChange={(event) => setContactForm((current) => ({ ...current, organization: event.target.value }))} /></FormField>
<FormField label="Role title"><input value={contactForm.role_title} onChange={(event) => setContactForm((current) => ({ ...current, role_title: event.target.value }))} /></FormField>
</div>
<section className="address-form-section">
@@ -3214,7 +3308,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<Button type="submit" variant="primary" disabledReason={channelRuleSaveReason}><Plus size={15} /> Add</Button>
</div>
<div className="form-grid two">
<FormField label="Channel">
<FormField label="Channel" documentation={ADDRESS_GOVERNANCE_DOCUMENTATION}>
<select
value={channelRuleForm.channel}
onChange={(event) => setChannelRuleForm((current) => ({
@@ -3228,7 +3322,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<option value="portal">Portal</option>
</select>
</FormField>
<FormField label="Contact point">
<FormField label="Contact point" documentation={ADDRESS_GOVERNANCE_DOCUMENTATION}>
<select
value={channelRuleForm.contact_point_id}
disabled={governancePointOptions.length === 0}
@@ -3239,7 +3333,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
</FormField>
</div>
<div className="form-grid two">
<FormField label="Decision">
<FormField label="Decision" documentation={ADDRESS_GOVERNANCE_DOCUMENTATION}>
<select value={channelRuleForm.decision} onChange={(event) => setChannelRuleForm((current) => ({ ...current, decision: event.target.value as AddressChannelDecision }))}>
<option value="allowed">Allowed</option>
<option value="opted_in">Opted in</option>
@@ -3251,7 +3345,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<option value="temporarily_unavailable">Temporarily unavailable</option>
</select>
</FormField>
<FormField label="Communication purpose">
<FormField label="Communication purpose" documentation={ADDRESS_GOVERNANCE_DOCUMENTATION}>
<input value={channelRuleForm.purpose} placeholder="All purposes" onChange={(event) => setChannelRuleForm((current) => ({ ...current, purpose: event.target.value }))} />
</FormField>
</div>
@@ -3265,7 +3359,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
<FormField label="Effective until"><input type="datetime-local" value={channelRuleForm.effective_until} onChange={(event) => setChannelRuleForm((current) => ({ ...current, effective_until: event.target.value }))} /></FormField>
<FormField label="Preference rank"><input type="number" min="0" max="10000" value={channelRuleForm.preference_rank} onChange={(event) => setChannelRuleForm((current) => ({ ...current, preference_rank: event.target.value }))} /></FormField>
</div>
<FormField label="Reason"><textarea rows={2} value={channelRuleForm.reason} onChange={(event) => setChannelRuleForm((current) => ({ ...current, reason: event.target.value }))} /></FormField>
<FormField label="Reason" documentation={ADDRESS_GOVERNANCE_DOCUMENTATION}><textarea rows={2} value={channelRuleForm.reason} onChange={(event) => setChannelRuleForm((current) => ({ ...current, reason: event.target.value }))} /></FormField>
</form>
}
</div>
@@ -0,0 +1,26 @@
import type { DocumentationHelpReference } from "@govoplan/core-webui";
export const ADDRESSES_DOCUMENTATION = {
topicId: "addresses.boundary",
documentationType: "user"
} satisfies DocumentationHelpReference;
export const ADDRESS_FIELDS_DOCUMENTATION = {
topicId: "addresses.reference.fields-and-consequences",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const ADDRESS_GOVERNANCE_DOCUMENTATION = {
topicId: "addresses.contact-point-resolution",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const ADDRESSES_I18N = {
requiredAction: "i18n:govoplan-addresses.required_action",
actor: "i18n:govoplan-addresses.actor",
destination: "i18n:govoplan-addresses.destination",
permissionDetails: "i18n:govoplan-addresses.permission_details",
permissionAction: "i18n:govoplan-addresses.permission_action",
permissionActor: "i18n:govoplan-addresses.permission_actor",
permissionDestination: "i18n:govoplan-addresses.permission_destination"
} as const;
+82 -2
View File
@@ -45,7 +45,47 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-addresses.tenant_directory_and_approved_shared_contacts.fa671f1b": "Tenant directory and approved shared contacts.",
"i18n:govoplan-addresses.tenant_wide_contacts_functional_mailboxes_and_ap.c437a8b9": "Tenant-wide contacts, functional mailboxes, and approved shared entries.",
"i18n:govoplan-addresses.use_contacts_in_to_cc_bcc_sender_and_reply_to_fi.79f3ea6a": "Use contacts in To, Cc, Bcc, sender, and reply-to fields.",
"i18n:govoplan-addresses.used_recently.af75cea7": "Used recently"
"i18n:govoplan-addresses.used_recently.af75cea7": "Used recently",
"i18n:govoplan-addresses.sources": "Address sources",
"i18n:govoplan-addresses.contact_detail": "Contact detail",
"i18n:govoplan-addresses.communication_governance": "Communication governance",
"i18n:govoplan-addresses.required_action": "Required action",
"i18n:govoplan-addresses.actor": "Responsible actor",
"i18n:govoplan-addresses.destination": "Where to continue",
"i18n:govoplan-addresses.permission_details": "Your account can inspect Addresses but cannot create or change address books, lists, or contacts.",
"i18n:govoplan-addresses.permission_action": "Ask for the address-book, list, or contact permission needed for the intended task.",
"i18n:govoplan-addresses.permission_actor": "A tenant administrator or owner of the address-book scope",
"i18n:govoplan-addresses.permission_destination": "Access administration for the current tenant or group",
"i18n:govoplan-addresses.unsaved_book_title": "Unsaved address book",
"i18n:govoplan-addresses.unsaved_list_title": "Unsaved address list",
"i18n:govoplan-addresses.unsaved_contact_title": "Unsaved contact",
"i18n:govoplan-addresses.unsaved_message": "Save or discard this draft before leaving the editor.",
"Addresses are read-only": "Addresses are read-only",
"Address books": "Address books",
"Address sources": "Address sources",
"Contact detail": "Contact detail",
"Show archived": "Show archived",
"Search contacts": "Search contacts",
"No address books found.": "No address books found.",
"No contact selected": "No contact selected",
"Add address book": "Add address book",
"Edit address book": "Edit address book",
"Add address list": "Add address list",
"Edit address list": "Edit address list",
"Add contact": "Add contact",
"Edit contact": "Edit contact",
"Communication governance": "Communication governance",
"Display name": "Display name",
"Given name": "Given name",
"Family name": "Family name",
"Organization": "Organization",
"Role title": "Role title",
"Email addresses": "Email addresses",
"Phone numbers": "Phone numbers",
"Postal addresses": "Postal addresses",
"Primary": "Primary",
"Description": "Description",
"Note": "Note"
},
de: {
"i18n:govoplan-addresses.add_contact.6da0b4b8": "Kontakt hinzufügen",
@@ -91,6 +131,46 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-addresses.tenant_directory_and_approved_shared_contacts.fa671f1b": "Mandantenverzeichnis und freigegebene geteilte Kontakte.",
"i18n:govoplan-addresses.tenant_wide_contacts_functional_mailboxes_and_ap.c437a8b9": "Mandantenweite Kontakte, Funktionspostfächer und freigegebene Einträge.",
"i18n:govoplan-addresses.use_contacts_in_to_cc_bcc_sender_and_reply_to_fi.79f3ea6a": "Kontakte in An-, Cc-, Bcc-, Absender- und Antwortfeldern verwenden.",
"i18n:govoplan-addresses.used_recently.af75cea7": "Kürzlich verwendet"
"i18n:govoplan-addresses.used_recently.af75cea7": "Kürzlich verwendet",
"i18n:govoplan-addresses.sources": "Adressquellen",
"i18n:govoplan-addresses.contact_detail": "Kontaktdetails",
"i18n:govoplan-addresses.communication_governance": "Kommunikationssteuerung",
"i18n:govoplan-addresses.required_action": "Erforderliche Aktion",
"i18n:govoplan-addresses.actor": "Verantwortliche Stelle",
"i18n:govoplan-addresses.destination": "Fortsetzung",
"i18n:govoplan-addresses.permission_details": "Ihr Konto darf Adressen einsehen, aber keine Adressbücher, Listen oder Kontakte erstellen oder ändern.",
"i18n:govoplan-addresses.permission_action": "Fordern Sie die für die Aufgabe erforderliche Adressbuch-, Listen- oder Kontaktberechtigung an.",
"i18n:govoplan-addresses.permission_actor": "Mandantenadministration oder Eigentümer des Adressbuchbereichs",
"i18n:govoplan-addresses.permission_destination": "Zugriffsverwaltung des aktuellen Mandanten oder der Gruppe",
"i18n:govoplan-addresses.unsaved_book_title": "Ungespeichertes Adressbuch",
"i18n:govoplan-addresses.unsaved_list_title": "Ungespeicherte Adressliste",
"i18n:govoplan-addresses.unsaved_contact_title": "Ungespeicherter Kontakt",
"i18n:govoplan-addresses.unsaved_message": "Speichern oder verwerfen Sie diesen Entwurf, bevor Sie den Editor verlassen.",
"Addresses are read-only": "Adressen sind schreibgeschützt",
"Address books": "Adressbücher",
"Address sources": "Adressquellen",
"Contact detail": "Kontaktdetails",
"Show archived": "Archivierte anzeigen",
"Search contacts": "Kontakte suchen",
"No address books found.": "Keine Adressbücher gefunden.",
"No contact selected": "Kein Kontakt ausgewählt",
"Add address book": "Adressbuch hinzufügen",
"Edit address book": "Adressbuch bearbeiten",
"Add address list": "Adressliste hinzufügen",
"Edit address list": "Adressliste bearbeiten",
"Add contact": "Kontakt hinzufügen",
"Edit contact": "Kontakt bearbeiten",
"Communication governance": "Kommunikationssteuerung",
"Display name": "Anzeigename",
"Given name": "Vorname",
"Family name": "Nachname",
"Organization": "Organisation",
"Role title": "Funktionsbezeichnung",
"Email addresses": "E-Mail-Adressen",
"Phone numbers": "Telefonnummern",
"Postal addresses": "Postanschriften",
"Primary": "Primär",
"Description": "Beschreibung",
"Note": "Notiz"
}
};
+9 -1
View File
@@ -17,8 +17,16 @@ export const addressesModule: PlatformWebModule = {
dependencies: [],
optionalDependencies: ["campaigns", "mail", "forms", "reporting", "portal", "postbox"],
translations,
viewSurfaces: [
{ id: "addresses.page", moduleId: "addresses", kind: "route", label: "i18n:govoplan-addresses.address_book.f6327f59", order: 80 },
{ id: "addresses.sources", moduleId: "addresses", kind: "section", label: "i18n:govoplan-addresses.sources", parentId: "addresses.page", order: 10 },
{ id: "addresses.contacts", moduleId: "addresses", kind: "section", label: "i18n:govoplan-addresses.contacts.b0dd615c", parentId: "addresses.page", order: 20 },
{ id: "addresses.detail", moduleId: "addresses", kind: "section", label: "i18n:govoplan-addresses.contact_detail", parentId: "addresses.page", order: 30 },
{ id: "addresses.governance", moduleId: "addresses", kind: "action", label: "i18n:govoplan-addresses.communication_governance", parentId: "addresses.detail", order: 40 },
{ id: "addresses.sync", moduleId: "addresses", kind: "action", label: "i18n:govoplan-addresses.sync.905f6309", parentId: "addresses.sources", order: 50 }
],
navItems: [{ to: "/address-book", label: "i18n:govoplan-addresses.address_book.f6327f59", iconName: "book-user", anyOf: ["addresses:contact:read"], order: 80 }],
routes: [{ path: "/address-book", anyOf: ["addresses:contact:read"], order: 80, render: ({ settings, auth, onAuthChange }) => createElement(AddressBookPage, { settings, auth, onAuthChange }) }]
routes: [{ path: "/address-book", anyOf: ["addresses:contact:read"], order: 80, surfaceId: "addresses.page", render: ({ settings, auth, onAuthChange }) => createElement(AddressBookPage, { settings, auth, onAuthChange }) }]
};
export default addressesModule;