feat(scheduling-webui): manage participant invitations

This commit is contained in:
2026-07-22 04:25:54 +02:00
parent fc356e22c6
commit b328df67a3
4 changed files with 384 additions and 12 deletions

View File

@@ -5,6 +5,8 @@ import {
CalendarCheck,
Check,
Clock,
Copy,
Link2Off,
Pencil,
Plus,
RefreshCw,
@@ -15,6 +17,7 @@ import {
import {
Button,
Card,
ConfirmDialog,
DataGrid,
DataGridRowActions,
DateTimeField,
@@ -33,6 +36,7 @@ import {
StatusBadge,
hasScope,
i18nMessage,
isApiError,
usePlatformLanguage,
usePlatformUiCapability,
useUnsavedChanges,
@@ -54,15 +58,18 @@ import {
decideSchedulingRequest,
evaluateSchedulingFreeBusy,
getSchedulingAvailabilityResponse,
issueSchedulingParticipantInvitation,
listSchedulingNotifications,
listSchedulingRequests,
openSchedulingRequest,
revokeSchedulingParticipantInvitation,
searchSchedulingPeople,
schedulingSummary,
submitSchedulingAvailability,
updateSchedulingRequest,
type SchedulingCandidateSlot,
type SchedulingAvailabilityValue,
type SchedulingInvitationActionResponse,
type SchedulingNotification,
type SchedulingParticipant,
type SchedulingPollOptionResult,
@@ -76,10 +83,13 @@ import {
participantDraftFromResponse,
participantDraftsFromPicker,
participantPayload,
schedulingInvitationActionBlocks,
schedulingPublicInvitationUrl,
schedulingRelevantTimestamp,
schedulingRequestIsOwned,
schedulingSortPhase,
type SchedulingActor,
type SchedulingInvitationActionBlock,
type SchedulingParticipantDraft,
type SchedulingRequestGroups
} from "./schedulingViewModel";
@@ -97,6 +107,10 @@ type SlotDraft = {
metadata?: Record<string, unknown>;
};
type ParticipantDraft = SchedulingParticipantDraft;
type InvitationRevokeTarget = {
requestId: string;
participant: SchedulingParticipant;
};
const I18N = {
actions: "i18n:govoplan-core.actions.c3cd636a",
@@ -118,10 +132,13 @@ const I18N = {
calendarUnavailable: "i18n:govoplan-scheduling.calendar_integration_requires_the_calendar_module_plus_c.f892cb1e",
candidateAvailability: "i18n:govoplan-scheduling.candidate_availability.9541c4b5",
candidateSlots: "i18n:govoplan-scheduling.candidate_slots.c414946b",
cancellationNoticeExpired: "i18n:govoplan-scheduling.the_cancellation_notice_has_expired_a_new_link_cannot_be_issued.9c6ccc7c",
checkFreeBusy: "i18n:govoplan-scheduling.check_free_busy.e9700e00",
chooseAvailability: "i18n:govoplan-scheduling.choose_availability.ac95b8f6",
clipboardUnavailable: "i18n:govoplan-scheduling.the_invitation_link_could_not_be_copied_check_browser_clipboard_permissions_and_try_again.a8b17cbc",
closePoll: "i18n:govoplan-scheduling.close_poll.a6a18916",
closed: "i18n:govoplan-scheduling.closed.88d86b77",
copyInvitationLink: "i18n:govoplan-scheduling.copy_a_fresh_invitation_link_for_value0.e3799c79",
description: "i18n:govoplan-scheduling.description.55f8ebc8",
determined: "i18n:govoplan-scheduling.determined.9f23293d",
decideUnavailable: "i18n:govoplan-scheduling.a_slot_can_be_selected_after_the_request_is_closed.f91ec02d",
@@ -133,7 +150,13 @@ const I18N = {
generalSettings: "i18n:govoplan-scheduling.participation_settings.8dc6f62c",
free: "i18n:govoplan-scheduling.free.75f52718",
holds: "i18n:govoplan-scheduling.create_tentative_holds.51c4744e",
invitationDeliveryUnavailable: "i18n:govoplan-scheduling.automatic_invitation_delivery_is_unavailable_copy_the_link_instead.4e39d0b3",
invitationDeliveryFailed: "i18n:govoplan-scheduling.invitation_delivery_failed_the_link_was_created_but_was_not_delivered.8db0c306",
invitationDeliveryRequested: "i18n:govoplan-scheduling.invitation_delivery_requested.1aaa78ba",
invitationChanged: "i18n:govoplan-scheduling.this_invitation_changed_the_request_was_reloaded_try_again.c7095533",
invitedRequests: "i18n:govoplan-scheduling.scheduling_requests_for_me.1d521aba",
invitationLinkCopied: "i18n:govoplan-scheduling.invitation_link_copied.332973ec",
invitationLinkRevoked: "i18n:govoplan-scheduling.invitation_link_revoked.c7dd20d4",
loading: "i18n:govoplan-scheduling.loading_scheduling_requests.f42be95d",
location: "i18n:govoplan-scheduling.location.d219c681",
allowComments: "i18n:govoplan-scheduling.allow_comments.d63202a6",
@@ -155,6 +178,8 @@ const I18N = {
myRequests: "i18n:govoplan-scheduling.my_scheduling_requests.d28ef235",
name: "i18n:govoplan-scheduling.name.709a2322",
newRequest: "i18n:govoplan-scheduling.new_scheduling_request.2080f675",
noActiveInvitation: "i18n:govoplan-scheduling.no_active_invitation_link_to_revoke.4ad0f0cc",
noDeliveryTarget: "i18n:govoplan-scheduling.this_participant_has_no_deliverable_email_address_or_account.dbe14180",
noNotifications: "i18n:govoplan-scheduling.no_notifications_have_been_created.c8d43ca3",
notificationsUnavailable: "i18n:govoplan-scheduling.notifications_could_not_be_loaded.f0e1b2c3",
noParticipants: "i18n:govoplan-scheduling.no_participants.73a89101",
@@ -165,6 +190,7 @@ const I18N = {
notifyOnAnswersHelp: "i18n:govoplan-scheduling.create_an_organizer_notification_whenever_a_response_is.253ddca8",
open: "i18n:govoplan-scheduling.open.cf9b7706",
openPoll: "i18n:govoplan-scheduling.open_poll.2beac9a7",
participant: "i18n:govoplan-scheduling.participant.554f4235",
participantEmail: "i18n:govoplan-scheduling.participant_email.2cadfd9e",
participantRosterHidden: "i18n:govoplan-scheduling.participant_names_and_statuses_are_hidden_aggregate_counts_r.d811a69a",
participantPrivacy: "i18n:govoplan-scheduling.participant_privacy.108c470f",
@@ -181,7 +207,12 @@ const I18N = {
capacity: "i18n:govoplan-scheduling.capacity.d3c375f8",
past: "i18n:govoplan-scheduling.past.405c12fb",
refresh: "i18n:govoplan-scheduling.refresh_requests.0a3ed7a1",
reloadInvitation: "i18n:govoplan-scheduling.reload_the_request_before_changing_this_invitation.9e685df4",
reminder: "i18n:govoplan-scheduling.send_reminder.cf5eb3bf",
revokeInvitation: "i18n:govoplan-scheduling.revoke_the_invitation_link_for_value0.15a9c9fa",
revokeInvitationConfirm: "i18n:govoplan-scheduling.revoke_the_current_invitation_link_for_value0_it_will_stop_working_immediately.3cdc5817",
revokeInvitationLabel: "i18n:govoplan-scheduling.revoke_invitation_link.87bf89cf",
revokeLink: "i18n:govoplan-scheduling.revoke_link.da371ee1",
requestFailed: "i18n:govoplan-scheduling.request_failed.9fcda32c",
publicPolicyUnavailable: "i18n:govoplan-scheduling.guest_links_are_not_issued_while_the_configured_participation.0794ebf0",
policyLocked: "i18n:govoplan-scheduling.participation_controls_are_locked_after_invitation_links_are_issued.66f5a740",
@@ -199,6 +230,7 @@ const I18N = {
unsavedResponse: "i18n:govoplan-scheduling.save_or_discard_your_unsent_availability_changes_before_leaving.97e10df1",
singleChoice: "i18n:govoplan-scheduling.participants_can_choose_only_one_option.4311f51c",
singleChoiceHelp: "i18n:govoplan-scheduling.each_participant_can_answer_yes_to_at_most_one_candidate.5313a465",
sendInvitation: "i18n:govoplan-scheduling.send_a_fresh_invitation_to_value0.fd8d9dea",
sendResponse: "i18n:govoplan-scheduling.submit_response.a5f0c053",
start: "i18n:govoplan-scheduling.start.952f3754",
statusLabel: "i18n:govoplan-scheduling.status.bae7d5be",
@@ -255,6 +287,8 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
const [summaryUnavailable, setSummaryUnavailable] = useState(false);
const [notificationsUnavailable, setNotificationsUnavailable] = useState(false);
const [detailsLoading, setDetailsLoading] = useState(false);
const [revokeInvitationTarget, setRevokeInvitationTarget] = useState<InvitationRevokeTarget | null>(null);
const [invitationActionClock, setInvitationActionClock] = useState(() => new Date());
const detailLoadSequence = useRef(0);
const canWrite = hasScope(auth, "scheduling:schedule:write");
@@ -356,6 +390,29 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
void loadDetails(selected.id);
}, [editorMode, selected?.id]);
useEffect(() => {
setInvitationActionClock(new Date());
if (selected?.status !== "cancelled" || !selected.cancellation_notice_until) return undefined;
const expiresAt = Date.parse(selected.cancellation_notice_until);
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now()) return undefined;
let timer: number | undefined;
const scheduleExpiryRefresh = () => {
const remaining = expiresAt - Date.now();
if (remaining <= 0) {
setInvitationActionClock(new Date());
return;
}
timer = window.setTimeout(
scheduleExpiryRefresh,
Math.min(remaining + 25, 2_147_483_647)
);
};
scheduleExpiryRefresh();
return () => {
if (timer !== undefined) window.clearTimeout(timer);
};
}, [selected?.cancellation_notice_until, selected?.status]);
useEffect(() => {
if (!selected?.id || !selectedParticipant || !canRespond || editorMode || selected.status !== "collecting") {
setAvailabilityLoading(false);
@@ -645,6 +702,91 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
}
}
async function runParticipantInvitationAction(
requestId: string,
participant: SchedulingParticipant,
action: "copy" | "send" | "revoke"
): Promise<boolean> {
const request = requests.find((item) => item.id === requestId);
if (!request || !(canAdminister || (canWrite && schedulingRequestIsOwned(request, actor)))) return false;
if (!participant.revision) {
setError(I18N.reloadInvitation);
return false;
}
let succeeded = false;
let failureMessage = "";
let completionMessage = "";
setSaving(true);
setError("");
setSuccess("");
try {
let copied = true;
let result: SchedulingInvitationActionResponse;
if (action === "copy") {
const copyResult = await copySchedulingInvitationLink(
issueSchedulingParticipantInvitation(
settings,
requestId,
participant.id,
participant.revision,
action
)
);
result = copyResult.response;
copied = copyResult.copied;
} else if (action === "send") {
result = await issueSchedulingParticipantInvitation(
settings,
requestId,
participant.id,
participant.revision,
action
);
} else {
result = await revokeSchedulingParticipantInvitation(
settings,
requestId,
participant.id,
participant.revision
);
}
if (action === "copy") {
if (!copied) {
throw new Error(translateText(I18N.clipboardUnavailable));
}
completionMessage = I18N.invitationLinkCopied;
} else if (action === "send") {
if (["failed", "skipped"].includes(result.status)) {
throw new Error(translateText(I18N.invitationDeliveryFailed));
}
completionMessage = I18N.invitationDeliveryRequested;
} else {
completionMessage = I18N.invitationLinkRevoked;
}
succeeded = true;
} catch (err) {
if (isApiError(err, 409)) {
failureMessage = I18N.invitationChanged;
} else {
failureMessage = errorMessage(err, translateText(I18N.requestFailed));
}
} finally {
await loadRequests(requestId);
await loadDetails(requestId);
setSaving(false);
if (failureMessage) setError(failureMessage);
if (completionMessage) setSuccess(completionMessage);
}
return succeeded;
}
async function confirmRevokeInvitation() {
if (!revokeInvitationTarget) return;
const target = revokeInvitationTarget;
setRevokeInvitationTarget(null);
await runParticipantInvitationAction(target.requestId, target.participant, "revoke");
}
async function sendAvailability(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
await persistAvailability();
@@ -706,9 +848,9 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
label={I18N.refresh}
icon={<RefreshCw aria-hidden="true" size={16} />}
onClick={() => requestNavigation(() => void loadRequests(selected?.id))}
disabled={loading} />
disabled={loading || saving} />
{canCreateOrWrite ? (
<Button type="button" variant="primary" onClick={beginCreate}>
<Button type="button" variant="primary" onClick={beginCreate} disabled={saving}>
<Plus aria-hidden="true" size={16} /> {I18N.add}
</Button>
) : null}
@@ -720,12 +862,14 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
requests={groups.owned}
selectedId={editorMode ? undefined : selected?.id}
actor={actor}
disabled={saving}
onSelect={selectRequest} />
<RequestGroup
title={I18N.invitedRequests}
requests={groups.invited}
selectedId={editorMode ? undefined : selected?.id}
actor={actor}
disabled={saving}
onSelect={selectRequest} />
{canAdminister && groups.other.length ? (
<RequestGroup
@@ -733,6 +877,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
requests={groups.other}
selectedId={editorMode ? undefined : selected?.id}
actor={actor}
disabled={saving}
onSelect={selectRequest} />
) : null}
</Card>
@@ -1060,7 +1205,18 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
))}
</div>
{selected.effective_participant_visibility === "names_and_statuses" ? (
<ParticipantsGrid participants={selected.participants} />
<ParticipantsGrid
request={selected}
participants={selected.participants}
canManage={canManageSelected}
saving={saving}
now={invitationActionClock}
onCopy={(participant) => void runParticipantInvitationAction(selected.id, participant, "copy")}
onSend={(participant) => void runParticipantInvitationAction(selected.id, participant, "send")}
onRevoke={(participant) => setRevokeInvitationTarget({
requestId: selected.id,
participant
})} />
) : <p className="scheduling-capability-note">{I18N.participantRosterHidden}</p>}
</Card>
<Card title={I18N.notifications}>
@@ -1090,6 +1246,17 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
</section>
</div>
</section>
<ConfirmDialog
open={Boolean(revokeInvitationTarget)}
title={I18N.revokeInvitationLabel}
message={i18nMessage(I18N.revokeInvitationConfirm, {
value0: participantDisplayLabel(revokeInvitationTarget?.participant ?? null)
})}
confirmLabel={I18N.revokeLink}
tone="danger"
busy={saving}
onCancel={() => setRevokeInvitationTarget(null)}
onConfirm={() => void confirmRevokeInvitation()} />
</main>
);
@@ -1177,12 +1344,14 @@ function RequestGroup({
requests,
selectedId,
actor,
disabled,
onSelect
}: {
title: string;
requests: SchedulingRequest[];
selectedId?: string;
actor: SchedulingActor;
disabled: boolean;
onSelect: (requestId: string) => void;
}) {
return (
@@ -1196,6 +1365,7 @@ function RequestGroup({
key={request.id}
selected={selectedId === request.id}
className="scheduling-list-item"
disabled={disabled}
onClick={() => onSelect(request.id)}>
<span><strong>{request.title}</strong><small>{formatRelevantDate(request)}</small></span>
<StatusBadge status={request.status} label={requestStatusLabel(request, actor)} />
@@ -1636,7 +1806,25 @@ function CandidateSlotsGrid({
);
}
function ParticipantsGrid({ participants }: { participants: SchedulingParticipant[] }) {
function ParticipantsGrid({
request,
participants,
canManage,
saving,
now,
onCopy,
onSend,
onRevoke
}: {
request: SchedulingRequest;
participants: SchedulingParticipant[];
canManage: boolean;
saving: boolean;
now: Date;
onCopy: (participant: SchedulingParticipant) => void;
onSend: (participant: SchedulingParticipant) => void;
onRevoke: (participant: SchedulingParticipant) => void;
}) {
const columns: DataGridColumn<SchedulingParticipant>[] = [
{ id: "name", header: I18N.name, minWidth: 180, resizable: true, value: (participant) => participant.display_name ?? "" },
{ id: "email", header: I18N.participantEmail, minWidth: 220, resizable: true, value: (participant) => participant.email ?? "" },
@@ -1646,7 +1834,52 @@ function ParticipantsGrid({ participants }: { participants: SchedulingParticipan
width: 140,
value: (participant) => participantStatusLabel(participant.status),
render: (participant) => <StatusBadge status={participant.status} label={participantStatusLabel(participant.status)} />
}
},
...(canManage ? [{
id: "actions",
header: I18N.actions,
width: 132,
sticky: "end",
align: "right",
render: (participant) => {
const label = participantDisplayLabel(participant);
const blocks = schedulingInvitationActionBlocks(request, participant, now);
const copyDisabledReason = invitationActionDisabledReason(blocks.copy, request);
const deliveryDisabledReason = invitationActionDisabledReason(blocks.send, request);
const revokeDisabledReason = invitationActionDisabledReason(blocks.revoke, request);
return (
<TableActionGroup
minimumSlots={3}
actions={[
{
id: "copy-invitation",
label: i18nMessage(I18N.copyInvitationLink, { value0: label }),
icon: <Copy aria-hidden="true" size={16} />,
disabled: saving || Boolean(copyDisabledReason),
disabledReason: copyDisabledReason,
onClick: () => onCopy(participant)
},
{
id: "send-invitation",
label: i18nMessage(I18N.sendInvitation, { value0: label }),
icon: <Send aria-hidden="true" size={16} />,
disabled: saving || Boolean(deliveryDisabledReason),
disabledReason: deliveryDisabledReason,
onClick: () => onSend(participant)
},
{
id: "revoke-invitation",
label: i18nMessage(I18N.revokeInvitation, { value0: label }),
icon: <Link2Off aria-hidden="true" size={16} />,
variant: "danger",
disabled: saving || Boolean(revokeDisabledReason),
disabledReason: revokeDisabledReason,
onClick: () => onRevoke(participant)
}
]} />
);
}
} satisfies DataGridColumn<SchedulingParticipant>] : [])
];
return (
@@ -1660,6 +1893,68 @@ function ParticipantsGrid({ participants }: { participants: SchedulingParticipan
);
}
function invitationActionDisabledReason(
block: SchedulingInvitationActionBlock | null,
request: SchedulingRequest
): string | undefined {
if (block === "participation_policy_unavailable") {
return request.public_participation_policy_enforcement_reason || I18N.publicPolicyUnavailable;
}
if (block === "cancellation_notice_expired") return I18N.cancellationNoticeExpired;
if (block === "delivery_unavailable") return I18N.invitationDeliveryUnavailable;
if (block === "no_delivery_target") return I18N.noDeliveryTarget;
if (block === "no_active_invitation") return I18N.noActiveInvitation;
if (block === "participant_revision_unavailable") return I18N.reloadInvitation;
return undefined;
}
function participantDisplayLabel(participant: SchedulingParticipant | null): string {
return participant?.display_name?.trim() || participant?.email?.trim() || I18N.participant;
}
async function copySchedulingInvitationLink(
responsePromise: Promise<SchedulingInvitationActionResponse>
): Promise<{ response: SchedulingInvitationActionResponse; copied: boolean }> {
if (
typeof navigator !== "undefined" &&
navigator.clipboard?.write &&
typeof ClipboardItem !== "undefined"
) {
try {
const content = responsePromise.then((response) => {
const value = response.action_url
? schedulingPublicInvitationUrl(response.action_url, window.location.origin)
: null;
return new Blob([value ?? ""], { type: "text/plain" });
}).catch(() => new Blob([], { type: "text/plain" }));
await navigator.clipboard.write([new ClipboardItem({ "text/plain": content })]);
const response = await responsePromise;
const copied = Boolean(
response.action_url &&
schedulingPublicInvitationUrl(response.action_url, window.location.origin)
);
return { response, copied };
} catch {
// The request result is reused below; no second invitation is issued.
}
}
const response = await responsePromise;
const value = response.action_url
? schedulingPublicInvitationUrl(response.action_url, window.location.origin)
: null;
if (!value) return { response, copied: false };
try {
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(value);
return { response, copied: true };
}
} catch {
// The caller reports the bounded clipboard failure without exposing the URL.
}
return { response, copied: false };
}
function firstRequest(groups: SchedulingRequestGroups, includeManaged: boolean): SchedulingRequest | null {
return groups.owned[0] ?? groups.invited[0] ?? (includeManaged ? groups.other[0] : null) ?? null;
}

View File

@@ -1,12 +1,30 @@
export const generatedTranslations = {
en: {
"i18n:govoplan-scheduling.access_details.79c06b89": "Access details",
"i18n:govoplan-scheduling.automatic_invitation_delivery_is_unavailable_copy_the_link_instead.4e39d0b3": "Automatic invitation delivery is unavailable; copy the link instead.",
"i18n:govoplan-scheduling.cancellation_notice_available_until.f840d1e6": "Cancellation notice available until",
"i18n:govoplan-scheduling.copy_a_fresh_invitation_link_for_value0.e3799c79": "Copy a fresh invitation link for {value0}",
"i18n:govoplan-scheduling.enter_the_details_supplied_with_the_invitation_for_privacy.81ba419c": "Enter the details supplied with the invitation. For privacy, invalid and expired links use the same response.",
"i18n:govoplan-scheduling.loading_scheduling_request.43c39c1b": "Loading scheduling request…",
"i18n:govoplan-scheduling.invitation_delivery_failed_the_link_was_created_but_was_not_delivered.8db0c306": "Invitation delivery failed. The link was created but was not delivered.",
"i18n:govoplan-scheduling.invitation_delivery_requested.1aaa78ba": "Invitation delivery requested.",
"i18n:govoplan-scheduling.invitation_link_copied.332973ec": "Invitation link copied.",
"i18n:govoplan-scheduling.invitation_link_revoked.c7dd20d4": "Invitation link revoked.",
"i18n:govoplan-scheduling.no_active_invitation_link_to_revoke.4ad0f0cc": "No active invitation link to revoke.",
"i18n:govoplan-scheduling.open_in_scheduling.48df1541": "Open in Scheduling",
"i18n:govoplan-scheduling.open_scheduling_request.31829cce": "Open scheduling request",
"i18n:govoplan-scheduling.response_deadline.7fd9e3aa": "Response deadline",
"i18n:govoplan-scheduling.reload_the_request_before_changing_this_invitation.9e685df4": "Reload the request before changing this invitation.",
"i18n:govoplan-scheduling.participant.554f4235": "Participant",
"i18n:govoplan-scheduling.revoke_invitation_link.87bf89cf": "Revoke invitation link",
"i18n:govoplan-scheduling.revoke_link.da371ee1": "Revoke link",
"i18n:govoplan-scheduling.revoke_the_current_invitation_link_for_value0_it_will_stop_working_immediately.3cdc5817": "Revoke the current invitation link for {value0}? It will stop working immediately.",
"i18n:govoplan-scheduling.revoke_the_invitation_link_for_value0.15a9c9fa": "Revoke the invitation link for {value0}",
"i18n:govoplan-scheduling.send_a_fresh_invitation_to_value0.fd8d9dea": "Send a fresh invitation to {value0}",
"i18n:govoplan-scheduling.the_cancellation_notice_has_expired_a_new_link_cannot_be_issued.9c6ccc7c": "The cancellation notice has expired; a new link cannot be issued.",
"i18n:govoplan-scheduling.the_invitation_link_could_not_be_copied_check_browser_clipboard_permissions_and_try_again.a8b17cbc": "The invitation link could not be copied. Check browser clipboard permissions and try again.",
"i18n:govoplan-scheduling.this_participant_has_no_deliverable_email_address_or_account.dbe14180": "This participant has no deliverable email address or account.",
"i18n:govoplan-scheduling.this_invitation_changed_the_request_was_reloaded_try_again.c7095533": "This invitation changed. The request was reloaded; try again.",
"i18n:govoplan-scheduling.this_scheduling_link_is_invalid_expired_or_the_access_details.8e7aa197": "This scheduling link is invalid, expired, or the access details do not match.",
"i18n:govoplan-scheduling.this_scheduling_request_was_cancelled.1af3c85e": "This scheduling request was cancelled.",
"i18n:govoplan-scheduling.this_scheduling_request_is_no_longer_accepting_responses.c612e78a": "This scheduling request is no longer accepting responses.",
@@ -155,12 +173,30 @@ export const generatedTranslations = {
},
de: {
"i18n:govoplan-scheduling.access_details.79c06b89": "Zugangsdaten",
"i18n:govoplan-scheduling.automatic_invitation_delivery_is_unavailable_copy_the_link_instead.4e39d0b3": "Die automatische Einladungszustellung ist nicht verfügbar; kopieren Sie stattdessen den Link.",
"i18n:govoplan-scheduling.cancellation_notice_available_until.f840d1e6": "Stornierungshinweis verfügbar bis",
"i18n:govoplan-scheduling.copy_a_fresh_invitation_link_for_value0.e3799c79": "Einen neuen Einladungslink für {value0} kopieren",
"i18n:govoplan-scheduling.enter_the_details_supplied_with_the_invitation_for_privacy.81ba419c": "Geben Sie die mit der Einladung übermittelten Daten ein. Aus Datenschutzgründen wird für ungültige und abgelaufene Links dieselbe Meldung angezeigt.",
"i18n:govoplan-scheduling.loading_scheduling_request.43c39c1b": "Terminanfrage wird geladen …",
"i18n:govoplan-scheduling.invitation_delivery_failed_the_link_was_created_but_was_not_delivered.8db0c306": "Die Zustellung der Einladung ist fehlgeschlagen. Der Link wurde erstellt, aber nicht zugestellt.",
"i18n:govoplan-scheduling.invitation_delivery_requested.1aaa78ba": "Die Zustellung der Einladung wurde angefordert.",
"i18n:govoplan-scheduling.invitation_link_copied.332973ec": "Einladungslink kopiert.",
"i18n:govoplan-scheduling.invitation_link_revoked.c7dd20d4": "Einladungslink widerrufen.",
"i18n:govoplan-scheduling.no_active_invitation_link_to_revoke.4ad0f0cc": "Kein aktiver Einladungslink zum Widerrufen vorhanden.",
"i18n:govoplan-scheduling.open_in_scheduling.48df1541": "In der Terminplanung öffnen",
"i18n:govoplan-scheduling.open_scheduling_request.31829cce": "Terminanfrage öffnen",
"i18n:govoplan-scheduling.response_deadline.7fd9e3aa": "Antwortfrist",
"i18n:govoplan-scheduling.reload_the_request_before_changing_this_invitation.9e685df4": "Laden Sie die Anfrage neu, bevor Sie diese Einladung ändern.",
"i18n:govoplan-scheduling.participant.554f4235": "Teilnehmende Person",
"i18n:govoplan-scheduling.revoke_invitation_link.87bf89cf": "Einladungslink widerrufen",
"i18n:govoplan-scheduling.revoke_link.da371ee1": "Link widerrufen",
"i18n:govoplan-scheduling.revoke_the_current_invitation_link_for_value0_it_will_stop_working_immediately.3cdc5817": "Den aktuellen Einladungslink für {value0} widerrufen? Er funktioniert danach sofort nicht mehr.",
"i18n:govoplan-scheduling.revoke_the_invitation_link_for_value0.15a9c9fa": "Den Einladungslink für {value0} widerrufen",
"i18n:govoplan-scheduling.send_a_fresh_invitation_to_value0.fd8d9dea": "Eine neue Einladung an {value0} senden",
"i18n:govoplan-scheduling.the_cancellation_notice_has_expired_a_new_link_cannot_be_issued.9c6ccc7c": "Der Stornierungshinweis ist abgelaufen; ein neuer Link kann nicht ausgestellt werden.",
"i18n:govoplan-scheduling.the_invitation_link_could_not_be_copied_check_browser_clipboard_permissions_and_try_again.a8b17cbc": "Der Einladungslink konnte nicht kopiert werden. Prüfen Sie die Zwischenablageberechtigungen des Browsers und versuchen Sie es erneut.",
"i18n:govoplan-scheduling.this_participant_has_no_deliverable_email_address_or_account.dbe14180": "Für diese teilnehmende Person ist weder eine zustellbare E-Mail-Adresse noch ein Konto hinterlegt.",
"i18n:govoplan-scheduling.this_invitation_changed_the_request_was_reloaded_try_again.c7095533": "Diese Einladung wurde zwischenzeitlich geändert. Die Anfrage wurde neu geladen; versuchen Sie es erneut.",
"i18n:govoplan-scheduling.this_scheduling_link_is_invalid_expired_or_the_access_details.8e7aa197": "Dieser Terminlink ist ungültig oder abgelaufen, oder die Zugangsdaten stimmen nicht überein.",
"i18n:govoplan-scheduling.this_scheduling_request_was_cancelled.1af3c85e": "Diese Terminanfrage wurde storniert.",
"i18n:govoplan-scheduling.this_scheduling_request_is_no_longer_accepting_responses.c612e78a": "Diese Terminanfrage nimmt keine Antworten mehr an.",