Skip to content
Snippets Groups Projects
Commit 2835b3b9 authored by Moritz Strohm's avatar Moritz Strohm
Browse files

added GUI improvements to schedule, re #4421

Merge request studip/studip!3578
parent 60c88b92
No related branches found
No related tags found
No related merge requests found
Showing
with 436 additions and 80 deletions
......@@ -72,13 +72,13 @@ class Calendar_ScheduleController extends AuthenticatedController
_('Neuer Termin'),
$this->url_for('calendar/schedule/entry/add'),
Icon::create('add'),
['data-dialog' => '']
['data-dialog' => 'size=auto']
);
if ($show_hidden) {
$actions->addLink(
_('Ausgeblendete Veranstaltungen verstecken'),
$this->indexURL(['semester_id' => Request::get('semester_id')]),
Icon::create('visibility-invisible')
Icon::create('visibility-visible')
)->asButton();
} else {
$actions->addLink(
......@@ -87,7 +87,7 @@ class Calendar_ScheduleController extends AuthenticatedController
'show_hidden' => true,
'semester_id' => Request::get('semester_id'),
]),
Icon::create('visibility-visible')
Icon::create('visibility-invisible')
)->asButton();
}
......@@ -99,12 +99,40 @@ class Calendar_ScheduleController extends AuthenticatedController
);
$actions->addLink(
_('Einstellungen'),
$this->url_for('settings/calendar'),
$this->url_for('calendar/schedule/settings'),
Icon::create('settings'),
['data-dialog' => 'size=auto;reload-on-close']
);
$sidebar->addWidget($actions);
$schedule_settings = UserConfig::get()->getValue('SCHEDULE_SETTINGS');
$size = $schedule_settings['size'] ?? 'medium';
if (Request::submitted('size')) {
$size = Request::option('size');
if (in_array($size, ['small', 'medium', 'large'])) {
//Set the new size in the schedule settings:
$schedule_settings['size'] = $size;
UserConfig::get()->store('SCHEDULE_SETTINGS', $schedule_settings);
} else {
$size = 'medium';
}
}
$views = new ViewsWidget();
$views->setTitle(_('Größe'));
$views->addLink(
_('Klein'),
$this->url_for('calendar/schedule/index', ['size' => 'small'])
)->setActive($size === 'small');
$views->addLink(
_('Mittel'),
$this->url_for('calendar/schedule/index', ['size' => 'medium'])
)->setActive($size === 'medium');
$views->addLink(
_('Groß'),
$this->url_for('calendar/schedule/index', ['size' => 'large'])
)->setActive($size === 'large');
$sidebar->addWidget($views);
$fullcalendar = \Studip\Calendar\Helper::getScheduleFullcalendar(
$semester->id ?? '',
Request::bool('show_hidden', false)
......@@ -203,9 +231,23 @@ class Calendar_ScheduleController extends AuthenticatedController
);
$event_classes = ['schedule'];
$event_title = $cycle_date->course->getFullName();
$event_title = $cycle_date->course->getFullName('number-name');
if ($course_membership) {
$event_classes[] = sprintf('course-color-%u', $course_membership->gruppe);
$lecturer_names = array_map(
fn($lecturer) => $lecturer->user->nachname,
CourseMember::findByCourseAndStatus($course_membership->seminar_id, 'dozent')
);
sort($lecturer_names);
$event_title = studip_interpolate(
'%{course_name} (%{lecturer_names})',
[
'course_name' => $cycle_date->course->getFullName('number-name'),
'lecturer_names' => implode(', ', $lecturer_names)
]
);
} elseif ($schedule_course) {
$event_classes[] = 'marked-course';
$event_title = studip_interpolate(
......@@ -274,6 +316,7 @@ class Calendar_ScheduleController extends AuthenticatedController
$this->entry->user_id = $GLOBALS['user']->id;
if (!Request::submitted('save')) {
//Provide good default values:
$this->entry->colour_id = 1;
if (Request::submitted('start')) {
//String format
$this->entry->dow = Request::int('dow',date('N'));
......@@ -284,10 +327,16 @@ class Calendar_ScheduleController extends AuthenticatedController
$begin = Request::get('begin');
$end = Request::get('end');
if ($begin && $end) {
$this->entry->dow = date('N', $begin);
$this->entry->dow = intval(date('N', $begin));
$this->entry->setFormattedStart(date('H:i', $begin));
$this->entry->setFormattedEnd(date('H:i', $end));
}
} else {
$begin = time() + 3600;
$end = $begin + 3600;
$this->entry->dow = intval(date('N', $begin));
$this->entry->setFormattedStart(date('H:00', $begin));
$this->entry->setFormattedEnd(date('H:00', $end));
}
}
PageLayout::setTitle(_('Neuer Termin'));
......@@ -338,6 +387,7 @@ class Calendar_ScheduleController extends AuthenticatedController
$this->entry->dow = Request::int('dow', date('N'));
$this->entry->setFormattedStart(Request::get('start'));
$this->entry->setFormattedEnd(Request::get('end'));
$this->entry->colour_id = Request::get('colour_id') ?? '';
$this->entry->label = Request::get('label', '');
$this->entry->content = Request::get('content', '');
......@@ -589,4 +639,76 @@ class Calendar_ScheduleController extends AuthenticatedController
}
$this->redirect('calendar/schedule/index');
}
/**
* Shows the settings dialog for the schedule.
*/
public function settings_action()
{
$user_config = UserConfig::get($GLOBALS['user']->id);
$this->schedule_settings = $user_config->getValue('SCHEDULE_SETTINGS');
//Provide good defaults:
$default_config = [
'start_time' => '08:00',
'end_time' => '20:00',
'weekdays' => 5,
'visible_days' => [1, 2, 3, 4, 5]
];
if (
empty($this->schedule_settings['start_time'])
&& empty($this->schedule_settings['end_time'])
&& empty($this->schedule_settings['weekdays'])
&& empty($this->schedule_settings['visible_days'])
) {
//Use the defaults:
$this->schedule_settings = $default_config;
}
}
/**
* Saves the schedule settings from the settings dialog.
*/
public function save_settings_action()
{
CSRFProtection::verifyUnsafeRequest();
$start_time = Request::get('start_time', '08:00');
$end_time = Request::get('end_time', '20:00');
$weekdays = Request::int('weekdays', 5);
$visible_days = Request::intArray('visible_days');
if ($start_time >= $end_time) {
PageLayout::postError(_('Die Startuhrzeit muss vor der Enduhrzeit liegen.'));
$this->redirect('calendar/schedule/settings');
return;
}
if (!in_array($weekdays, [5, 7])) {
PageLayout::postError(_('Der Stundenplan kann nur 5 oder 7 Tage anzeigen.'));
$this->redirect('calendar/schedule/settings');
return;
}
if (empty($visible_days)) {
PageLayout::postError(_('Es wurde kein Wochentag ausgewählt.'));
$this->redirect('calendar/schedule/settings');
return;
}
$schedule_settings = [
'start_time' => $start_time,
'end_time' => $end_time,
'weekdays' => $weekdays,
'visible_days' => $visible_days
];
UserConfig::get($GLOBALS['user']->id)->store('SCHEDULE_SETTINGS', $schedule_settings);
PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.'));
if (Request::isDialog()) {
$this->response->add_header('X-Dialog-Close', '1');
} else {
$this->redirect('calendar/schedule/index');
}
$this->render_nothing();
}
}
<?php
/**
* @var string $selected_colour_id
*/
?>
<? foreach ($GLOBALS['PERS_TERMIN_KAT'] as $colour_id => $data) : ?>
<td class="colour">
<input type="radio" name="colour_id" value="<?= htmlReady($colour_id) ?>"
aria-label="<?= sprintf(_('Farbe %s zuordnen'), htmlReady($colour_id)) ?>"
<?= $selected_colour_id === $colour_id ? 'checked' : '' ?>
id="colour-<?= htmlReady($colour_id) ?>">
<label for="colour-<?= htmlReady($colour_id) ?>"
style="background-color: <?= htmlReady($data['bgcolor']) ?>;">
<span class="colour-id"></span>
<span class="checked-icon"><?= Icon::create('accept', Icon::ROLE_INFO) ?></span>
</label>
</td>
<? endforeach ?>
......@@ -7,7 +7,6 @@
*/
?>
<? if ($course) : ?>
<h2><?= htmlReady($course->getFullName()) ?></h2>
<form class="default" method="post" data-dialog="reload-on-close"
action="<?= $controller->link_for('calendar/schedule/course_info/' . $course->id) ?>">
<?= CSRFProtection::tokenTag() ?>
......@@ -30,8 +29,10 @@
<fieldset>
<legend><?= _('Informationen') ?></legend>
<section>
<? if ($course->veranstaltungsnummer) : ?>
<h3><?= _('Veranstaltungsnummer') ?></h3>
<p><?= htmlReady($course->veranstaltungsnummer) ?></p>
<? endif ?>
<h3><?= _('Lehrende') ?></h3>
<ul class="default">
<?
......@@ -48,6 +49,22 @@
<h3><?= _('Veranstaltungszeiten') ?></h3>
<?= $course->getAllDatesInSemester()->toHtml() ?>
</section>
<section>
<?
$enrolment_info = $course->getEnrolmentInformation($GLOBALS['user']->id);
?>
<? if ($enrolment_info->isEnrolmentAllowed()) : ?>
<a href="<?= URLHelper::getLink('dispatch.php/course/overview', ['cid' => $course->id]) ?>">
<?= _('Direkt zur Veranstaltung') ?>
<?= Icon::create('link-intern')->asImg(Icon::SIZE_INLINE, ['class' => 'text-bottom']) ?>
</a>
<? else : ?>
<a href="<?= URLHelper::getLink('dispatch.php/course/details', ['sem_id' => $course->id]) ?>">
<?= _('Direkt zur Veranstaltung') ?>
<?= Icon::create('link-intern')->asImg(Icon::SIZE_INLINE, ['class' => 'text-bottom']) ?>
</a>
<? endif ?>
</section>
</fieldset>
<div data-dialog-button>
<?= \Studip\Button::create(
......@@ -68,20 +85,6 @@
['formaction' => $controller->url_for('calendar/schedule/hide_course/' . $course->id)]
) ?>
<? endif ?>
<?php
$enrolment_info = $course->getEnrolmentInformation($GLOBALS['user']->id);
?>
<? if ($enrolment_info->isEnrolmentAllowed()) : ?>
<?= \Studip\LinkButton::create(
_('Direkt zur Veranstaltung'),
URLHelper::getURL('dispatch.php/course/overview', ['cid' => $course->id])
) ?>
<? else : ?>
<?= \Studip\LinkButton::create(
_('Direkt zur Veranstaltung'),
URLHelper::getURL('dispatch.php/course/details', ['sem_id' => $course->id])
) ?>
<? endif ?>
</div>
</form>
<? endif ?>
......@@ -4,15 +4,27 @@
* @var ScheduleEntry $entry The schedule entry to be created/modified.
*/
?>
<form class="default" method="post" action="<?= $controller->link_for('calendar/schedule/entry/' . ($entry->isNew() ? 'add' : $entry->id)) ?>"
<form class="default schedule-entry" method="post"
action="<?= $controller->link_for('calendar/schedule/entry/' . ($entry->isNew() ? 'add' : $entry->id)) ?>"
data-dialog="reload-on-close">
<?= CSRFProtection::tokenTag() ?>
<fieldset>
<legend><?= _('Farbe') ?></legend>
<table class="default colour-selector">
<tr>
<?= $this->render_partial(
'calendar/schedule/_colour_selector',
['selected_colour_id' => $entry->colour_id]
) ?>
</tr>
</table>
</fieldset>
<fieldset>
<legend><?= _('Zeit') ?></legend>
<section class="flex-row">
<section class="hgroup nowrap">
<label>
<?= _('Wochentag') ?>
<select name="dow">
<select name="dow" class="size-s">
<option value="1" <?= $entry->dow === 1 ? 'selected' : '' ?>>
<?= _('Montag') ?>
</option>
......@@ -37,13 +49,13 @@
</select>
</label>
<label>
<?= _('Startuhrzeit') ?>
<input type="text" class="has-time-picker" name="start"
<?= _('Anfang') ?>
<input type="text" class="has-time-picker size-s" name="start"
value="<?= htmlReady($entry->getFormattedStart()) ?>">
</label>
<label>
<?= _('Enduhrzeit') ?>
<input type="text" class="has-time-picker" name="end"
<?= _('Ende') ?>
<input type="text" class="has-time-picker size-s" name="end"
value="<?= htmlReady($entry->getFormattedEnd()) ?>">
</label>
</section>
......@@ -60,7 +72,7 @@
</label>
</fieldset>
<div data-dialog-button>
<?= \Studip\Button::create(
<?= \Studip\Button::createAccept(
_('Speichern'),
'save',
['formaction' => $controller->url_for('calendar/schedule/save_entry/' . ($entry->isNew() ? 'add' : $entry->id))]
......
<?php
/**
* @var StudipController $controller
* @var array $schedule_settings
*/
?>
<form class="default" method="post" action="<?= $controller->link_for('calendar/schedule/save_settings') ?>"
<?= Request::isDialog() ? 'data-dialog="reload-on-close"' : '' ?>>
<?= CSRFProtection::tokenTag() ?>
<fieldset>
<legend><?= _('Zeiten') ?></legend>
<label>
<?= _('Anfang') ?>
<select name="start_time" aria-label="<?= _('Anfang des Stundenplans') ?>" class="size-s">
<? for ($i = 0; $i < 24; $i += 1): ?>
<? $value = sprintf('%02u:00', $i); ?>
<option value="<?= htmlReady($value) ?>"
<?= $schedule_settings['start_time'] === $value ? 'selected' : '' ?>>
<?= studip_interpolate('%{time} Uhr', ['time' => $value]) ?>
</option>
<? endfor ?>
</select>
</label>
<label>
<?= _('Ende') ?>
<select name="end_time" aria-label="<?= _('Ende des Stundenplans') ?>" class="size-s">
<? for ($i = 0; $i < 24; $i += 1): ?>
<? $value = sprintf('%02u:00', $i); ?>
<option value="<?= $value ?>"
<?= $schedule_settings['end_time'] === $value ? 'selected' : '' ?>>
<?= studip_interpolate('%{time} Uhr', ['time' => $value]) ?>
</option>
<? endfor ?>
</select>
</label>
<label>
<input type="radio" name="weekdays" value="7"
<?= $schedule_settings['weekdays'] === 7 ? 'checked' : '' ?>>
<?= _('Alle Wochentage im Stundenplan anzeigen.') ?>
</label>
<label>
<input type="radio" name="weekdays" value="5"
<?= $schedule_settings['weekdays'] === 5 ? 'checked' : '' ?>>
<?= _('Nur Montag bis Freitag im Stundenplan anzeigen.') ?>
</label>
</fieldset>
<fieldset>
<legend><?= _('Wochentage') ?></legend>
<section class="hgroup">
<? for ($i = 1; $i < 8; $i++) : ?>
<label>
<input type="checkbox" name="visible_days[]" value="<?= $i ?>"
<?= in_array($i, $schedule_settings['visible_days']) ? 'checked' : '' ?>>
<?= getWeekday($i, false) ?>
</label>
<? endfor ?>
</section>
</fieldset>
<div data-dialog-button>
<?= \Studip\Button::createAccept(_('Speichern')) ?>
<?= \Studip\Button::createCancel(_('Abbrechen')) ?>
</div>
</form>
......@@ -16,7 +16,7 @@ class AlterScheduleTable extends Migration
$db->exec(
"ALTER TABLE `schedule_entries`
DROP COLUMN color,
RENAME COLUMN color TO colour_id,
CHANGE COLUMN start start_time SMALLINT(6) NOT NULL,
CHANGE COLUMN end end_time SMALLINT(6) NOT NULL,
CHANGE COLUMN day dow TINYINT(1) NOT NULL,
......@@ -51,7 +51,7 @@ class AlterScheduleTable extends Migration
$db->exec(
"ALTER TABLE `schedule_entries`
ADD COLUMN color TINYINT(4) NULL DEFAULT NULL,
RENAME COLUMN colour_id TO color,
CHANGE COLUMN start_time start SMALLINT(6) NOT NULL,
CHANGE COLUMN end_time end SMALLINT(6) NOT NULL,
CHANGE COLUMN dow day TINYINT(1) NOT NULL,
......
<?php
class NewScheduleImprovements extends Migration
{
public function description()
{
return 'A bugfix migration to add colours to personal schedule entries again and to migrate schedule configurations.';
}
protected function up()
{
$db = DBManager::get();
$db->exec(
"ALTER TABLE `schedule_entries`
ADD COLUMN IF NOT EXISTS `colour_id` TINYINT(3) NOT NULL DEFAULT 0"
);
//Migrate the content of schedule configuration entries:
$fetch_stmt = $db->prepare(
"SELECT `range_id`, `value`
FROM `config_values`
WHERE `field` = 'SCHEDULE_SETTINGS'"
);
$update_stmt = $db->prepare(
"UPDATE `config_values`
SET `value` = :new_value, `chdate` = UNIX_TIMESTAMP()
WHERE `field` = 'SCHEDULE_SETTINGS'
AND `range_id` = :range_id"
);
$delete_stmt = $db->prepare(
"DELETE FROM `config_values`
WHERE `field` = 'SCHEDULE_SETTINGS'
AND `range_id` = :range_id"
);
$fetch_stmt->execute();
while ($row = $fetch_stmt->fetch(PDO::FETCH_ASSOC)) {
$old_config = json_decode($row['value'], true);
if (is_array($old_config)) {
//Convert the configuration:
$new_config = [
'start_time' => sprintf('%02u:00', $old_config['glb_start_time']),
'end_time' => sprintf('%02u:00', $old_config['glb_end_time']),
'semester_id' => $old_config['semester_id']
];
if (count($old_config['glb_days']) === 7) {
$new_config['weekdays'] = 7;
} else {
$new_config['weekdays'] = 5;
}
//Convert the visible days array:
$visible_days = [];
if (is_array($old_config['glb_days'])) {
foreach ($old_config['glb_days'] as $day) {
if ($day == 0) {
$visible_days[] = 7;
} else {
$visible_days[] = (int) $day;
}
}
}
$new_config['visible_days'] = $visible_days;
$update_stmt->execute([
'range_id' => $row['range_id'],
'new_value' => json_encode($new_config)
]);
} else {
//Delete the configuration:
$delete_stmt->execute(['range_id' => $old_config['range_id']]);
}
}
}
}
......@@ -128,7 +128,31 @@ class Helper
if (!$semester_id) {
$semester_id = \Semester::findCurrent()?->id ?? '';
}
$calendar_settings = \User::findCurrent()->getConfiguration()->CALENDAR_SETTINGS ?? [];
$schedule_settings = \UserConfig::get($GLOBALS['user']->id)->getValue('SCHEDULE_SETTINGS') ?? [];
$slot_duration = '00:30:00';
if (!empty($schedule_settings['size']) && in_array($schedule_settings['size'], ['small', 'large'])) {
if ($schedule_settings['size'] === 'small') {
$slot_duration = '01:00:00';
} elseif ($schedule_settings['size'] === 'large') {
$slot_duration = '00:15:00';
}
}
//Determine the value of the hiddenDays config.
$hidden_days = [1, 2, 3, 4, 5, 6, 7];
$hidden_days = array_diff(
$hidden_days,
$schedule_settings['visible_days'] ?? [1, 2, 3, 4, 5, 6, 7]
);
$fullcalendar_hidden_days = [];
foreach ($hidden_days as $day) {
if ($day === 7) {
$fullcalendar_hidden_days[] = 0;
} else {
$fullcalendar_hidden_days[] = $day;
}
}
return new \Studip\Fullcalendar(
_('Stundenplan'),
......@@ -136,8 +160,8 @@ class Helper
'editable' => true,
'selectable' => true,
'dialog_size' => 'auto',
'minTime' => sprintf('%02u:00', $calendar_settings['start'] ?? 8),
'maxTime' => sprintf('%02u:00', $calendar_settings['end'] ?? 20),
'minTime' => $schedule_settings['start_time'] ?? '08:00',
'maxTime' => $schedule_settings['end_time'] ?? '20:00',
'allDaySlot' => false,
'header' => [
'left' => '',
......@@ -145,13 +169,19 @@ class Helper
],
'views' => [
'timeGridWeek' => [
'columnHeaderFormat' => ['weekday' => 'long'],
'weekends' => $calendar_settings['type_week'] === 'LONG',
'slotDuration' => self::getCalendarSlotDuration('week'),
'columnHeaderFormat' => ['weekday' => 'short'],
'weekends' => !empty($schedule_settings['weekdays']) && $schedule_settings['weekdays'] === 7,
'slotDuration' => $slot_duration
]
],
'defaultView' => 'timeGridWeek',
'defaultDate' => date('Y-m-d'),
'slotLabelFormat' => [
'hour' => 'numeric',
'minute' => '2-digit',
'omitZeroMinute' => false
],
'hiddenDays' => $fullcalendar_hidden_days,
'timeGridEventMinHeight' => 20,
'eventSources' => [
[
......
......@@ -17,6 +17,7 @@
* @property string $start_time database column
* @property string $end_time database column
* @property string $dow database column
* @property string $colour_id database column
* @property string $label database column
* @property string $content database column
* @property string $user_id database column
......@@ -216,7 +217,7 @@ class ScheduleEntry extends SimpleORMap implements Event
*/
public function getDescription(): string
{
return $this->content;
return $this->getValue('content');
}
/**
......@@ -298,13 +299,24 @@ class ScheduleEntry extends SimpleORMap implements Event
*/
public function toEventData(string $user_id): \Studip\Calendar\EventData
{
$title = $this->label;
$description = $this->getDescription();
if ($description) {
if ($this->label) {
$title = $this->label . ': ' . $description;
} else {
$title = $description;
}
}
$event_classes = ['schedule-entry'];
return new \Studip\Calendar\EventData(
$this->getBegin(),
$this->getEnd(),
$this->label,
['schedule-entry'],
'#000000',
'#ffffff',
$title,
$event_classes,
$GLOBALS['PERS_TERMIN_KAT'][$this->colour_id]['fgcolor'] ?? '#000000',
$GLOBALS['PERS_TERMIN_KAT'][$this->colour_id]['bgcolor'] ?? '#ffffff',
$this->isWritable($user_id),
self::class,
$this->id,
......@@ -317,7 +329,7 @@ class ScheduleEntry extends SimpleORMap implements Event
],
[],
'',
'#000000',
$GLOBALS['PERS_TERMIN_KAT'][$this->colour_id]['border_color'] ?? '#000000',
$this->isAllDayEvent()
);
}
......
......@@ -3,7 +3,7 @@
background-color: #fff;
color: #000;
border-width: 2px;
border-width: 1px;
&:hover {
color: #000;
......
......@@ -18,7 +18,8 @@ form.default table.mycourses-group-selector {
}
}
form.default td.mycourses-group-selector {
form.default td.mycourses-group-selector,
form.default table.colour-selector td.colour {
position: relative;
background-clip: padding-box;
......@@ -28,7 +29,8 @@ form.default td.mycourses-group-selector {
@extend .sr-only;
&:checked + label {
.group-number {
.group-number,
.colour-id {
display: none;
}
.checked-icon {
......
form.default.schedule-entry {
section.nowrap {
flex-wrap: nowrap;
}
table.colour-selector td.colour {
label {
width: 1.5em;
height: 1.5em;
.studip-icon {
height: 1.5em;
}
}
}
}
......@@ -82,6 +82,7 @@
@import "scss/resources";
@import "scss/sidebar";
@import "scss/wizard";
@import "scss/schedule";
@import "scss/select";
@import "scss/selects";
@import "scss/search";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment