Skip to content

Commit 091920f

Browse files
committed
RotationConfigForm: Improve usablity
* Reorder elements * Rename elements * Change mode appearance
1 parent b553a55 commit 091920f

File tree

2 files changed

+123
-114
lines changed

2 files changed

+123
-114
lines changed

application/forms/RotationConfigForm.php

Lines changed: 99 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Icinga\Web\Session;
1919
use ipl\Html\Attributes;
2020
use ipl\Html\DeferredText;
21+
use ipl\Html\FormDecoration\DescriptionDecorator;
2122
use ipl\Html\FormElement\FieldsetElement;
2223
use ipl\Html\HtmlDocument;
2324
use ipl\Html\HtmlElement;
@@ -602,7 +603,9 @@ protected function assembleModeSelection(): string
602603
'24-7' => $this->translate('24/7')
603604
];
604605

605-
$modeList = new HtmlElement('ul');
606+
$modeList = new HtmlElement('ul', Attributes::create([
607+
'class' => ['rotation-mode', $this->disableModeSelection ? 'disabled' : '']
608+
]));
606609
foreach ($modes as $mode => $label) {
607610
$radio = $this->createElement('input', 'mode', [
608611
'type' => 'radio',
@@ -684,8 +687,14 @@ protected function assembleModeSelection(): string
684687

685688
$this->addHtml(new HtmlElement(
686689
'div',
687-
Attributes::create(['class' => ['rotation-mode', $this->disableModeSelection ? 'disabled' : '']]),
688-
new HtmlElement('h2', null, Text::create($this->translate('Mode'))),
690+
Attributes::create([
691+
'class' => ['control-group']
692+
]),
693+
new HtmlElement(
694+
'div',
695+
Attributes::create(['class' => 'control-label-group']),
696+
Text::create($this->translate('Rotation Mode'))
697+
),
689698
$modeList
690699
));
691700

@@ -704,12 +713,15 @@ protected function assembleTwentyFourSevenOptions(FieldsetElement $options): Dat
704713
$options->addElement('number', 'interval', [
705714
'required' => true,
706715
'label' => $this->translate('Handoff every'),
716+
'description' => $this->translate('Have multiple rotation members take turns after this interval.'),
707717
'step' => 1,
708718
'min' => 1,
709719
'value' => 1,
710720
'validators' => [new GreaterThanValidator()]
711721
]);
712722
$interval = $options->getElement('interval');
723+
$interval->getDecorators()
724+
->replaceDecorator('Description', DescriptionDecorator::class, ['class' => 'description']);
713725

714726
$frequency = $options->createElement('select', 'frequency', [
715727
'required' => true,
@@ -796,11 +808,15 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime
796808
$options->addElement('number', 'interval', [
797809
'required' => true,
798810
'label' => $this->translate('Handoff every'),
811+
'description' => $this->translate('Have multiple rotation members take turns after this interval.'),
799812
'step' => 1,
800813
'min' => 1,
801814
'value' => 1,
802815
'validators' => [new GreaterThanValidator()]
803816
]);
817+
$interval = $options->getElement('interval');
818+
$interval->getDecorators()
819+
->replaceDecorator('Description', DescriptionDecorator::class, ['class' => 'description']);
804820

805821
$selectedFromTime = $from->getValue();
806822
foreach ($timeOptions as $key => $value) {
@@ -830,7 +846,6 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime
830846
)
831847
);
832848

833-
$interval = $options->getElement('interval');
834849
$interval->prependWrapper(
835850
(new HtmlDocument())->addHtml(
836851
$interval,
@@ -912,8 +927,12 @@ protected function assembleMultiDayOptions(FieldsetElement $options): DateTime
912927
'step' => 1,
913928
'min' => 1,
914929
'value' => 1,
915-
'label' => $this->translate('Handoff every')
930+
'label' => $this->translate('Handoff every'),
931+
'description' => $this->translate('Have multiple rotation members take turns after this interval.')
916932
]);
933+
$interval = $options->getElement('interval');
934+
$interval->getDecorators()
935+
->replaceDecorator('Description', DescriptionDecorator::class, ['class' => 'description']);
917936

918937
$timeOptions = $this->getTimeOptions();
919938
$fromAt = $options->createElement('select', 'from_at', [
@@ -988,7 +1007,6 @@ protected function assembleMultiDayOptions(FieldsetElement $options): DateTime
9881007
)
9891008
);
9901009

991-
$interval = $options->getElement('interval');
9921010
$interval->prependWrapper(
9931011
(new HtmlDocument())->addHtml(
9941012
$interval,
@@ -1029,17 +1047,9 @@ protected function assemble()
10291047

10301048
$this->addElement('hidden', 'priority', ['ignore' => true]);
10311049

1032-
$mode = $this->assembleModeSelection();
1033-
1034-
$autoSubmittedBy = $this->getRequest()->getHeader('X-Icinga-Autosubmittedby')[0] ?? '';
1035-
if ($autoSubmittedBy === 'mode') {
1036-
$this->clearPopulatedValue('options');
1037-
$this->clearPopulatedValue('first_handoff');
1038-
}
1039-
10401050
$this->addElement('text', 'name', [
10411051
'required' => true,
1042-
'label' => $this->translate('Title'),
1052+
'label' => $this->translate('Rotation Name'),
10431053
'validators' => [
10441054
new CallbackValidator(function ($value, $validator) {
10451055
$rotations = Rotation::on($this->db)
@@ -1051,7 +1061,7 @@ protected function assemble()
10511061
}
10521062

10531063
if ($rotations->first() !== null) {
1054-
$validator->addMessage($this->translate('A rotation with this title already exists'));
1064+
$validator->addMessage($this->translate('A rotation with this name already exists'));
10551065

10561066
return false;
10571067
}
@@ -1061,6 +1071,73 @@ protected function assemble()
10611071
]
10621072
]);
10631073

1074+
$termValidator = function (array $terms) {
1075+
$contactTerms = [];
1076+
$groupTerms = [];
1077+
foreach ($terms as $term) {
1078+
/** @var TermInput\Term $term */
1079+
if (strpos($term->getSearchValue(), ':') === false) {
1080+
// TODO: Auto-correct this to a valid type:id pair, if possible
1081+
$term->setMessage($this->translate('Is not a contact nor a group of contacts'));
1082+
continue;
1083+
}
1084+
1085+
list($type, $id) = explode(':', $term->getSearchValue(), 2);
1086+
if ($type === 'contact') {
1087+
$contactTerms[$id] = $term;
1088+
} elseif ($type === 'group') {
1089+
$groupTerms[$id] = $term;
1090+
}
1091+
}
1092+
1093+
if (! empty($contactTerms)) {
1094+
$contacts = (Contact::on(Database::get()))
1095+
->filter(Filter::equal('id', array_keys($contactTerms)));
1096+
foreach ($contacts as $contact) {
1097+
$contactTerms[$contact->id]
1098+
->setLabel($contact->full_name)
1099+
->setClass('contact');
1100+
}
1101+
}
1102+
1103+
if (! empty($groupTerms)) {
1104+
$groups = (Contactgroup::on(Database::get()))
1105+
->filter(Filter::equal('id', array_keys($groupTerms)));
1106+
foreach ($groups as $group) {
1107+
$groupTerms[$group->id]
1108+
->setLabel($group->name)
1109+
->setClass('group');
1110+
}
1111+
}
1112+
};
1113+
1114+
$members = (new TermInput('members'))
1115+
->setIgnored()
1116+
->setRequired()
1117+
->setOrdered()
1118+
->setReadOnly()
1119+
->setVerticalTermDirection()
1120+
->setLabel($this->translate('Rotation Members'))
1121+
->setSuggestionUrl($this->suggestionUrl->with(['showCompact' => true, '_disableLayout' => 1]))
1122+
->on(TermInput::ON_ENRICH, $termValidator)
1123+
->on(TermInput::ON_ADD, $termValidator)
1124+
->on(TermInput::ON_SAVE, $termValidator)
1125+
->on(TermInput::ON_PASTE, $termValidator);
1126+
$this->addElement($members);
1127+
1128+
// TODO: TermInput is not compatible with the new decorators yet: https://github.com/Icinga/ipl-web/pull/317
1129+
$legacyDecorator = new IcingaFormDecorator();
1130+
$members->setDefaultElementDecorator($legacyDecorator);
1131+
$legacyDecorator->decorate($members);
1132+
1133+
$mode = $this->assembleModeSelection();
1134+
1135+
$autoSubmittedBy = $this->getRequest()->getHeader('X-Icinga-Autosubmittedby')[0] ?? '';
1136+
if ($autoSubmittedBy === 'mode') {
1137+
$this->clearPopulatedValue('options');
1138+
$this->clearPopulatedValue('first_handoff');
1139+
}
1140+
10641141
$this->addElement('fieldset', 'options');
10651142
/** @var FieldsetElement $options */
10661143
$options = $this->getElement('options');
@@ -1098,7 +1175,7 @@ protected function assemble()
10981175
'aria-describedby' => 'first-handoff-description',
10991176
'min' => $earliestHandoff !== null ? $earliestHandoff->format('Y-m-d') : null,
11001177
'max' => $latestHandoff->format('Y-m-d'),
1101-
'label' => $this->translate('First Handoff'),
1178+
'label' => $this->translate('Rotation Start'),
11021179
'value' => $firstHandoffDefault,
11031180
'validators' => [
11041181
new CallbackValidator(
@@ -1110,14 +1187,14 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
11101187
);
11111188
if ($earliestHandoff !== null && $chosenHandoff < $earliestHandoff) {
11121189
$validator->addMessage(sprintf(
1113-
$this->translate('The first handoff can only happen after %s'),
1190+
$this->translate('The rotation can only start after %s'),
11141191
$earliestHandoff->format('Y-m-d') // TODO: Use intl here
11151192
));
11161193

11171194
return false;
11181195
} elseif ($chosenHandoff > $latestHandoff) {
11191196
$validator->addMessage(sprintf(
1120-
$this->translate('The first handoff can only happen before %s'),
1197+
$this->translate('The rotation can only start before %s'),
11211198
$latestHandoff->format('Y-m-d') // TODO: Use intl here
11221199
));
11231200

@@ -1142,10 +1219,10 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
11421219

11431220
$actualFirstHandoff = $ruleGenerator->current()[0]->getStartDate();
11441221
if ($actualFirstHandoff < new DateTime()) {
1145-
return $this->translate('The first handoff will happen immediately');
1222+
return $this->translate('The rotation will start immediately');
11461223
} else {
11471224
return sprintf(
1148-
$this->translate('The first handoff will happen on %s'),
1225+
$this->translate('The rotation will start on %s'),
11491226
(new \IntlDateFormatter(
11501227
\Locale::getDefault(),
11511228
\IntlDateFormatter::MEDIUM,
@@ -1157,65 +1234,6 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
11571234
));
11581235
}
11591236

1160-
$termValidator = function (array $terms) {
1161-
$contactTerms = [];
1162-
$groupTerms = [];
1163-
foreach ($terms as $term) {
1164-
/** @var TermInput\Term $term */
1165-
if (strpos($term->getSearchValue(), ':') === false) {
1166-
// TODO: Auto-correct this to a valid type:id pair, if possible
1167-
$term->setMessage($this->translate('Is not a contact nor a group of contacts'));
1168-
continue;
1169-
}
1170-
1171-
list($type, $id) = explode(':', $term->getSearchValue(), 2);
1172-
if ($type === 'contact') {
1173-
$contactTerms[$id] = $term;
1174-
} elseif ($type === 'group') {
1175-
$groupTerms[$id] = $term;
1176-
}
1177-
}
1178-
1179-
if (! empty($contactTerms)) {
1180-
$contacts = (Contact::on(Database::get()))
1181-
->filter(Filter::equal('id', array_keys($contactTerms)));
1182-
foreach ($contacts as $contact) {
1183-
$contactTerms[$contact->id]
1184-
->setLabel($contact->full_name)
1185-
->setClass('contact');
1186-
}
1187-
}
1188-
1189-
if (! empty($groupTerms)) {
1190-
$groups = (Contactgroup::on(Database::get()))
1191-
->filter(Filter::equal('id', array_keys($groupTerms)));
1192-
foreach ($groups as $group) {
1193-
$groupTerms[$group->id]
1194-
->setLabel($group->name)
1195-
->setClass('group');
1196-
}
1197-
}
1198-
};
1199-
1200-
$members = (new TermInput('members'))
1201-
->setIgnored()
1202-
->setRequired()
1203-
->setOrdered()
1204-
->setReadOnly()
1205-
->setVerticalTermDirection()
1206-
->setLabel($this->translate('Members'))
1207-
->setSuggestionUrl($this->suggestionUrl->with(['showCompact' => true, '_disableLayout' => 1]))
1208-
->on(TermInput::ON_ENRICH, $termValidator)
1209-
->on(TermInput::ON_ADD, $termValidator)
1210-
->on(TermInput::ON_SAVE, $termValidator)
1211-
->on(TermInput::ON_PASTE, $termValidator);
1212-
$this->addElement($members);
1213-
1214-
// TODO: TermInput is not compatible with the new decorators yet: https://github.com/Icinga/ipl-web/pull/317
1215-
$legacyDecorator = new IcingaFormDecorator();
1216-
$members->setDefaultElementDecorator($legacyDecorator);
1217-
$legacyDecorator->decorate($members);
1218-
12191237
$this->addElement('submit', 'submit', [
12201238
'label' => $this->getSubmitLabel()
12211239
]);
@@ -1243,7 +1261,7 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
12431261
$this->getElement('submit')->prependWrapper((new HtmlDocument())->setHtmlContent(...$removeButtons));
12441262
}
12451263

1246-
$this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId()));
1264+
$this->addCsrfCounterMeasure(Session::getSession()->getId());
12471265
}
12481266

12491267
/**

public/css/form.less

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,50 +33,40 @@
3333

3434
.rotation-config {
3535
.rotation-mode {
36-
width: 50em;
37-
padding: .5em 1em;
38-
margin: 0 auto;
36+
display: flex;
37+
justify-content: space-between;
38+
flex: 1 1 auto;
39+
list-style: none;
40+
margin: 0 1em 0 0;
41+
padding: 0;
42+
43+
li {
44+
flex: 1 1 auto;
45+
width: 0;
3946

40-
h2 {
41-
margin: 0;
47+
&:not(:last-child) {
48+
margin-right: 1em;
49+
}
4250
}
4351

44-
ul {
52+
label {
4553
display: flex;
46-
justify-content: space-between;
54+
flex-direction: column;
55+
width: auto;
4756

48-
list-style: none;
49-
margin: 0;
50-
padding: 0;
51-
52-
li {
53-
flex: 1 1 auto;
54-
width: 0;
55-
56-
&:not(:last-child) {
57-
margin-right: 1em;
58-
}
57+
input {
58+
display: none;
5959
}
6060

61-
label {
62-
display: flex;
63-
flex-direction: column;
64-
width: auto;
65-
66-
input {
67-
display: none;
68-
}
69-
70-
.mode-img {
71-
width: 8em;
72-
margin-bottom: .5em;
73-
outline: 3px solid @icinga-blue;
74-
}
61+
.mode-img {
62+
width: 8em;
63+
margin-bottom: .5em;
64+
outline: 3px solid @icinga-blue;
7565
}
7666
}
7767
}
7868

79-
.control-group {
69+
.control-group:not(:has(.rotation-mode)) {
8070
align-items: baseline;
8171
}
8272

@@ -133,6 +123,7 @@
133123
}
134124

135125
.rotation-mode {
126+
padding: .75em;
136127
border: 1px solid @gray-light;
137128
.rounded-corners();
138129

0 commit comments

Comments
 (0)