refactor(webui): use central scheduling controls

This commit is contained in:
2026-07-21 12:05:02 +02:00
parent 98b797a95d
commit 770ecf8786
3 changed files with 26 additions and 39 deletions

View File

@@ -52,7 +52,7 @@ assert.match(page, /<DataGridEmptyAction/);
assert.doesNotMatch(page, /EmailAddressInput|MailboxAddress|addressSuggestions|addressLookupQuery/); assert.doesNotMatch(page, /EmailAddressInput|MailboxAddress|addressSuggestions|addressLookupQuery/);
assert.match(page, /type="email"[\s\S]*aria-label=\{I18N\.participantEmail\}/); assert.match(page, /type="email"[\s\S]*aria-label=\{I18N\.participantEmail\}/);
assert.doesNotMatch(page, /header: I18N\.required|<table|scheduling-table|scheduling-card(?:\s|"|`)/); assert.doesNotMatch(page, /header: I18N\.required|<table|scheduling-table|scheduling-card(?:\s|"|`)/);
assert.match(page, /aria-label=\{i18nMessage\("i18n:govoplan-scheduling\.decide_on_value/); assert.match(page, /<TableActionGroup[\s\S]*label: i18nMessage\("i18n:govoplan-scheduling\.decide_on_value/);
assert.match(page, /submitSchedulingAvailability\(settings, selected\.id/); assert.match(page, /submitSchedulingAvailability\(settings, selected\.id/);
assert.match(page, /option_revision: slot\.revision/); assert.match(page, /option_revision: slot\.revision/);
assert.match(page, /getSchedulingAvailabilityResponse\(settings, selected\.id\)/); assert.match(page, /getSchedulingAvailabilityResponse\(settings, selected\.id\)/);

View File

@@ -11,12 +11,15 @@ import {
XCircle XCircle
} from "lucide-react"; } from "lucide-react";
import { import {
AdminIconButton,
Button, Button,
Card, Card,
DataGrid, DataGrid,
DataGridEmptyAction, DataGridEmptyAction,
DataGridRowActions, DataGridRowActions,
DateTimeField,
ModuleSubnav, ModuleSubnav,
TableActionGroup,
ToggleSwitch, ToggleSwitch,
hasScope, hasScope,
i18nMessage, i18nMessage,
@@ -578,9 +581,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
<h1>{I18N.requests}</h1> <h1>{I18N.requests}</h1>
</div> </div>
<div className="scheduling-page-actions"> <div className="scheduling-page-actions">
<Button type="button" className="scheduling-icon-button" onClick={() => void loadRequests()} title={I18N.refresh} aria-label={I18N.refresh}> <AdminIconButton label={I18N.refresh} icon={<RefreshCw aria-hidden="true" size={16} />} onClick={() => void loadRequests()} disabled={loading} />
<RefreshCw aria-hidden="true" size={16} />
</Button>
{canWrite ? ( {canWrite ? (
<Button type="button" variant="primary" onClick={beginCreate}> <Button type="button" variant="primary" onClick={beginCreate}>
<Plus aria-hidden="true" size={16} /> {I18N.add} <Plus aria-hidden="true" size={16} /> {I18N.add}
@@ -883,33 +884,31 @@ function EditableSlots({
{ {
id: "start", id: "start",
header: I18N.start, header: I18N.start,
minWidth: 200, minWidth: 280,
resizable: true, resizable: true,
value: (slot) => slot.start_at, value: (slot) => slot.start_at,
render: (slot, index) => ( render: (slot, index) => (
<input <DateTimeField
className="scheduling-grid-input" className="scheduling-grid-date-time"
required required
type="datetime-local"
aria-label={I18N.start} aria-label={I18N.start}
value={slot.start_at} value={slot.start_at}
onChange={(event) => onChange(index, "start_at", event.target.value)} /> onChange={(value) => onChange(index, "start_at", value)} />
) )
}, },
{ {
id: "end", id: "end",
header: I18N.end, header: I18N.end,
minWidth: 200, minWidth: 280,
resizable: true, resizable: true,
value: (slot) => slot.end_at, value: (slot) => slot.end_at,
render: (slot, index) => ( render: (slot, index) => (
<input <DateTimeField
className="scheduling-grid-input" className="scheduling-grid-date-time"
required required
type="datetime-local"
aria-label={I18N.end} aria-label={I18N.end}
value={slot.end_at} value={slot.end_at}
onChange={(event) => onChange(index, "end_at", event.target.value)} /> onChange={(value) => onChange(index, "end_at", value)} />
) )
}, },
{ {
@@ -1087,17 +1086,14 @@ function CandidateSlotsGrid({
header: I18N.actions, header: I18N.actions,
width: 72, width: 72,
sticky: "end", sticky: "end",
render: (slot) => canDecide ? ( render: (slot) => <TableActionGroup actions={[{
<Button id: "decide",
className="scheduling-icon-button" label: i18nMessage("i18n:govoplan-scheduling.decide_on_value.196409cd", { value0: slot.label }),
type="button" icon: <Check aria-hidden="true" size={16} />,
title={i18nMessage("i18n:govoplan-scheduling.decide_on_value.196409cd", { value0: slot.label })} applicable: canDecide,
aria-label={i18nMessage("i18n:govoplan-scheduling.decide_on_value.196409cd", { value0: slot.label })} disabled: saving,
disabled={saving} onClick: () => onDecide(slot)
onClick={() => onDecide(slot)}> }]} />
<Check aria-hidden="true" size={16} />
</Button>
) : null
} }
]; ];

View File

@@ -89,15 +89,6 @@
gap: 6px; gap: 6px;
} }
.scheduling-icon-button.btn {
width: 34px;
min-width: 34px;
height: 34px;
min-height: 34px;
padding: 0;
justify-content: center;
}
.scheduling-alert, .scheduling-alert,
.scheduling-success { .scheduling-success {
margin: 10px 14px 0; margin: 10px 14px 0;
@@ -107,8 +98,8 @@
} }
.scheduling-alert { .scheduling-alert {
border-color: var(--danger); border-color: var(--danger-border-strong);
color: var(--danger); color: var(--danger-text-strong);
background: var(--danger-soft); background: var(--danger-soft);
} }
@@ -163,7 +154,7 @@
} }
.scheduling-list-item:hover { .scheduling-list-item:hover {
background: var(--hover-bg); background: var(--hover-tint-soft);
} }
.scheduling-list-item.is-selected { .scheduling-list-item.is-selected {
@@ -247,7 +238,7 @@
.scheduling-freebusy.busy, .scheduling-freebusy.busy,
.scheduling-freebusy.error { .scheduling-freebusy.error {
color: var(--danger); color: var(--danger-text-strong);
} }
.scheduling-columns { .scheduling-columns {
@@ -317,7 +308,7 @@
border: var(--border-line); border: var(--border-line);
border-radius: 6px; border-radius: 6px;
color: var(--text); color: var(--text);
background: var(--input-bg); background: var(--control-bg);
font: inherit; font: inherit;
} }