feat(webui): configure participant roster visibility
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user