Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
129f2a4c73 | ||
|
|
1d41cdbbe2 | ||
|
|
2081bd18c3 | ||
|
|
44e53ce2ce |
@@ -304,3 +304,21 @@ inline help. The only intentional omissions are:
|
|||||||
There are no other authorized Scheduling omissions. Inline help remains
|
There are no other authorized Scheduling omissions. Inline help remains
|
||||||
controlled by the user's shared interface setting; hiding it does not remove
|
controlled by the user's shared interface setting; hiding it does not remove
|
||||||
accessible labels.
|
accessible labels.
|
||||||
|
|
||||||
|
## Git-source WebUI package
|
||||||
|
|
||||||
|
The repository root exposes `@govoplan/scheduling-webui` for Git-tagged release
|
||||||
|
dependencies. It mirrors the owning `webui/package.json` version, public
|
||||||
|
TypeScript/CSS exports and peer requirements, with entry paths under
|
||||||
|
`webui/src`. Consumers provide the shared Core/React peers; the facade runs no
|
||||||
|
development or install scripts. The source archive contains `webui/src`, this
|
||||||
|
README and any repository license file. Run module development checks from `webui/`; Python
|
||||||
|
installation remains governed by `pyproject.toml`.
|
||||||
|
|
||||||
|
Das Repository stellt `@govoplan/scheduling-webui` am Wurzelpfad für versionierte
|
||||||
|
Git-Abhängigkeiten bereit. Version, öffentliche TypeScript-/CSS-Exporte und
|
||||||
|
Peer-Anforderungen entsprechen `webui/package.json`; die Einstiegspfade liegen
|
||||||
|
unter `webui/src`. Gemeinsame Core-/React-Peers stellt die einbindende Anwendung
|
||||||
|
bereit. Die Fassade führt keine Entwicklungs- oder Installationsskripte aus.
|
||||||
|
Entwicklungsprüfungen bleiben in `webui/`, die Python-Installation weiterhin in
|
||||||
|
`pyproject.toml` definiert.
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@govoplan/scheduling-webui",
|
||||||
|
"version": "0.1.22",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "webui/src/index.ts",
|
||||||
|
"module": "webui/src/index.ts",
|
||||||
|
"types": "webui/src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./webui/src/index.ts",
|
||||||
|
"import": "./webui/src/index.ts"
|
||||||
|
},
|
||||||
|
"./styles/scheduling.css": "./webui/src/styles/scheduling.css"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@govoplan/core-webui": "^0.1.45",
|
||||||
|
"lucide-react": "^1.23.0",
|
||||||
|
"react": ">=19.2.7 <20",
|
||||||
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
"react-router": ">=8.3.0 <9",
|
||||||
|
"@vitejs/plugin-react": "^5.2.0",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^7.3.6"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@govoplan/core-webui": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"webui/src",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
}
|
||||||
+2
-2
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-scheduling"
|
name = "govoplan-scheduling"
|
||||||
version = "0.1.20"
|
version = "0.1.22"
|
||||||
description = "GovOPlaN meeting scheduling and Terminfindung module seed."
|
description = "GovOPlaN meeting scheduling and Terminfindung module seed."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.18",
|
"govoplan-core>=0.1.45",
|
||||||
"govoplan-poll>=0.1.18",
|
"govoplan-poll>=0.1.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
__all__ = ["__version__"]
|
__all__ = ["__version__"]
|
||||||
|
|
||||||
__version__ = "0.1.20"
|
__version__ = "0.1.22"
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"""German translations for public structured documentation metadata."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
GERMAN_STRUCTURED_TRANSLATIONS: dict[str, dict[str, Any]] = {'scheduling.find-and-decide-meeting-time': {'outcome': 'Eine aufgezeichnete '
|
||||||
|
'Terminplanungsentscheidung mit begrenzter '
|
||||||
|
'Teilnahme und optionaler '
|
||||||
|
'Kalenderübergabe.',
|
||||||
|
'steps': ['Bereiten Sie Kandidatenzeiten und '
|
||||||
|
'Teilnahmekontrollen vor.',
|
||||||
|
'Erfassen und Überprüfen der Verfügbarkeit.',
|
||||||
|
'Schließen Sie die Umfrage und bestätigen '
|
||||||
|
'Sie die gewählte Zeit.',
|
||||||
|
'Geben Sie die Entscheidung an den '
|
||||||
|
'Kalender, wenn er konfiguriert ist.'],
|
||||||
|
'verification': 'Das Anforderungsdetail zeigt den '
|
||||||
|
'entschiedenen Slot, den '
|
||||||
|
'Lebenszykluszustand, das '
|
||||||
|
'Teilnehmeraggregat und jede '
|
||||||
|
'Kalenderereignisreferenz.'},
|
||||||
|
'scheduling.privacy.data-subject-requests': {'limitations': ['Umfragen und Einladungsbeweise '
|
||||||
|
'werden nicht in den Export von '
|
||||||
|
'Scheduling kopiert.',
|
||||||
|
'Die Löschung der Teilnehmer bleibt '
|
||||||
|
'manuell, wenn gemeinsame Antworten, '
|
||||||
|
'Lieferung, Selbsteinschreibung, '
|
||||||
|
'Kalendereffekte oder '
|
||||||
|
'Terminalentscheidungen vorliegen.'],
|
||||||
|
'prerequisites': ['Die Datenschutzanfrage und die '
|
||||||
|
'Planungsauswahl wurden unabhängig '
|
||||||
|
'autorisiert und bestätigt.',
|
||||||
|
'Der Rezensent kann sich mit Poll- '
|
||||||
|
'und Kalenderbesitzern abstimmen, '
|
||||||
|
'wenn eine Antwort oder ein '
|
||||||
|
'Ereignis involviert ist.'],
|
||||||
|
'steps': ['Führen Sie die Scheduling-Provider-Such- '
|
||||||
|
'und Überprüfungsdispositionen für '
|
||||||
|
'Teilnehmer, Anfrage, Slot und '
|
||||||
|
'Benachrichtigung aus.',
|
||||||
|
'Führen Sie den Umfrageanbieter für '
|
||||||
|
'tatsächliche Verfügbarkeitsoptionen und '
|
||||||
|
'den Kalenderanbieter für Ereignis oder '
|
||||||
|
'Haltezustand aus.',
|
||||||
|
'Bewahren Sie die Entscheidung und den '
|
||||||
|
'Liefernachweis mit dem Grund auf.',
|
||||||
|
'Anonymisierung nur für einen zugelassenen '
|
||||||
|
'Teilnehmer durchführen, der nach der '
|
||||||
|
'Revalidierung als nicht engagiert '
|
||||||
|
'eingestuft wurde.']},
|
||||||
|
'scheduling.public-self-enrollment': {'steps': ['Wählen Sie eine begrenzte Kapazität, Ablauf und '
|
||||||
|
'erlaubte Identitätsmodi.',
|
||||||
|
'Kopieren Sie den neu ausgestellten Link; der '
|
||||||
|
'Rohnachweis wird nur einmal angezeigt.',
|
||||||
|
'Überwachen Sie die Anzahl der Registrierungen '
|
||||||
|
'und widerrufen Sie den Link, wenn er nicht mehr '
|
||||||
|
'benötigt wird.',
|
||||||
|
'Erfordern Sie einen Wiederherstellungsnachweis, '
|
||||||
|
'bevor Sie eine anonyme Antwort aktualisieren '
|
||||||
|
'oder binden.'],
|
||||||
|
'verification': 'Die Linkliste zeigt Status, Ablauf, '
|
||||||
|
'Kapazitätsnutzung, Zugriffsmodi und '
|
||||||
|
'Widerruf an, ohne die Anmeldeinformationen '
|
||||||
|
'erneut anzuzeigen.'}}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from govoplan_core.core.modules import with_documentation_structured_translations
|
||||||
|
from govoplan_scheduling.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from govoplan_core.core.access import (
|
from govoplan_core.core.access import (
|
||||||
@@ -48,7 +51,7 @@ from govoplan_scheduling.backend.dsar_provider import SCHEDULING_DSAR_CAPABILITY
|
|||||||
|
|
||||||
MODULE_ID = "scheduling"
|
MODULE_ID = "scheduling"
|
||||||
MODULE_NAME = "Scheduling"
|
MODULE_NAME = "Scheduling"
|
||||||
MODULE_VERSION = "0.1.20"
|
MODULE_VERSION = "0.1.22"
|
||||||
READ_SCOPE = "scheduling:schedule:read"
|
READ_SCOPE = "scheduling:schedule:read"
|
||||||
WRITE_SCOPE = "scheduling:schedule:write"
|
WRITE_SCOPE = "scheduling:schedule:write"
|
||||||
ADMIN_SCOPE = "scheduling:schedule:admin"
|
ADMIN_SCOPE = "scheduling:schedule:admin"
|
||||||
@@ -108,6 +111,28 @@ ROLE_TEMPLATES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
DOCUMENTATION = (
|
DOCUMENTATION = (
|
||||||
|
DocumentationTopic(
|
||||||
|
id="scheduling.workspace-layout",
|
||||||
|
title="Scheduling workspace layout",
|
||||||
|
summary="Find workspace actions and read consistently arranged content.",
|
||||||
|
body="Documentation books sit immediately beside the visible heading or contextual label for "
|
||||||
|
"Scheduling, the selected request and public participation details, not among operational "
|
||||||
|
"action buttons. Field help remains beside its label. "
|
||||||
|
"The workspace always keeps Reload and New scheduling request at the upper right, including empty, detail, and editor states. Reload sits immediately before New. Creation remains visible but disabled while saving or without creation/write permission; it never disappears inside the request-list card. An open editor keeps its own Save and Discard actions and the unsaved-change guard applies before switching requests. Administrators grant existing Scheduling permissions; this layout adds no permission or automatic invitation or Calendar action.",
|
||||||
|
layer="static",
|
||||||
|
documentation_types=("user", "admin"),
|
||||||
|
audience=("user", "module_admin", "operator"),
|
||||||
|
order=5,
|
||||||
|
translations={"de": {
|
||||||
|
"title": "Terminfindung: Aufbau des Arbeitsbereichs",
|
||||||
|
"summary": "Arbeitsbereichsaktionen finden und einheitlich angeordnete Inhalte lesen.",
|
||||||
|
"body": "Dokumentationsbücher stehen unmittelbar neben der sichtbaren Überschrift oder "
|
||||||
|
"Kontextbezeichnung für Terminfindung, die ausgewählte Anfrage und öffentliche "
|
||||||
|
"Teilnahmedetails, nicht zwischen ausführbaren Aktionsschaltflächen. Feldhilfe bleibt neben "
|
||||||
|
"der Feldbezeichnung. "
|
||||||
|
"Der Arbeitsbereich zeigt Neu laden und Neue Terminanfrage immer oben rechts, auch in leerem Zustand, Detailansicht und Bearbeitung. Neu laden steht unmittelbar vor Neu. Das Anlegen bleibt beim Speichern oder ohne Schreibrecht sichtbar, aber deaktiviert; es verschwindet nicht innerhalb der Anfragekartenleiste. Ein geöffneter Editor behält Speichern und Verwerfen; beim Wechsel schützt die Rückfrage ungespeicherte Änderungen. Administratoren vergeben bestehende Scheduling-Rechte; dieses Layout fügt weder Rechte noch automatische Einladungen oder Kalenderaktionen hinzu.",
|
||||||
|
}},
|
||||||
|
),
|
||||||
DocumentationTopic(
|
DocumentationTopic(
|
||||||
id="scheduling.module-boundary",
|
id="scheduling.module-boundary",
|
||||||
title="Scheduling module boundary",
|
title="Scheduling module boundary",
|
||||||
@@ -799,5 +824,10 @@ manifest = ModuleManifest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
manifest = with_documentation_structured_translations(
|
||||||
|
manifest, locale="de", translations=GERMAN_STRUCTURED_TRANSLATIONS
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_manifest() -> ModuleManifest:
|
def get_manifest() -> ModuleManifest:
|
||||||
return manifest
|
return manifest
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from govoplan_core.core.modules import ModuleManifest
|
from govoplan_core.core.modules import ModuleManifest
|
||||||
from govoplan_scheduling.backend.manifest import get_manifest
|
from govoplan_scheduling.backend.manifest import get_manifest
|
||||||
|
|
||||||
|
|
||||||
class SchedulingManifestTests(unittest.TestCase):
|
class SchedulingManifestTests(unittest.TestCase):
|
||||||
|
def test_widget_documentation_is_contributed_for_its_dashboard_title(self) -> None:
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
contribution = (root / "webui/src/module.ts").read_text(encoding="utf-8")
|
||||||
|
widget = (root / "webui/src/features/scheduling/SchedulingRequestsWidget.tsx").read_text(encoding="utf-8")
|
||||||
|
self.assertIn(
|
||||||
|
'documentation: { topicId: "scheduling.find-and-decide-meeting-time", documentationType: "user" }',
|
||||||
|
contribution,
|
||||||
|
)
|
||||||
|
self.assertNotIn("DocumentationHelpLink", widget)
|
||||||
|
|
||||||
def test_all_static_topics_have_complete_german_content(self) -> None:
|
def test_all_static_topics_have_complete_german_content(self) -> None:
|
||||||
manifest = get_manifest()
|
manifest = get_manifest()
|
||||||
for topic in manifest.documentation:
|
for topic in manifest.documentation:
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/scheduling-webui",
|
"name": "@govoplan/scheduling-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.22",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"test:ui-structure": "node scripts/test-scheduling-page-structure.mjs"
|
"test:ui-structure": "node scripts/test-scheduling-page-structure.mjs"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.45",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": ">=19.2.7 <20",
|
"react": ">=19.2.7 <20",
|
||||||
"react-dom": ">=19.2.7 <20",
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ assert.match(page, /ActionBlockerHint,[\s\S]*DocumentationHelpLink,[\s\S]*StageR
|
|||||||
assert.match(page, /className="scheduling-workspace-layout"/);
|
assert.match(page, /className="scheduling-workspace-layout"/);
|
||||||
assert.match(page, /<aside className="scheduling-request-sidebar">/);
|
assert.match(page, /<aside className="scheduling-request-sidebar">/);
|
||||||
assert.match(page, /<section className="scheduling-main-panel">/);
|
assert.match(page, /<section className="scheduling-main-panel">/);
|
||||||
assert.match(page, /title=\{I18N\.requests\}[\s\S]*<Plus aria-hidden="true" size=\{16\} \/> \{I18N\.add\}/);
|
const workspaceBar = page.slice(page.indexOf('<section className="scheduling-workspace">'), page.indexOf('<div className="scheduling-workspace-layout">'));
|
||||||
|
assert.match(workspaceBar, /scope="workspace"[\s\S]*variant="collection"[\s\S]*reloadAction=/);
|
||||||
|
assert.match(workspaceBar, /createAction=\{<Button[\s\S]*disabled=\{!canCreateOrWrite \|\| saving\}[\s\S]*I18N\.newRequest/);
|
||||||
|
assert.doesNotMatch(workspaceBar, /createAction=\{canCreateOrWrite \?/);
|
||||||
|
assert.match(workspaceBar, /!canCreateOrWrite \? I18N\.createPermissionRequired : undefined/);
|
||||||
|
assert.doesNotMatch(page, /scheduling-sidebar-actions/);
|
||||||
assert.match(page, /title=\{I18N\.myRequests\}/);
|
assert.match(page, /title=\{I18N\.myRequests\}/);
|
||||||
assert.match(page, /title=\{I18N\.invitedRequests\}/);
|
assert.match(page, /title=\{I18N\.invitedRequests\}/);
|
||||||
assert.ok(page.indexOf("title={I18N.myRequests}") < page.indexOf("title={I18N.invitedRequests}"));
|
assert.ok(page.indexOf("title={I18N.myRequests}") < page.indexOf("title={I18N.invitedRequests}"));
|
||||||
@@ -196,7 +201,8 @@ assert.match(enrollmentPage, /topicId: "scheduling\.public-self-enrollment"/);
|
|||||||
assert.doesNotMatch(enrollmentPage, /window\.(?:alert|confirm)\(/);
|
assert.doesNotMatch(enrollmentPage, /window\.(?:alert|confirm)\(/);
|
||||||
assert.doesNotMatch(enrollmentPage, /(?:localStorage|sessionStorage).*(?:token|proof)|(?:token|proof).*(?:localStorage|sessionStorage)/);
|
assert.doesNotMatch(enrollmentPage, /(?:localStorage|sessionStorage).*(?:token|proof)|(?:token|proof).*(?:localStorage|sessionStorage)/);
|
||||||
|
|
||||||
assert.match(widget, /DocumentationHelpLink/);
|
assert.doesNotMatch(widget, /DocumentationHelpLink/, "widget help belongs to the host card title, not a detached footer");
|
||||||
|
assert.match(moduleSource, /documentation: \{ topicId: "scheduling\.find-and-decide-meeting-time", documentationType: "user" \}/);
|
||||||
assert.match(widget, /to: `\/scheduling\?request_id=\$\{encodeURIComponent\(request\.id\)\}`/);
|
assert.match(widget, /to: `\/scheduling\?request_id=\$\{encodeURIComponent\(request\.id\)\}`/);
|
||||||
assert.match(widget, /label=\{request\.status === "collecting" \? I18N\.open : I18N\.draft\}/);
|
assert.match(widget, /label=\{request\.status === "collecting" \? I18N\.open : I18N\.draft\}/);
|
||||||
assert.doesNotMatch(widget, /Loading scheduling requests|Open scheduling|No scheduling requests are awaiting responses/);
|
assert.doesNotMatch(widget, /Loading scheduling requests|Open scheduling|No scheduling requests are awaiting responses/);
|
||||||
|
|||||||
@@ -202,11 +202,11 @@ export default function SchedulingEnrollmentPage({ settings, auth }: SchedulingE
|
|||||||
{enrollment ? (
|
{enrollment ? (
|
||||||
<form className="scheduling-public-content" onSubmit={submit}>
|
<form className="scheduling-public-content" onSubmit={submit}>
|
||||||
<Card
|
<Card
|
||||||
title={enrollment.title}
|
title={enrollment.title} titleHelp={<DocumentationHelpLink reference={{
|
||||||
actions={<DocumentationHelpLink reference={{
|
|
||||||
topicId: "scheduling.public-self-enrollment",
|
topicId: "scheduling.public-self-enrollment",
|
||||||
documentationType: "user"
|
documentationType: "user"
|
||||||
}} />}>
|
}} />}
|
||||||
|
>
|
||||||
{enrollment.description ? <p>{enrollment.description}</p> : null}
|
{enrollment.description ? <p>{enrollment.description}</p> : null}
|
||||||
<dl className="scheduling-public-summary">
|
<dl className="scheduling-public-summary">
|
||||||
{enrollment.deadline_at ? <><dt>{I18N.deadline}</dt><dd>{formatDateTime(enrollment.deadline_at)}</dd></> : null}
|
{enrollment.deadline_at ? <><dt>{I18N.deadline}</dt><dd>{formatDateTime(enrollment.deadline_at)}</dd></> : null}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { ContentGrid, FormGrid,
|
|||||||
DocumentationHelpLink,
|
DocumentationHelpLink,
|
||||||
FormField,
|
FormField,
|
||||||
MetricCard,
|
MetricCard,
|
||||||
PageTitle,
|
TextWithHelp,
|
||||||
PasswordField,
|
PasswordField,
|
||||||
PeoplePicker,
|
PeoplePicker,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
@@ -140,6 +140,7 @@ const I18N = {
|
|||||||
actions: "i18n:govoplan-core.actions.c3cd636a",
|
actions: "i18n:govoplan-core.actions.c3cd636a",
|
||||||
add: "i18n:govoplan-core.add.61cc55aa",
|
add: "i18n:govoplan-core.add.61cc55aa",
|
||||||
addRequest: "i18n:govoplan-scheduling.add_scheduling_request.3c71be15",
|
addRequest: "i18n:govoplan-scheduling.add_scheduling_request.3c71be15",
|
||||||
|
createPermissionRequired: "i18n:govoplan-scheduling.create_permission_required",
|
||||||
addParticipant: "i18n:govoplan-scheduling.add_participant.6cff957d",
|
addParticipant: "i18n:govoplan-scheduling.add_participant.6cff957d",
|
||||||
addSlot: "i18n:govoplan-scheduling.add_slot.9fcff3ed",
|
addSlot: "i18n:govoplan-scheduling.add_slot.9fcff3ed",
|
||||||
answered: "i18n:govoplan-scheduling.answered.e0aafffa",
|
answered: "i18n:govoplan-scheduling.answered.e0aafffa",
|
||||||
@@ -933,39 +934,19 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
|||||||
return (
|
return (
|
||||||
<WorkspaceFrame as="main" height="viewport" surface="plain" className="scheduling-page" label="Scheduling workspace">
|
<WorkspaceFrame as="main" height="viewport" surface="plain" className="scheduling-page" label="Scheduling workspace">
|
||||||
<section className="scheduling-workspace">
|
<section className="scheduling-workspace">
|
||||||
<div className="scheduling-workspace-layout">
|
|
||||||
<aside className="scheduling-request-sidebar">
|
|
||||||
<Card
|
|
||||||
title={I18N.requests}
|
|
||||||
actions={(
|
|
||||||
<WorkspaceActionBar
|
<WorkspaceActionBar
|
||||||
scope="collection-pane"
|
scope="workspace"
|
||||||
variant="collection"
|
variant="collection"
|
||||||
refreshable
|
refreshable
|
||||||
reloadAction={{
|
reloadAction={{ onReload: () => void loadRequests(selected?.id), loading: loading || saving, disabledReason: saving ? I18N.saving : undefined, label: I18N.refresh }}
|
||||||
onReload: () => void loadRequests(selected?.id),
|
title="Scheduling"
|
||||||
loading: loading || saving,
|
titleLevel={1}
|
||||||
disabledReason: saving ? I18N.saving : undefined,
|
titleHelp={<DocumentationHelpLink reference={{ topicId: "scheduling.find-and-decide-meeting-time", documentationType: "user" }} />}
|
||||||
label: I18N.refresh
|
createAction={<Button type="button" variant="primary" onClick={beginCreate} disabled={!canCreateOrWrite || saving} disabledReason={saving ? I18N.saving : !canCreateOrWrite ? I18N.createPermissionRequired : undefined}><Plus aria-hidden="true" size={16} /> {I18N.newRequest}</Button>}
|
||||||
}}
|
|
||||||
className="scheduling-sidebar-actions"
|
|
||||||
helpAction={<DocumentationHelpLink
|
|
||||||
reference={{
|
|
||||||
topicId: "scheduling.find-and-decide-meeting-time",
|
|
||||||
documentationType: "user"
|
|
||||||
}} />}
|
|
||||||
createAction={canCreateOrWrite ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="primary"
|
|
||||||
onClick={beginCreate}
|
|
||||||
disabled={saving}
|
|
||||||
disabledReason={saving ? I18N.saving : undefined}>
|
|
||||||
<Plus aria-hidden="true" size={16} /> {I18N.add}
|
|
||||||
</Button>
|
|
||||||
) : undefined}
|
|
||||||
/>
|
/>
|
||||||
)}>
|
<div className="scheduling-workspace-layout">
|
||||||
|
<aside className="scheduling-request-sidebar">
|
||||||
|
<Card title={I18N.requests}>
|
||||||
{loading ? <p className="scheduling-note" role="status">{I18N.loading}</p> : null}
|
{loading ? <p className="scheduling-note" role="status">{I18N.loading}</p> : null}
|
||||||
<RequestGroup
|
<RequestGroup
|
||||||
title={I18N.myRequests}
|
title={I18N.myRequests}
|
||||||
@@ -1003,19 +984,15 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
|||||||
scope="editor-pane"
|
scope="editor-pane"
|
||||||
variant="editor"
|
variant="editor"
|
||||||
state={saving ? "saving" : draftDirty && !title.trim() ? "invalid" : draftDirty ? "dirty" : "clean"}
|
state={saving ? "saving" : draftDirty && !title.trim() ? "invalid" : draftDirty ? "dirty" : "clean"}
|
||||||
className="scheduling-page-header"
|
|
||||||
contextActions={<div className="scheduling-page-title">
|
contextActions={<div className="scheduling-page-title">
|
||||||
<CalendarCheck aria-hidden="true" size={20} />
|
<CalendarCheck aria-hidden="true" size={20} />
|
||||||
<div>
|
<div>
|
||||||
<PageTitle>{editorMode === "create" ? I18N.newRequest : I18N.edit}</PageTitle>
|
<TextWithHelp as="div" help={<DocumentationHelpLink
|
||||||
|
reference={{ topicId: "scheduling.find-and-decide-meeting-time", documentationType: "user" }}
|
||||||
|
/>}><h2>{editorMode === "create" ? I18N.newRequest : I18N.edit}</h2></TextWithHelp>
|
||||||
<p>{editorMode === "create" ? I18N.addRequest : title}</p>
|
<p>{editorMode === "create" ? I18N.addRequest : title}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
helpAction={<DocumentationHelpLink
|
|
||||||
reference={{
|
|
||||||
topicId: "scheduling.find-and-decide-meeting-time",
|
|
||||||
documentationType: "user"
|
|
||||||
}} />}
|
|
||||||
discardAction={{ label: I18N.discard, type: "button", onClick: discardEditor }}
|
discardAction={{ label: I18N.discard, type: "button", onClick: discardEditor }}
|
||||||
saveAction={{
|
saveAction={{
|
||||||
label: <><Save aria-hidden="true" size={16} /> {saving ? I18N.saving : I18N.save}</>,
|
label: <><Save aria-hidden="true" size={16} /> {saving ? I18N.saving : I18N.save}</>,
|
||||||
@@ -1193,14 +1170,14 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
|||||||
statusLabel: stage.current ? requestStatusLabel(selected, actor) : undefined
|
statusLabel: stage.current ? requestStatusLabel(selected, actor) : undefined
|
||||||
}))} />
|
}))} />
|
||||||
<Card
|
<Card
|
||||||
title={selected.title}
|
title={selected.title} titleHelp={<DocumentationHelpLink
|
||||||
actions={(
|
|
||||||
<div className="scheduling-actions">
|
|
||||||
<DocumentationHelpLink
|
|
||||||
reference={{
|
reference={{
|
||||||
topicId: "scheduling.find-and-decide-meeting-time",
|
topicId: "scheduling.find-and-decide-meeting-time",
|
||||||
documentationType: "user"
|
documentationType: "user"
|
||||||
}} />
|
}} />}
|
||||||
|
actions={(
|
||||||
|
<div className="scheduling-actions">
|
||||||
|
|
||||||
{canEditSelected ? (
|
{canEditSelected ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2416,11 +2393,11 @@ function SelfEnrollmentLinksCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={I18N.selfEnrollmentLinks}
|
title={I18N.selfEnrollmentLinks} titleHelp={<DocumentationHelpLink reference={{
|
||||||
actions={<DocumentationHelpLink reference={{
|
|
||||||
topicId: "scheduling.public-self-enrollment",
|
topicId: "scheduling.public-self-enrollment",
|
||||||
documentationType: "user"
|
documentationType: "user"
|
||||||
}} />}>
|
}} />}
|
||||||
|
>
|
||||||
<p className="scheduling-capability-note">
|
<p className="scheduling-capability-note">
|
||||||
{I18N.selfEnrollmentLinksHelp}
|
{I18N.selfEnrollmentLinksHelp}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -170,14 +170,12 @@ export default function SchedulingPublicPage({ settings, auth }: SchedulingPubli
|
|||||||
|
|
||||||
{!response && !loading && (
|
{!response && !loading && (
|
||||||
<Card
|
<Card
|
||||||
title={I18N.accessDetails}
|
title={I18N.accessDetails} titleHelp={<DocumentationHelpLink
|
||||||
actions={(
|
|
||||||
<DocumentationHelpLink
|
|
||||||
reference={{
|
reference={{
|
||||||
topicId: "scheduling.find-and-decide-meeting-time",
|
topicId: "scheduling.find-and-decide-meeting-time",
|
||||||
documentationType: "user"
|
documentationType: "user"
|
||||||
}} />
|
}} />}
|
||||||
)}>
|
>
|
||||||
<form className="scheduling-public-access-form" onSubmit={openRequest}>
|
<form className="scheduling-public-access-form" onSubmit={openRequest}>
|
||||||
<p className="muted">{I18N.accessHelp}</p>
|
<p className="muted">{I18N.accessHelp}</p>
|
||||||
{accessAttempted && error && <DismissibleAlert tone="danger">{error}</DismissibleAlert>}
|
{accessAttempted && error && <DismissibleAlert tone="danger">{error}</DismissibleAlert>}
|
||||||
@@ -213,14 +211,12 @@ export default function SchedulingPublicPage({ settings, auth }: SchedulingPubli
|
|||||||
{response && (
|
{response && (
|
||||||
<form className="scheduling-public-content" onSubmit={saveResponse}>
|
<form className="scheduling-public-content" onSubmit={saveResponse}>
|
||||||
<Card
|
<Card
|
||||||
title={response.title}
|
title={response.title} titleHelp={<DocumentationHelpLink
|
||||||
actions={(
|
|
||||||
<DocumentationHelpLink
|
|
||||||
reference={{
|
reference={{
|
||||||
topicId: "scheduling.find-and-decide-meeting-time",
|
topicId: "scheduling.find-and-decide-meeting-time",
|
||||||
documentationType: "user"
|
documentationType: "user"
|
||||||
}} />
|
}} />}
|
||||||
)}>
|
>
|
||||||
{response.description && <p className="scheduling-public-description">{response.description}</p>}
|
{response.description && <p className="scheduling-public-description">{response.description}</p>}
|
||||||
<dl className="scheduling-public-summary">
|
<dl className="scheduling-public-summary">
|
||||||
{response.location && <><dt>i18n:govoplan-scheduling.location.d219c681</dt><dd>{response.location}</dd></>}
|
{response.location && <><dt>i18n:govoplan-scheduling.location.d219c681</dt><dd>{response.location}</dd></>}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Link } from "react-router";
|
|||||||
import {
|
import {
|
||||||
DashboardWidgetList,
|
DashboardWidgetList,
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
DocumentationHelpLink,
|
|
||||||
LoadingFrame,
|
LoadingFrame,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
@@ -80,11 +79,6 @@ export default function SchedulingRequestsWidget({
|
|||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
<div className="dashboard-contribution-footer">
|
<div className="dashboard-contribution-footer">
|
||||||
<DocumentationHelpLink
|
|
||||||
reference={{
|
|
||||||
topicId: "scheduling.find-and-decide-meeting-time",
|
|
||||||
documentationType: "user"
|
|
||||||
}} />
|
|
||||||
<Link className="btn btn-secondary" to="/scheduling">
|
<Link className="btn btn-secondary" to="/scheduling">
|
||||||
{I18N.openScheduling}
|
{I18N.openScheduling}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const generatedTranslations = {
|
export const generatedTranslations = {
|
||||||
en: {
|
en: {
|
||||||
|
"Scheduling": "Scheduling",
|
||||||
"i18n:govoplan-scheduling.self_enrollment.access": "Open self-enrollment",
|
"i18n:govoplan-scheduling.self_enrollment.access": "Open self-enrollment",
|
||||||
"i18n:govoplan-scheduling.self_enrollment.bind_account": "Bind this enrollment to my signed-in account",
|
"i18n:govoplan-scheduling.self_enrollment.bind_account": "Bind this enrollment to my signed-in account",
|
||||||
"i18n:govoplan-scheduling.self_enrollment.bind_help": "Account binding requires confirmation and lets you manage this response from Scheduling.",
|
"i18n:govoplan-scheduling.self_enrollment.bind_help": "Account binding requires confirmation and lets you manage this response from Scheduling.",
|
||||||
@@ -206,6 +207,7 @@ export const generatedTranslations = {
|
|||||||
"i18n:govoplan-scheduling.the_response_replaces_your_previous_availability_choices.74c16d53": "The response replaces your previous availability choices.",
|
"i18n:govoplan-scheduling.the_response_replaces_your_previous_availability_choices.74c16d53": "The response replaces your previous availability choices.",
|
||||||
"i18n:govoplan-scheduling.title.768e0c1c": "Title",
|
"i18n:govoplan-scheduling.title.768e0c1c": "Title",
|
||||||
"i18n:govoplan-scheduling.unavailable.2c9c1f79": "Unavailable",
|
"i18n:govoplan-scheduling.unavailable.2c9c1f79": "Unavailable",
|
||||||
|
"i18n:govoplan-scheduling.create_permission_required": "Creating a scheduling request requires Scheduling write or administration permission. Ask an Access or tenant administrator to grant it.",
|
||||||
"i18n:govoplan-scheduling.unchecked.1b927dec": "Unchecked",
|
"i18n:govoplan-scheduling.unchecked.1b927dec": "Unchecked",
|
||||||
"i18n:govoplan-scheduling.update_response.346233cf": "Update response",
|
"i18n:govoplan-scheduling.update_response.346233cf": "Update response",
|
||||||
"i18n:govoplan-scheduling.use_a_calendar_for_availability_checks_tentative_holds_a.20ccc1fa": "Use a calendar for availability checks, tentative holds, and the final event.",
|
"i18n:govoplan-scheduling.use_a_calendar_for_availability_checks_tentative_holds_a.20ccc1fa": "Use a calendar for availability checks, tentative holds, and the final event.",
|
||||||
@@ -218,6 +220,7 @@ export const generatedTranslations = {
|
|||||||
"i18n:govoplan-scheduling.your_response_has_been_recorded.b855088d": "Your response has been recorded."
|
"i18n:govoplan-scheduling.your_response_has_been_recorded.b855088d": "Your response has been recorded."
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
|
"Scheduling": "Terminplanung",
|
||||||
"i18n:govoplan-scheduling.self_enrollment.access": "Selbstanmeldung öffnen",
|
"i18n:govoplan-scheduling.self_enrollment.access": "Selbstanmeldung öffnen",
|
||||||
"i18n:govoplan-scheduling.self_enrollment.bind_account": "Diese Anmeldung mit meinem angemeldeten Konto verknüpfen",
|
"i18n:govoplan-scheduling.self_enrollment.bind_account": "Diese Anmeldung mit meinem angemeldeten Konto verknüpfen",
|
||||||
"i18n:govoplan-scheduling.self_enrollment.bind_help": "Die Kontoverknüpfung muss bestätigt werden und ermöglicht die Verwaltung dieser Antwort in der Terminplanung.",
|
"i18n:govoplan-scheduling.self_enrollment.bind_help": "Die Kontoverknüpfung muss bestätigt werden und ermöglicht die Verwaltung dieser Antwort in der Terminplanung.",
|
||||||
@@ -424,6 +427,7 @@ export const generatedTranslations = {
|
|||||||
"i18n:govoplan-scheduling.the_response_replaces_your_previous_availability_choices.74c16d53": "Die Antwort ersetzt Ihre vorherige Verfügbarkeitsauswahl.",
|
"i18n:govoplan-scheduling.the_response_replaces_your_previous_availability_choices.74c16d53": "Die Antwort ersetzt Ihre vorherige Verfügbarkeitsauswahl.",
|
||||||
"i18n:govoplan-scheduling.title.768e0c1c": "Titel",
|
"i18n:govoplan-scheduling.title.768e0c1c": "Titel",
|
||||||
"i18n:govoplan-scheduling.unavailable.2c9c1f79": "Nicht verfügbar",
|
"i18n:govoplan-scheduling.unavailable.2c9c1f79": "Nicht verfügbar",
|
||||||
|
"i18n:govoplan-scheduling.create_permission_required": "Zum Anlegen einer Terminanfrage benötigen Sie Schreib- oder Administrationsrechte für Scheduling. Bitten Sie die Access- oder Mandantenadministration um diese Berechtigung.",
|
||||||
"i18n:govoplan-scheduling.unchecked.1b927dec": "Nicht geprüft",
|
"i18n:govoplan-scheduling.unchecked.1b927dec": "Nicht geprüft",
|
||||||
"i18n:govoplan-scheduling.update_response.346233cf": "Antwort aktualisieren",
|
"i18n:govoplan-scheduling.update_response.346233cf": "Antwort aktualisieren",
|
||||||
"i18n:govoplan-scheduling.use_a_calendar_for_availability_checks_tentative_holds_a.20ccc1fa": "Einen Kalender für Verfügbarkeitsprüfungen, vorläufige Reservierungen und den endgültigen Termin verwenden.",
|
"i18n:govoplan-scheduling.use_a_calendar_for_availability_checks_tentative_holds_a.20ccc1fa": "Einen Kalender für Verfügbarkeitsprüfungen, vorläufige Reservierungen und den endgültigen Termin verwenden.",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const schedulingDashboardWidgets: DashboardWidgetsUiCapability = {
|
|||||||
id: "scheduling.open-requests",
|
id: "scheduling.open-requests",
|
||||||
surfaceId: "scheduling.widget.open-requests",
|
surfaceId: "scheduling.widget.open-requests",
|
||||||
title: "Scheduling requests",
|
title: "Scheduling requests",
|
||||||
|
documentation: { topicId: "scheduling.find-and-decide-meeting-time", documentationType: "user" },
|
||||||
description: "Open scheduling polls and their response progress.",
|
description: "Open scheduling polls and their response progress.",
|
||||||
moduleId: "scheduling",
|
moduleId: "scheduling",
|
||||||
category: "Planning",
|
category: "Planning",
|
||||||
|
|||||||
@@ -145,18 +145,6 @@
|
|||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scheduling-sidebar-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scheduling-sidebar-actions .btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scheduling-main-panel {
|
.scheduling-main-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -176,18 +164,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scheduling-page-header {
|
|
||||||
min-height: 58px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
background: var(--panel-header);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scheduling-page-title,
|
.scheduling-page-title,
|
||||||
.scheduling-page-actions,
|
.scheduling-page-actions,
|
||||||
.scheduling-card-actions,
|
.scheduling-card-actions,
|
||||||
@@ -206,7 +182,7 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scheduling-page-title h1 {
|
.scheduling-page-title h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-strong);
|
color: var(--text-strong);
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
@@ -468,11 +444,6 @@
|
|||||||
border-bottom: var(--border-line);
|
border-bottom: var(--border-line);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scheduling-page-header {
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scheduling-page-actions,
|
.scheduling-page-actions,
|
||||||
.scheduling-card-actions,
|
.scheduling-card-actions,
|
||||||
.scheduling-actions {
|
.scheduling-actions {
|
||||||
|
|||||||
Reference in New Issue
Block a user