feat(scheduling-webui): build two-pane request workspace

This commit is contained in:
2026-07-21 21:17:26 +02:00
parent b1725b8f59
commit 82b158c170
7 changed files with 1559 additions and 627 deletions

View File

@@ -23,11 +23,12 @@ export type SchedulingCandidateSlot = {
export type SchedulingParticipant = {
id: string;
is_current_participant: boolean;
respondent_id?: string | null;
display_name?: string | null;
email?: string | null;
participant_type: string;
required: boolean;
participant_type: string | null;
required: boolean | null;
status: string;
poll_invitation_id?: string | null;
invitation_token?: string | null;
@@ -52,7 +53,7 @@ export type SchedulingParticipantVisibilityDecision = {
export type SchedulingRequest = {
id: string;
tenant_id: string;
tenant_id: string | null;
title: string;
description?: string | null;
location?: string | null;
@@ -69,11 +70,20 @@ export type SchedulingRequest = {
effective_participant_visibility: SchedulingParticipantVisibility;
participant_aggregate: SchedulingParticipantAggregate;
participant_visibility_decision: SchedulingParticipantVisibilityDecision;
calendar_integration_enabled: boolean;
notify_on_answers: boolean;
single_choice: boolean;
max_participants_per_option: number | null;
allow_maybe: boolean;
allow_comments: boolean;
participant_email_required: boolean;
anonymous_password_protection_enabled: boolean;
public_participation_policy_enforcement_available: boolean | null;
public_participation_policy_enforcement_reason?: string | null;
calendar_integration_enabled: boolean | null;
calendar_id?: string | null;
calendar_freebusy_enabled: boolean;
calendar_hold_enabled: boolean;
create_calendar_event_on_decision: boolean;
calendar_freebusy_enabled: boolean | null;
calendar_hold_enabled: boolean | null;
create_calendar_event_on_decision: boolean | null;
calendar_event_id?: string | null;
handed_off_at?: string | null;
cancelled_at?: string | null;
@@ -117,6 +127,14 @@ export type SchedulingRequestCreatePayload = {
allow_participant_updates?: boolean;
result_visibility?: "organizer" | "after_response" | "after_close" | "public";
participant_visibility?: SchedulingParticipantVisibility;
notify_on_answers?: boolean;
single_choice?: boolean;
max_participants_per_option?: number | null;
allow_maybe?: boolean;
allow_comments?: boolean;
participant_email_required?: boolean;
anonymous_password_protection_enabled?: boolean;
anonymous_password?: string;
calendar?: {
enabled?: boolean;
calendar_id?: string | null;
@@ -130,6 +148,33 @@ export type SchedulingRequestCreatePayload = {
metadata?: Record<string, unknown>;
};
export type SchedulingRequestUpdatePayload = Omit<
SchedulingRequestCreatePayload,
"timezone" | "status" | "slots" | "participants"
> & {
slots?: SchedulingCandidateSlotReconcilePayload[];
participants?: SchedulingParticipantReconcilePayload[];
};
export type SchedulingCandidateSlotReconcilePayload = SchedulingCandidateSlotPayload & {
id?: string;
revision?: string;
};
export type SchedulingParticipantReconcilePayload = SchedulingParticipantPayload & {
id?: string;
};
export type SchedulingCandidateSlotUpdatePayload = {
label?: string | null;
description?: string | null;
start_at?: string | null;
end_at?: string | null;
timezone?: string | null;
location?: string | null;
metadata?: Record<string, unknown> | null;
};
export type SchedulingDecisionPayload = {
slot_id?: string | null;
poll_option_id?: string | null;
@@ -144,6 +189,7 @@ export type SchedulingAvailabilityPayload = {
value: SchedulingAvailabilityValue;
option_revision: string;
}>;
comment?: string | null;
};
export type SchedulingAvailabilityResponse = {
@@ -151,12 +197,62 @@ export type SchedulingAvailabilityResponse = {
participant_id: string;
has_response: boolean;
submitted_at?: string | null;
comment?: string | null;
answers: Array<{
slot_id: string;
value: SchedulingAvailabilityValue;
}>;
};
export type SchedulingPublicCandidateSlot = {
id: string;
label: string;
description?: string | null;
start_at: string;
end_at: string;
timezone: string;
location?: string | null;
position: number;
revision: string;
};
export type SchedulingPublicParticipationResponse = {
request_id: string;
title: string;
description?: string | null;
location?: string | null;
timezone: string;
status: SchedulingStatus;
deadline_at?: string | null;
participant_email_required: boolean;
anonymous_password_required: boolean;
single_choice: boolean;
max_participants_per_option: number | null;
allow_maybe: boolean;
allow_comments: boolean;
allow_participant_updates: boolean;
has_response: boolean;
submitted_at?: string | null;
answers: Array<{
slot_id: string;
value: SchedulingAvailabilityValue;
}>;
comment?: string | null;
replayed: boolean;
slots: SchedulingPublicCandidateSlot[];
};
export type SchedulingPublicParticipationAccessPayload = {
participant_email?: string | null;
password?: string | null;
};
export type SchedulingPublicParticipationSubmitPayload = SchedulingPublicParticipationAccessPayload & {
answers: SchedulingAvailabilityPayload["answers"];
comment?: string | null;
idempotency_key?: string;
};
export type SchedulingCalendarActionResponse = {
request: SchedulingRequest;
created_event_ids: string[];
@@ -239,6 +335,29 @@ export function createSchedulingRequest(settings: ApiSettings, payload: Scheduli
return apiFetch<SchedulingRequest>(settings, "/api/v1/scheduling/requests", json(payload));
}
export function updateSchedulingRequest(
settings: ApiSettings,
requestId: string,
payload: SchedulingRequestUpdatePayload
): Promise<SchedulingRequest> {
return apiFetch<SchedulingRequest>(settings, `/api/v1/scheduling/requests/${requestId}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function updateSchedulingCandidateSlot(
settings: ApiSettings,
requestId: string,
slotId: string,
payload: SchedulingCandidateSlotUpdatePayload
): Promise<SchedulingRequest> {
return apiFetch<SchedulingRequest>(settings, `/api/v1/scheduling/requests/${requestId}/slots/${slotId}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function submitSchedulingAvailability(
settings: ApiSettings,
requestId: string,
@@ -261,6 +380,32 @@ export function getSchedulingAvailabilityResponse(
);
}
export function getPublicSchedulingParticipation(
settings: ApiSettings,
requestId: string,
token: string,
payload: SchedulingPublicParticipationAccessPayload
): Promise<SchedulingPublicParticipationResponse> {
return apiFetch<SchedulingPublicParticipationResponse>(
settings,
`/api/v1/scheduling/public/${encodeURIComponent(requestId)}/${encodeURIComponent(token)}`,
json(payload)
);
}
export function submitPublicSchedulingParticipation(
settings: ApiSettings,
requestId: string,
token: string,
payload: SchedulingPublicParticipationSubmitPayload
): Promise<SchedulingPublicParticipationResponse> {
return apiFetch<SchedulingPublicParticipationResponse>(
settings,
`/api/v1/scheduling/public/${encodeURIComponent(requestId)}/${encodeURIComponent(token)}/responses`,
json(payload)
);
}
export function openSchedulingRequest(settings: ApiSettings, requestId: string): Promise<SchedulingStatusResponse> {
return apiFetch<SchedulingStatusResponse>(settings, `/api/v1/scheduling/requests/${requestId}/open`, json({}));
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
import type { SchedulingParticipant, SchedulingRequest } from "../../api/scheduling";
import type {
SchedulingAvailabilityValue,
SchedulingParticipant,
SchedulingRequest
} from "../../api/scheduling";
export type SchedulingActor = {
accountId?: string | null;
@@ -28,20 +32,27 @@ export function schedulingActorIds(actor: SchedulingActor): string[] {
actor.membershipId,
actor.identityId,
actor.email
].filter((value): value is string => Boolean(value))));
].filter((value): value is string => Boolean(value)).flatMap((value) =>
value.includes("@") ? [value, value.trim().toLowerCase()] : [value]
)));
}
export function schedulingParticipantForActor(
request: SchedulingRequest,
actor: SchedulingActor
): SchedulingParticipant | null {
const projected = request.participants.find(
(participant) => participant.is_current_participant
);
if (projected) return projected;
const ids = new Set(schedulingActorIds(actor));
return request.participants.find((participant) =>
Boolean(
return request.participants.find((participant) => {
const email = participant.email?.trim().toLowerCase();
return Boolean(
(participant.respondent_id && ids.has(participant.respondent_id)) ||
(participant.email && ids.has(participant.email))
)
) ?? null;
(email && ids.has(email))
);
}) ?? null;
}
export function schedulingRequestIsOwned(
@@ -137,6 +148,26 @@ export function schedulingRequestIsPast(
return slotEnds.every((value) => Number.isFinite(value) && value < now.getTime());
}
export function applySchedulingAvailabilityChoice(
slotIds: string[],
current: Record<string, SchedulingAvailabilityValue | "">,
slotId: string,
value: SchedulingAvailabilityValue | "",
singleChoice: boolean
): Record<string, SchedulingAvailabilityValue | ""> {
if (!singleChoice || !["available", "maybe"].includes(value)) {
return { ...current, [slotId]: value };
}
return Object.fromEntries(slotIds.map((candidateId) => [
candidateId,
candidateId === slotId
? value
: current[candidateId] && current[candidateId] !== "unavailable"
? "unavailable"
: current[candidateId] ?? ""
]));
}
const SORT_PHASE_ORDER: Record<SchedulingSortPhase, number> = {
unanswered: 0,
answered: 1,

View File

@@ -1,5 +1,38 @@
export const generatedTranslations = {
en: {
"i18n:govoplan-scheduling.save_or_discard_your_unsent_availability_changes_before_leaving.97e10df1": "Save or discard your unsent availability changes before leaving.",
"i18n:govoplan-scheduling.a_slot_can_be_selected_after_the_request_is_closed.f91ec02d": "A slot can be selected after the request is closed.",
"i18n:govoplan-scheduling.add_maybe_between_yes_and_no_for_each_candidate_slot.74dc9db6": "Add Maybe between Available and Unavailable for each candidate slot.",
"i18n:govoplan-scheduling.allow_comments.d63202a6": "Allow comments",
"i18n:govoplan-scheduling.awaiting.42aa82e0": "Awaiting",
"i18n:govoplan-scheduling.capacity.d3c375f8": "Capacity",
"i18n:govoplan-scheduling.comment.d03495b1": "Comment",
"i18n:govoplan-scheduling.create_an_organizer_notification_whenever_a_response_is.253ddca8": "Create an organizer notification whenever a response is submitted or updated.",
"i18n:govoplan-scheduling.each_participant_can_answer_yes_to_at_most_one_candidate.5313a465": "Each participant can select Available or Maybe for at most one candidate slot.",
"i18n:govoplan-scheduling.edit_scheduling_request.7e749c19": "Edit scheduling request",
"i18n:govoplan-scheduling.invited_participant_identity_is_locked_remove_and_add_the_participant_to_change_it.34e1201a": "An invited participant's name and email are locked. Remove and add the participant to change them.",
"i18n:govoplan-scheduling.guest_links_are_not_issued_while_the_configured_participation.0794ebf0": "Guest links are not issued while the configured participation controls cannot be enforced by the public response gateway. Signed-in participants can still respond here.",
"i18n:govoplan-scheduling.guest_password.94545e82": "Guest password",
"i18n:govoplan-scheduling.let_participants_add_a_comment_to_their_response.9dce8d17": "Let participants add a comment to their response.",
"i18n:govoplan-scheduling.limit_participants_per_option.1e9aa51d": "Limit participants per option",
"i18n:govoplan-scheduling.maximum_participants_per_option.5abdfb27": "Maximum participants per option",
"i18n:govoplan-scheduling.notify_me_about_each_answer.505749d6": "Notify me about each answer",
"i18n:govoplan-scheduling.participants_can_choose_only_one_option.4311f51c": "Participants can choose only one option",
"i18n:govoplan-scheduling.participation.9ad70cc4": "Participation",
"i18n:govoplan-scheduling.participation_rate.46bc5504": "Participation rate",
"i18n:govoplan-scheduling.participation_settings.8dc6f62c": "Participation settings",
"i18n:govoplan-scheduling.participant_privacy.108c470f": "Participant privacy",
"i18n:govoplan-scheduling.participation_controls_are_locked_after_invitation_links_are_issued.66f5a740": "Participation controls are locked after invitation links are issued. Participant visibility and answer notifications can still be changed.",
"i18n:govoplan-scheduling.password_protect_guest_access.13d7f08b": "Password-protect guest access",
"i18n:govoplan-scheduling.people_responding_without_an_account_must_provide_an_email.19fd3dc8": "People responding without an account must provide an email address.",
"i18n:govoplan-scheduling.people_who_are_not_signed_in_must_enter_this_password.82bcc4ce": "People who are not signed in must enter this password before viewing the request.",
"i18n:govoplan-scheduling.provide_a_maybe_option.e39da57a": "Provide a Maybe option",
"i18n:govoplan-scheduling.require_an_email_address_from_guests.c2289a58": "Require an email address from guests",
"i18n:govoplan-scheduling.responses.3427e3ab": "Responses",
"i18n:govoplan-scheduling.response_results_are_not_available_for_this_view.1e82db18": "Response results are not available for this view.",
"i18n:govoplan-scheduling.notifications_could_not_be_loaded.f0e1b2c3": "Notifications could not be loaded.",
"i18n:govoplan-scheduling.stop_accepting_yes_responses_for_an_option_when_its_limit.79af21db": "Stop accepting Available responses for an option when its limit is reached.",
"i18n:govoplan-scheduling.use_at_least_8_characters_the_password_is_never_displayed.035708a2": "Use at least 8 characters. The password is never displayed after it is saved.",
"i18n:govoplan-scheduling.add_participant.6cff957d": "Add participant",
"i18n:govoplan-scheduling.add_scheduling_request.3c71be15": "Add scheduling request",
"i18n:govoplan-scheduling.add_slot.9fcff3ed": "Add slot",
@@ -21,7 +54,6 @@ export const generatedTranslations = {
"i18n:govoplan-scheduling.close_poll.a6a18916": "Close poll",
"i18n:govoplan-scheduling.close_stops_accepting_new_availability_responses.a1d57519": "Close stops accepting new availability responses.",
"i18n:govoplan-scheduling.closed.88d86b77": "Closed",
"i18n:govoplan-scheduling.create_and_open_scheduling_request.1dbc9345": "Create and open scheduling request",
"i18n:govoplan-scheduling.create_calendar_event.0b87cfcf": "Create calendar event",
"i18n:govoplan-scheduling.create_tentative_holds.51c4744e": "Create tentative holds",
"i18n:govoplan-scheduling.cancellation.319aaae4": "Cancellation",
@@ -108,6 +140,39 @@ export const generatedTranslations = {
"i18n:govoplan-scheduling.your_response_has_been_recorded.b855088d": "Your response has been recorded."
},
de: {
"i18n:govoplan-scheduling.save_or_discard_your_unsent_availability_changes_before_leaving.97e10df1": "Speichern oder verwerfen Sie Ihre noch nicht gesendeten Verfügbarkeitsänderungen, bevor Sie die Ansicht verlassen.",
"i18n:govoplan-scheduling.a_slot_can_be_selected_after_the_request_is_closed.f91ec02d": "Ein Terminvorschlag kann ausgewählt werden, nachdem die Anfrage geschlossen wurde.",
"i18n:govoplan-scheduling.add_maybe_between_yes_and_no_for_each_candidate_slot.74dc9db6": "Für jeden Terminvorschlag Vielleicht zwischen Verfügbar und Nicht verfügbar anbieten.",
"i18n:govoplan-scheduling.allow_comments.d63202a6": "Kommentare erlauben",
"i18n:govoplan-scheduling.awaiting.42aa82e0": "Ausstehend",
"i18n:govoplan-scheduling.capacity.d3c375f8": "Kapazität",
"i18n:govoplan-scheduling.comment.d03495b1": "Kommentar",
"i18n:govoplan-scheduling.create_an_organizer_notification_whenever_a_response_is.253ddca8": "Bei jeder neuen oder geänderten Antwort eine Benachrichtigung für die organisierende Person erstellen.",
"i18n:govoplan-scheduling.each_participant_can_answer_yes_to_at_most_one_candidate.5313a465": "Jede teilnehmende Person kann höchstens einen Terminvorschlag als Verfügbar oder Vielleicht markieren.",
"i18n:govoplan-scheduling.edit_scheduling_request.7e749c19": "Terminanfrage bearbeiten",
"i18n:govoplan-scheduling.invited_participant_identity_is_locked_remove_and_add_the_participant_to_change_it.34e1201a": "Name und E-Mail-Adresse einer eingeladenen Person sind gesperrt. Entfernen Sie die Person und fügen Sie sie erneut hinzu, um diese Angaben zu ändern.",
"i18n:govoplan-scheduling.guest_links_are_not_issued_while_the_configured_participation.0794ebf0": "Gastlinks werden nicht ausgegeben, solange die konfigurierten Teilnahmeregeln am öffentlichen Antwortzugang nicht durchgesetzt werden können. Angemeldete Teilnehmende können weiterhin hier antworten.",
"i18n:govoplan-scheduling.guest_password.94545e82": "Gastpasswort",
"i18n:govoplan-scheduling.let_participants_add_a_comment_to_their_response.9dce8d17": "Teilnehmende können ihrer Antwort einen Kommentar hinzufügen.",
"i18n:govoplan-scheduling.limit_participants_per_option.1e9aa51d": "Teilnehmende je Terminvorschlag begrenzen",
"i18n:govoplan-scheduling.maximum_participants_per_option.5abdfb27": "Maximale Teilnehmendenzahl je Terminvorschlag",
"i18n:govoplan-scheduling.notify_me_about_each_answer.505749d6": "Über jede Antwort benachrichtigen",
"i18n:govoplan-scheduling.participants_can_choose_only_one_option.4311f51c": "Teilnehmende können nur einen Terminvorschlag wählen",
"i18n:govoplan-scheduling.participation.9ad70cc4": "Teilnahme",
"i18n:govoplan-scheduling.participation_rate.46bc5504": "Teilnahmequote",
"i18n:govoplan-scheduling.participation_settings.8dc6f62c": "Teilnahmeeinstellungen",
"i18n:govoplan-scheduling.participant_privacy.108c470f": "Datenschutz für Teilnehmende",
"i18n:govoplan-scheduling.participation_controls_are_locked_after_invitation_links_are_issued.66f5a740": "Teilnahmeregeln sind gesperrt, nachdem Einladungslinks erstellt wurden. Sichtbarkeit und Antwortbenachrichtigungen können weiterhin geändert werden.",
"i18n:govoplan-scheduling.password_protect_guest_access.13d7f08b": "Gastzugang mit Passwort schützen",
"i18n:govoplan-scheduling.people_responding_without_an_account_must_provide_an_email.19fd3dc8": "Personen ohne Konto müssen für ihre Antwort eine E-Mail-Adresse angeben.",
"i18n:govoplan-scheduling.people_who_are_not_signed_in_must_enter_this_password.82bcc4ce": "Nicht angemeldete Personen müssen dieses Passwort eingeben, bevor sie die Anfrage sehen können.",
"i18n:govoplan-scheduling.provide_a_maybe_option.e39da57a": "Antwort Vielleicht anbieten",
"i18n:govoplan-scheduling.require_an_email_address_from_guests.c2289a58": "E-Mail-Adresse von Gästen verlangen",
"i18n:govoplan-scheduling.responses.3427e3ab": "Antworten",
"i18n:govoplan-scheduling.response_results_are_not_available_for_this_view.1e82db18": "Antwortergebnisse sind für diese Ansicht nicht verfügbar.",
"i18n:govoplan-scheduling.notifications_could_not_be_loaded.f0e1b2c3": "Benachrichtigungen konnten nicht geladen werden.",
"i18n:govoplan-scheduling.stop_accepting_yes_responses_for_an_option_when_its_limit.79af21db": "Keine weiteren Verfügbar-Antworten annehmen, sobald die Begrenzung eines Terminvorschlags erreicht ist.",
"i18n:govoplan-scheduling.use_at_least_8_characters_the_password_is_never_displayed.035708a2": "Verwenden Sie mindestens 8 Zeichen. Das Passwort wird nach dem Speichern nicht mehr angezeigt.",
"i18n:govoplan-scheduling.add_participant.6cff957d": "Teilnehmende Person hinzufügen",
"i18n:govoplan-scheduling.add_scheduling_request.3c71be15": "Terminanfrage hinzufügen",
"i18n:govoplan-scheduling.add_slot.9fcff3ed": "Terminvorschlag hinzufügen",
@@ -129,7 +194,6 @@ export const generatedTranslations = {
"i18n:govoplan-scheduling.close_poll.a6a18916": "Abstimmung schließen",
"i18n:govoplan-scheduling.close_stops_accepting_new_availability_responses.a1d57519": "Schließen beendet die Annahme neuer Verfügbarkeitsantworten.",
"i18n:govoplan-scheduling.closed.88d86b77": "Geschlossen",
"i18n:govoplan-scheduling.create_and_open_scheduling_request.1dbc9345": "Terminanfrage erstellen und öffnen",
"i18n:govoplan-scheduling.create_calendar_event.0b87cfcf": "Kalendertermin erstellen",
"i18n:govoplan-scheduling.create_tentative_holds.51c4744e": "Vorläufige Reservierungen erstellen",
"i18n:govoplan-scheduling.cancellation.319aaae4": "Stornierung",

View File

@@ -13,8 +13,7 @@
box-sizing: border-box;
}
.scheduling-workspace,
.scheduling-full-editor {
.scheduling-workspace {
width: 100%;
height: 100%;
min-width: 0;
@@ -26,6 +25,72 @@
background: var(--bg);
}
.scheduling-workspace-layout {
min-width: 0;
min-height: 0;
flex: 1 1 auto;
display: grid;
grid-template-columns: minmax(310px, 370px) minmax(0, 1fr);
overflow: hidden;
}
.scheduling-request-sidebar,
.scheduling-main-panel,
.scheduling-editor-surface {
min-width: 0;
min-height: 0;
}
.scheduling-request-sidebar {
padding: 12px;
border-right: var(--border-line);
background: var(--panel-soft);
}
.scheduling-request-sidebar > .card {
height: 100%;
display: flex;
flex-direction: column;
}
.scheduling-request-sidebar > .card > .card-body {
min-height: 0;
flex: 1 1 auto;
overflow: auto;
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 {
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--bg);
}
.scheduling-main-panel > .alert {
flex: 0 0 auto;
margin: 12px 14px 0;
}
.scheduling-editor-surface {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
}
.scheduling-page-header {
min-height: 58px;
flex: 0 0 auto;
@@ -89,28 +154,9 @@
gap: 6px;
}
.scheduling-alert,
.scheduling-success {
margin: 10px 14px 0;
padding: 9px 11px;
border: var(--border-line);
border-radius: 7px;
}
.scheduling-alert {
border-color: var(--danger-border-strong);
color: var(--danger-text-strong);
background: var(--danger-soft);
}
.scheduling-success {
border-color: var(--success);
color: var(--success);
background: color-mix(in srgb, var(--success) 10%, var(--panel));
}
.scheduling-detail,
.scheduling-editor-scroll {
flex: 1 1 auto;
min-width: 0;
min-height: 0;
overflow: auto;
@@ -124,15 +170,21 @@
gap: 12px;
}
.scheduling-request-groups {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 12px;
.scheduling-request-group + .scheduling-request-group {
margin-top: 14px;
padding-top: 14px;
border-top: var(--border-line);
}
.scheduling-request-group h2 {
margin: 0 0 8px;
color: var(--text-strong);
font-size: 13px;
}
.scheduling-list-group {
min-height: 52px;
max-height: 270px;
max-height: min(31vh, 320px);
overflow: auto;
}
@@ -171,14 +223,6 @@
font-size: 12px;
}
.scheduling-list-status {
max-width: 120px;
padding: 3px 7px;
border-radius: 999px;
background: var(--panel-soft);
font-weight: 700;
}
.scheduling-list-empty {
margin: 3px 0 8px;
}
@@ -187,46 +231,12 @@
flex-wrap: wrap;
}
.scheduling-status {
padding: 4px 8px;
border-radius: 999px;
color: var(--text-strong);
background: var(--panel-soft);
font-size: 12px;
font-weight: 800;
}
.scheduling-status-collecting,
.scheduling-status-handed_off {
background: color-mix(in srgb, var(--accent) 18%, transparent);
}
.scheduling-slot-title {
min-width: 0;
display: grid;
justify-items: start;
}
.scheduling-freebusy {
display: inline-block;
margin-top: 3px;
padding: 2px 6px;
border-radius: 5px;
color: var(--muted);
background: var(--panel-soft);
font-size: 11px;
font-weight: 700;
}
.scheduling-freebusy.free {
color: var(--success);
}
.scheduling-freebusy.busy,
.scheduling-freebusy.error {
color: var(--danger-text-strong);
}
.scheduling-columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -284,20 +294,6 @@
color: var(--muted);
}
.scheduling-response-grid select,
.scheduling-field input,
.scheduling-field textarea,
.scheduling-grid-input {
width: 100%;
min-height: 36px;
padding: 7px 9px;
border: var(--border-line);
border-radius: 6px;
color: var(--text);
background: var(--control-bg);
font: inherit;
}
.scheduling-action-help {
margin: 0;
}
@@ -308,47 +304,14 @@
text-align: center;
}
.scheduling-editor-page {
background: var(--panel);
}
.scheduling-editor-layout {
.scheduling-setting-with-field {
min-width: 0;
min-height: 0;
flex: 1 1 auto;
display: grid;
grid-template-columns: 198px minmax(0, 1fr);
overflow: hidden;
gap: 10px;
}
.scheduling-create-subnav {
min-height: 0;
}
.scheduling-create-section {
min-width: 0;
scroll-margin-top: 14px;
}
.scheduling-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.scheduling-field-wide {
grid-column: 1 / -1;
}
.scheduling-field {
display: grid;
gap: 5px;
}
.scheduling-field > span {
color: var(--muted);
font-size: 12px;
font-weight: 800;
.scheduling-setting-with-field .form-field {
padding-left: 48px;
}
.scheduling-capability-note {
@@ -362,9 +325,10 @@
}
@media (max-width: 900px) {
.scheduling-editor-layout {
grid-template-columns: minmax(0, 1fr);
.scheduling-workspace-layout {
grid-template-columns: minmax(270px, 320px) minmax(0, 1fr);
}
}
@media (max-width: 820px) {
@@ -375,16 +339,27 @@
}
.scheduling-workspace,
.scheduling-full-editor,
.scheduling-editor-layout,
.scheduling-workspace-layout,
.scheduling-main-panel,
.scheduling-editor-surface,
.scheduling-detail,
.scheduling-editor-scroll {
height: auto;
overflow: visible;
}
.scheduling-columns,
.scheduling-form-grid {
.scheduling-workspace-layout {
display: grid;
grid-template-columns: minmax(0, 1fr);
}
.scheduling-request-sidebar {
max-height: 48vh;
border-right: 0;
border-bottom: var(--border-line);
}
.scheduling-columns {
grid-template-columns: minmax(0, 1fr);
}