feat(webui): configure participant roster visibility

This commit is contained in:
2026-07-20 18:46:41 +02:00
parent 6141f39bf8
commit 705ac823ce
5 changed files with 78 additions and 2 deletions

View File

@@ -111,6 +111,9 @@ const I18N = {
open: "i18n:govoplan-scheduling.open.cf9b7706",
openPoll: "i18n:govoplan-scheduling.open_poll.2beac9a7",
participantEmail: "i18n:govoplan-scheduling.participant_email.2cadfd9e",
participantRosterHidden: "i18n:govoplan-scheduling.participant_names_and_statuses_are_hidden_aggregate_counts_r.d811a69a",
participantRosterVisibility: "i18n:govoplan-scheduling.share_participant_names_and_response_statuses.df0bf9e0",
participantRosterVisibilityHelp: "i18n:govoplan-scheduling.when_enabled_participants_can_see_other_participants_names.3ac78361",
participants: "i18n:govoplan-scheduling.participants.cd56e083",
past: "i18n:govoplan-scheduling.past.405c12fb",
refresh: "i18n:govoplan-scheduling.refresh_requests.0a3ed7a1",
@@ -176,6 +179,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
const [calendarEnabled, setCalendarEnabled] = useState(false);
const [slots, setSlots] = useState<SlotDraft[]>(() => initialSlots(translateText));
const [participants, setParticipants] = useState<ParticipantDraft[]>(() => [emptyParticipant()]);
const [participantRosterVisible, setParticipantRosterVisible] = useState(false);
const [availability, setAvailability] = useState<Record<string, SchedulingAvailabilityValue | "">>({});
const [availabilityLoading, setAvailabilityLoading] = useState(false);
@@ -347,6 +351,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
location: location.trim() || null,
timezone,
status: "collecting",
participant_visibility: participantRosterVisible ? "names_and_statuses" : "aggregates_only",
calendar: {
enabled: calendarEnabled,
calendar_id: calendarEnabled ? calendarId : null,
@@ -534,7 +539,12 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
<section id={CREATE_SECTION_IDS.participants} className="scheduling-create-section">
<EditableParticipants
participants={participants}
participantRosterVisible={participantRosterVisible}
disabled={!canWrite}
onParticipantRosterVisibleChange={(checked) => {
setParticipantRosterVisible(checked);
setDraftDirty(true);
}}
onAddBelow={(index) => {
setParticipants((items) => insertAfter(items, index, emptyParticipant()));
setDraftDirty(true);
@@ -606,7 +616,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
) : null}>
<div className="scheduling-status-row">
<span className={`scheduling-status scheduling-status-${selected.status}`}>{requestStatusLabel(selected, actor)}</span>
<span>{i18nMessage("i18n:govoplan-scheduling.value_participants.e776b092", { value0: selected.participants.length })}</span>
<span>{i18nMessage("i18n:govoplan-scheduling.value_participants.e776b092", { value0: selected.participant_aggregate.total })}</span>
{summary ? <span>{i18nMessage("i18n:govoplan-scheduling.value_responses.ba17af9a", { value0: summary.poll_summary.response_count })}</span> : null}
</div>
{selected.description ? <p>{selected.description}</p> : null}
@@ -721,7 +731,17 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
<div className="scheduling-columns">
<Card title={I18N.participants}>
<ParticipantsGrid participants={selected.participants} />
<div className="scheduling-status-row">
<span>{i18nMessage("i18n:govoplan-scheduling.value_participants.e776b092", { value0: selected.participant_aggregate.total })}</span>
{Object.entries(selected.participant_aggregate.status_counts)
.filter(([, count]) => count > 0)
.map(([status, count]) => (
<span key={status}><strong>{participantStatusLabel(status)}</strong> {count}</span>
))}
</div>
{selected.effective_participant_visibility === "names_and_statuses" ? (
<ParticipantsGrid participants={selected.participants} />
) : <p className="scheduling-capability-note">{I18N.participantRosterHidden}</p>}
</Card>
<Card title={I18N.notifications}>
{notifications.length ? notifications.map((notification) => (
@@ -767,6 +787,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
setCalendarEnabled(false);
setSlots(initialSlots(translateText));
setParticipants([emptyParticipant()]);
setParticipantRosterVisible(false);
setCreateSection("basic");
setDraftDirty(false);
}
@@ -923,14 +944,18 @@ function EditableSlots({
function EditableParticipants({
participants,
participantRosterVisible,
disabled,
onParticipantRosterVisibleChange,
onAddBelow,
onChange,
onMove,
onRemove
}: {
participants: ParticipantDraft[];
participantRosterVisible: boolean;
disabled: boolean;
onParticipantRosterVisibleChange: (checked: boolean) => void;
onAddBelow: (index: number) => void;
onChange: (index: number, patch: Partial<ParticipantDraft>) => void;
onMove: (from: number, to: number) => void;
@@ -988,6 +1013,12 @@ function EditableParticipants({
return (
<Card title={I18N.participants}>
<ToggleSwitch
label={I18N.participantRosterVisibility}
help={I18N.participantRosterVisibilityHelp}
checked={participantRosterVisible}
disabled={disabled}
onChange={onParticipantRosterVisibleChange} />
<DataGrid
id="scheduling-create-participants-grid"
rows={participants}