family: weekly agenda & many weeks display (#69454)
gitea-wip/combo/pipeline/head Build started... Details
gitea/combo/pipeline/head Something is wrong with the build of this commit Details

This commit is contained in:
Lauréline Guérin 2022-09-22 16:39:30 +02:00
parent 498f0db67e
commit c2afb605e0
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 214 additions and 166 deletions

View File

@ -1,104 +1,100 @@
.weeklyagenda-cell {
.weekly-agenda-cell {
margin-top: 1em;
display: grid;
grid-template-columns: repeat(3, auto);
grid-template-rows: repeat(2, auto);
justify-items: left;
--min-column-width: 250;
}
.weekly-agenda-cell--previous-week,
.weekly-agenda-cell--next-week {
height: 3em;
width: 3em;
}
.weekly-agenda-cell--next-week {
margin-left: 1rem;
}
.weekly-agenda-cell--edit-btn {
grid-column: 2;
grid-row: 2;
}
.weekly-agenda-cell--week-list {
overflow: hidden;
margin-bottom: 2rem;
width: 100%;
}
.weekly-agenda-cell--slider {
column-gap: 0px;
display: grid;
grid-auto-columns: 0px;
grid-auto-rows: 0px;
grid-auto-flow: column;
position: relative;
}
.weekly-agenda-cell--week-item:nth-child(even) {
background: #fafafa;
}
.weekly-agenda-cell--week-title {
border-bottom: 1px solid #888;
font-weight: bold;
margin-bottom: 1rem;
display: flex;
ul {
padding: 0;
margin: 0;
ul {
list-style: none;
}
justify-content: center;
align-items: center;
height: 3.2rem;
}
.weekly-agenda-cell--day-item {
margin: 0 1rem;
}
.weekly-agenda-cell--day-title {
border-bottom: 1px solid #888;
margin: 1rem 0;
font-weight: bold;
}
.weekly-agenda-cell--day-no-activity {
color: #888;
}
.weekly-agenda-cell--activity-item {
display: flex;
align-items: baseline;
margin: 0.3rem 0;
&+ .weekly-agenda-cell--day-no-activity {
display: none;
}
& > ul {
width: 100%;
& > li {
display: none;
&.shown {
display: block;
}
}
}
button.previous-week,
button.next-week {
height: 3em;
z-index: 10;
}
button.previous-week {
margin-right: 1em;
}
button.next-week {
margin-left: 1em;
margin-right: 0;
}
.week-title {
display: block;
height: 3em;
line-height: 3em;
}
.no-activity {
&.disabled {
color: #888;
}
li {
&.day-title {
margin: 1em 0 0.5em 0;
}
&.disabled {
color: #888;
}
& > span {
position: relative;
}
}
.weekly-agenda-cell--activity-status {
min-height: calc(0.66rem + 2px);
min-width: calc(0.66rem + 2px);
background: transparent;
border: 1px solid #aaa;
border-radius: 2px;
margin-right: 1rem;
&.booked {
background: green;
}
span > span {
padding-left: 1.8em;
&::before {
display: block;
content: '';
position: absolute;
margin: auto;
height: calc(0.66rem + 2px);
width: calc(0.66rem + 2px);
background: transparent;
top: 0.33rem;
left: 0;
border: 1px solid #aaa;
border-radius: 2px;
}
&::after {
display: block;
content: '';
position: absolute;
margin: auto;
height: calc(0.66rem);
width: calc(0.66rem);
background: transparent;
transition: background 0.1s linear;
top: calc(0.33rem + 1px);
left: 1px;
}
}
& [data-status=booked] span > span::after {
background: #3c3;
}
& [data-status=cancelled] span > span::after {
&.cancelled {
background: yellow;
}
& [data-status=absence] span > span::after {
&.absence {
background: red;
}
}
br.weekbreak {
display: none;
}
@media screen and (max-width: 500px) {
br.weekbreak { display: block; }
div.weeklyagenda-cell > ul {
margin: 0 -4em;
& .week-title {
margin: 0 4em;
line-height: 150%;
text-align: center;
}
}
.weekly-agenda-cell--activity-label {
flex-grow: 1;
}

View File

@ -1,46 +1,90 @@
$(function () {
init_agenda = function(cell_id) {
$('.weeklyagenda-cell-' + cell_id + ' .previous-week').on('click', function() {
var $cell = $(this).parents('.weeklyagenda-cell-' + cell_id);
var $prev = $('li.shown', $cell).prev();
if ($prev.length) { $('li.shown', $cell).removeClass('shown'); $prev.addClass('shown'); }
return false;
function initAgendaCell($cell, prefix) {
const $weekList = $(prefix + '--week-list', $cell);
const $slider = $(prefix + '--slider', $cell);
const $currentWeek = $(prefix + '--week-title.current', $cell);
const $nextWeekButton = $(prefix + '--next-week', $cell);
const $previousWeekButton = $(prefix + '--previous-week', $cell);
const $editAgendaBtn = $(prefix + '--edit-btn', $cell);
let $selectedWeek = $currentWeek;
function getPreviousWeek() { return $selectedWeek.prevAll(prefix + '--week-title:first'); }
function getNextWeek() { return $selectedWeek.nextAll(prefix + '--week-title:first'); }
function setCurrentWeek($week, enableTransition) {
if(!$week.length) {
return;
}
const currentOffset = $week[0].offsetLeft;
$slider.css({
'transform': `translate(-${currentOffset}px)`,
'transition': (enableTransition ? 'transform 0.5s' : '')
});
$selectedWeek = $week;
$nextWeekButton.prop('disabled', getNextWeek().length == 0);
$previousWeekButton.prop('disabled', getPreviousWeek().length == 0);
const selectedWeekIsInPast = $selectedWeek.nextAll(prefix + '--week-title.current').length;
$weekToEdit = selectedWeekIsInPast ? $currentWeek : $selectedWeek;
$editAgendaBtn.prop('href', $weekToEdit.attr('data-edit-url'));
}
function setPreviousWeek() { setCurrentWeek(getPreviousWeek(), true); }
function setNextWeek() { setCurrentWeek(getNextWeek(), true); }
$previousWeekButton.on('click', setPreviousWeek);
$nextWeekButton.on('click', setNextWeek);
let touchStartX = 0;
$cell.on('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
});
$('.weeklyagenda-cell-' + cell_id + ' .next-week').on('click', function() {
var $cell = $(this).parents('.weeklyagenda-cell-' + cell_id);
var $next = $('li.shown', $cell).next();
if ($next.length) { $('li.shown', $cell).removeClass('shown'); $next.addClass('shown'); }
return false;
});
$('.weeklyagenda-cell-' + cell_id + ' li.day-title').each(function(idx, elem) {
/* hide empty days */
var $next = $(elem).next();
if ($next.length == 0 || $next.is('.day-title')) {
$(elem).hide();
$cell.on('touchend', (e) => {
const touchEndX = e.changedTouches[0].screenX;
if (touchEndX - touchStartX < -30) {
setNextWeek();
} else if (touchEndX - touchStartX > 30) {
setPreviousWeek();
}
});
$('.weeklyagenda-cell-' + cell_id + ' li.week').each(function(idx, elem) {
/* hide no-activity message if not empty */
if ($('.activity', $(elem)).length > 0) {
$('.no-activity', $(elem)).hide();
function updateColumnsWidth() {
const minWidth = $slider.css('--min-column-width');
const availableWidth = $weekList[0].offsetWidth;
let columnsWidth = availableWidth < minWidth ? availableWidth : availableWidth / Math.floor(availableWidth / minWidth);
$slider.css('grid-template-columns', `repeat(53, ${columnsWidth}px)`);
if($selectedWeek) {
// Update slider offset to bring it at the new position of the selectedWeek
setCurrentWeek($selectedWeek, false);
}
/* hide booking button if all items are disabled */
if ($('.activity', $(elem)).not('.disabled').length == 0) {
$('.booking-btn', $(elem)).hide();
}
function hideEmptyDays() {
let nbRows = 8;
for(let i = 0; i < 7; ++i) {
const $days = $(prefix + `--day-item[data-weekday=${i}]`, $cell);
const $activities = $(prefix + '--activity-item', $days);
if(!$activities.length) {
$days.css('display', 'none');
nbRows -= 1;
}
$slider.css('grid-template-rows', `repeat(${nbRows}, auto`);
}
});
$('.weeklyagenda-cell-' + cell_id).each(function(idx, elem) {
/* init first week shown */
var $cell = $(this);
$('li', $cell).removeClass('shown');
if ($('li.day-title.current', $cell).length) {
$('li.day-title.current', $cell).parent().parent().addClass('shown');
} else {
$('li', $cell).first().addClass('shown');
}
});
}
hideEmptyDays();
new ResizeObserver(updateColumnsWidth).observe($weekList[0]);
addEventListener('resize', updateColumnsWidth);
}
$(document).on('combo:cell-loaded', function(ev, cell) {
init_agenda($('.weeklyagenda-cell', $(cell)).data('cell-id'));
if ($('.weekly-agenda-cell', $(cell)).length) {
initAgendaCell($(cell), '.weekly-agenda-cell');
}
});
});

View File

@ -8,43 +8,50 @@
</h2>
{% endif %}
<div class="weeklyagenda-cell weeklyagenda-cell-{{ cell.pk }}" data-cell-id="{{ cell.pk }}">
<div class="weekly-agenda-cell weekly-agenda-cell-{{ cell.pk }}" data-cell-id="{{ cell.pk }}">
{% with first_monday=json.data.0.date|date|adjust_to_week_monday last_item=json.data|last %}
{% with last_day=last_item.date|date|adjust_to_week_monday|add_days:6 %}
{% now 'Y-m-d' as now %}
{% spaceless %}
<button class="previous-week"></button>
<ul>
{% for day in first_monday|iterate_days_until:last_day %}
{% if day.weekday == 0 %}
{% with sunday=day|add_days:6 %}
<li class="week"><span class="week-title">{% blocktrans with day_date=day|date:"d/m" sunday_date=sunday|date:"d/m" %}Week<br class="weekbreak"> of {{ day_date }} to {{ sunday_date }}{% endblocktrans %}</span><ul>
{% endwith %}
{% endif %}
<li class="day-title {% if now == day|date:"Y-m-d" %}current{% endif %}" data-weekday="{{ day.weekday }}"><strong>{{ day|date:"l d/m" }}</strong></li>
{% with day_str=day|date:"Y-m-d" %}
{% for item in json.data %}
{% if item.date == day_str %}
<li class="activity {% if item.disabled %}disabled{% endif %}" data-status="{{ item.status }}">
<span><span>{{ item.label }}</span></span>
</li>
{% with last_day=last_item.date|date|adjust_to_week_monday|add_days:6 %}
{% now 'Y-m-W' as current_week %}
{% spaceless %}
<button class="weekly-agenda-cell--previous-week"></button>
<div class="weekly-agenda-cell--week-list">
<div class="weekly-agenda-cell--slider">
{% for day in first_monday|iterate_days_until:last_day %}
{% if day.weekday == 0 %}
{% with sunday=day|add_days:6 %}
<div class="weekly-agenda-cell--week-title {% if current_week == day|date:'Y-m-W' %}current{% endif %}"
{% if booking_form_url %}data-edit-url="{{ booking_form_url }}{% if '?' in booking_form_url %}&{% else %}?{% endif %}current={{ day|adjust_to_week_monday|date:'Y-m-d' }}"{% endif %}>
{% blocktrans with day_date=day|date:'d/m' sunday_date=sunday|date:'d/m' %}Week<br class="weekbreak"> of {{ day_date }} to {{ sunday_date }}{% endblocktrans %}
</div>
{% endwith %}
{% endif %}
<div class="weekly-agenda-cell--day-item" data-weekday="{{ day.weekday }}">
<div class="weekly-agenda-cell--day-title" >
{{ day|date:"l d/m" }}
</div>
{% with day_str=day|date:"Y-m-d" %}
{% for item in json.data %}
{% if item.date == day_str %}
<div class="weekly-agenda-cell--activity-item {% if item.disabled %}disabled{% endif %}">
<div class="weekly-agenda-cell--activity-status {{ item.status }}"/></div>
<div class="weekly-agenda-cell--activity-label"/>{{ item.label }}</div>
</div>
{% endif %}
{% endfor %}
{% endwith %}
<div class="weekly-agenda-cell--day-no-activity">{% trans "No activity this week" %}</div>
</div>
{% endfor %}
</div>
</div>
<button class="weekly-agenda-cell--next-week"></button>
{% if booking_form_url %}
<a class="pk-button weekly-agenda-cell--edit-btn">
{% trans "Update bookings" %}
</a>
{% endif %}
{% endfor %}
{% endspaceless %}
{% endwith %}
{% if day.weekday == 6 %}
</ul>
{% if booking_form_url %}<p class="booking-btn"><a class="pk-button" href="{{ booking_form_url }}{% if '?' in booking_form_url %}&{% else %}?{% endif %}current={{ day|adjust_to_week_monday|date:"Y-m-d" }}">{% trans "Update bookings" %}</a></p>{% endif %}
<p class="no-activity">{% trans "No activity this week" %}</p>
</li>
{% endif %}
{% endfor %}
</ul>
<button class="next-week"></button>
{% endspaceless %}
{% endwith %}
{% endwith %}
</div>
{% endif %}

View File

@ -295,20 +295,21 @@ def test_weeklyagenda_cell_booking_form_url(context):
with mock.patch('requests.Session.get') as requests_get:
requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
result = cell.render(context)
assert 'booking-btn' not in result
assert 'pk-button weekly-agenda-cell--edit-btn' not in result
assert 'data-edit-url' not in result
cell.booking_form_url = 'http://example.com/foobar/'
cell.save()
with mock.patch('requests.Session.get') as requests_get:
requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
result = cell.render(context)
assert 'booking-btn' in result
assert 'http://example.com/foobar/?current=2022-02-28' in result
assert 'pk-button weekly-agenda-cell--edit-btn' in result
assert 'data-edit-url="http://example.com/foobar/?current=2022-02-28"' in result
cell.booking_form_url = 'http://example.com/foobar/?user={{ user_nameid }}'
cell.save()
with mock.patch('requests.Session.get') as requests_get:
requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
result = cell.render(context)
assert 'booking-btn' in result
assert 'http://example.com/foobar/?user=xyz&current=2022-02-28' in result
assert 'pk-button weekly-agenda-cell--edit-btn' in result
assert 'data-edit-url="http://example.com/foobar/?user=xyz&current=2022-02-28"' in result