Projet

Général

Profil

0001-family-weekly-agenda-many-weeks-display-69454.patch

Lauréline Guérin, 22 septembre 2022 17:15

Télécharger (15,4 ko)

Voir les différences:

Subject: [PATCH] family: weekly agenda & many weeks display (#69454)

 .../static/css/combo.weekly_agenda.scss       | 178 +++++++++---------
 .../family/static/js/combo.weekly_agenda.js   | 116 ++++++++----
 .../templates/combo/family/weekly_agenda.html |  75 ++++----
 tests/test_family.py                          |  11 +-
 4 files changed, 214 insertions(+), 166 deletions(-)
combo/apps/family/static/css/combo.weekly_agenda.scss
1
.weeklyagenda-cell {
1
.weekly-agenda-cell {
2 2
	margin-top: 1em;
3
	display: grid;
4
	grid-template-columns: repeat(3, auto);
5
	grid-template-rows: repeat(2, auto);
6
	justify-items: left;
7
	--min-column-width: 250;
8
}
9

  
10
.weekly-agenda-cell--previous-week,
11
.weekly-agenda-cell--next-week {
12
	height: 3em;
13
	width: 3em;
14
}
15

  
16
.weekly-agenda-cell--next-week {
17
	margin-left: 1rem;
18
}
19

  
20
.weekly-agenda-cell--edit-btn {
21
	grid-column: 2;
22
	grid-row: 2;
23
}
24

  
25
.weekly-agenda-cell--week-list {
26
	overflow: hidden;
27
	margin-bottom: 2rem;
28
	width: 100%;
29
}
30

  
31
.weekly-agenda-cell--slider {
32
	column-gap: 0px;
33
	display: grid;
34
	grid-auto-columns: 0px;
35
	grid-auto-rows: 0px;
36
	grid-auto-flow: column;
37
	position: relative;
38
}
39

  
40
.weekly-agenda-cell--week-item:nth-child(even) {
41
	background: #fafafa;
42
}
43

  
44
.weekly-agenda-cell--week-title {
45
	border-bottom: 1px solid #888;
46
	font-weight: bold;
47
	margin-bottom: 1rem;
3 48
	display: flex;
4
	ul {
5
		padding: 0;
6
		margin: 0;
7
		ul {
8
			list-style: none;
9
		}
10
	}
11
	& > ul {
12
		width: 100%;
13
		& > li {
14
			display: none;
15
			&.shown {
16
				display: block;
17
			}
18
		}
19
	}
20
	button.previous-week,
21
	button.next-week {
22
		height: 3em;
23
		z-index: 10;
24
	}
25
	button.previous-week {
26
		margin-right: 1em;
27
	}
28
	button.next-week {
29
		margin-left: 1em;
30
		margin-right: 0;
31
	}
32
	.week-title {
33
		display: block;
34
		height: 3em;
35
		line-height: 3em;
49
	justify-content: center;
50
	align-items: center;
51
	height: 3.2rem;
52
}
53

  
54
.weekly-agenda-cell--day-item {
55
	margin: 0 1rem;
56
}
57

  
58
.weekly-agenda-cell--day-title {
59
	border-bottom: 1px solid #888;
60
	margin: 1rem 0;
61
	font-weight: bold;
62
}
63

  
64
.weekly-agenda-cell--day-no-activity {
65
	color: #888;
66
}
67

  
68
.weekly-agenda-cell--activity-item {
69
	display: flex;
70
	align-items: baseline;
71
	margin: 0.3rem 0;
72
	&+ .weekly-agenda-cell--day-no-activity {
73
		display: none;
36 74
	}
37
	.no-activity {
75
	&.disabled {
38 76
		color: #888;
39 77
	}
40
	li {
41
		&.day-title {
42
			margin: 1em 0 0.5em 0;
43
		}
44
		&.disabled {
45
			color: #888;
46
		}
47
		& > span {
48
			position: relative;
49
		}
50
	}
51
	span > span {
52
		padding-left: 1.8em;
53
		&::before {
54
			display: block;
55
			content: '';
56
			position: absolute;
57
			margin: auto;
58
			height: calc(0.66rem + 2px);
59
			width: calc(0.66rem + 2px);
60
			background: transparent;
61
			top: 0.33rem;
62
			left: 0;
63
			border: 1px solid #aaa;
64
			border-radius: 2px;
65
		}
66
		&::after {
67
			display: block;
68
			content: '';
69
			position: absolute;
70
			margin: auto;
71
			height: calc(0.66rem);
72
			width: calc(0.66rem);
73
			background: transparent;
74
			transition: background 0.1s linear;
75
			top: calc(0.33rem + 1px);
76
			left: 1px;
77
		}
78
	}
79
	& [data-status=booked] span > span::after {
80
		background: #3c3;
78
}
79

  
80
.weekly-agenda-cell--activity-status {
81
	min-height: calc(0.66rem + 2px);
82
	min-width: calc(0.66rem + 2px);
83
	background: transparent;
84
	border: 1px solid #aaa;
85
	border-radius: 2px;
86
	margin-right: 1rem;
87
	&.booked {
88
		background: green;
81 89
	}
82
	& [data-status=cancelled] span > span::after {
90
	&.cancelled {
83 91
		background: yellow;
84 92
	}
85
	& [data-status=absence] span > span::after {
93
	&.absence {
86 94
		background: red;
87 95
	}
88 96
}
89 97

  
90
br.weekbreak {
91
	display: none;
92
}
93

  
94
@media screen and (max-width: 500px) {
95
	br.weekbreak { display: block; }
96
	div.weeklyagenda-cell > ul {
97
		margin: 0 -4em;
98
		& .week-title {
99
			margin: 0 4em;
100
			line-height: 150%;
101
			text-align: center;
102
		}
103
	}
98
.weekly-agenda-cell--activity-label {
99
	flex-grow: 1;
104 100
}
combo/apps/family/static/js/combo.weekly_agenda.js
1 1
$(function () {
2
  init_agenda = function(cell_id) {
3
    $('.weeklyagenda-cell-' + cell_id + ' .previous-week').on('click', function() {
4
      var $cell = $(this).parents('.weeklyagenda-cell-' + cell_id);
5
      var $prev = $('li.shown', $cell).prev();
6
      if ($prev.length) { $('li.shown', $cell).removeClass('shown'); $prev.addClass('shown'); }
7
      return false;
8
    });
9
    $('.weeklyagenda-cell-' + cell_id + ' .next-week').on('click', function() {
10
      var $cell = $(this).parents('.weeklyagenda-cell-' + cell_id);
11
      var $next = $('li.shown', $cell).next();
12
      if ($next.length) { $('li.shown', $cell).removeClass('shown'); $next.addClass('shown'); }
13
      return false;
14
    });
15
    $('.weeklyagenda-cell-' + cell_id + ' li.day-title').each(function(idx, elem) {
16
      /* hide empty days */
17
      var $next = $(elem).next();
18
      if ($next.length == 0 || $next.is('.day-title')) {
19
        $(elem).hide();
2
  function initAgendaCell($cell, prefix) {
3
    const $weekList = $(prefix + '--week-list', $cell);
4
    const $slider = $(prefix + '--slider', $cell);
5
    const $currentWeek = $(prefix + '--week-title.current', $cell);
6
    const $nextWeekButton = $(prefix + '--next-week', $cell);
7
    const $previousWeekButton = $(prefix + '--previous-week', $cell);
8
    const $editAgendaBtn = $(prefix + '--edit-btn', $cell);
9

  
10
    let $selectedWeek = $currentWeek;
11

  
12
    function getPreviousWeek() { return $selectedWeek.prevAll(prefix + '--week-title:first'); }
13
    function getNextWeek() { return $selectedWeek.nextAll(prefix + '--week-title:first'); }
14

  
15
    function setCurrentWeek($week, enableTransition) {
16
      if(!$week.length) {
17
          return;
20 18
      }
19
      const currentOffset = $week[0].offsetLeft;
20
      $slider.css({
21
        'transform': `translate(-${currentOffset}px)`,
22
        'transition': (enableTransition ? 'transform 0.5s' : '')
23
      });
24

  
25
      $selectedWeek = $week;
26
      $nextWeekButton.prop('disabled', getNextWeek().length == 0);
27
      $previousWeekButton.prop('disabled', getPreviousWeek().length == 0);
28

  
29
      const selectedWeekIsInPast = $selectedWeek.nextAll(prefix + '--week-title.current').length;
30
      $weekToEdit = selectedWeekIsInPast ? $currentWeek : $selectedWeek;
31
      $editAgendaBtn.prop('href', $weekToEdit.attr('data-edit-url'));
32
    }
33

  
34
    function setPreviousWeek() { setCurrentWeek(getPreviousWeek(), true); }
35
    function setNextWeek() { setCurrentWeek(getNextWeek(), true); }
36

  
37
    $previousWeekButton.on('click', setPreviousWeek);
38
    $nextWeekButton.on('click', setNextWeek);
39

  
40
    let touchStartX = 0;
41
    $cell.on('touchstart', (e) => {
42
      touchStartX = e.changedTouches[0].screenX;
21 43
    });
22
    $('.weeklyagenda-cell-' + cell_id + ' li.week').each(function(idx, elem) {
23
      /* hide no-activity message if not empty */
24
      if ($('.activity', $(elem)).length > 0) {
25
        $('.no-activity', $(elem)).hide();
26
      }
27
      /* hide booking button if all items are disabled */
28
      if ($('.activity', $(elem)).not('.disabled').length == 0) {
29
        $('.booking-btn', $(elem)).hide();
44

  
45
    $cell.on('touchend', (e) => {
46
      const touchEndX = e.changedTouches[0].screenX;
47
      if (touchEndX - touchStartX < -30) {
48
        setNextWeek();
49
      } else if (touchEndX - touchStartX > 30) {
50
        setPreviousWeek();
30 51
      }
31 52
    });
32
    $('.weeklyagenda-cell-' + cell_id).each(function(idx, elem) {
33
      /* init first week shown */
34
      var $cell = $(this);
35
      $('li', $cell).removeClass('shown');
36
      if ($('li.day-title.current', $cell).length) {
37
        $('li.day-title.current', $cell).parent().parent().addClass('shown');
38
      } else {
39
        $('li', $cell).first().addClass('shown');
53

  
54
    function updateColumnsWidth() {
55
      const minWidth = $slider.css('--min-column-width');
56
      const availableWidth = $weekList[0].offsetWidth;
57
      let columnsWidth = availableWidth < minWidth ? availableWidth : availableWidth / Math.floor(availableWidth / minWidth);
58
      $slider.css('grid-template-columns', `repeat(53, ${columnsWidth}px)`);
59

  
60
      if($selectedWeek) {
61
        // Update slider offset to bring it at the new position of the selectedWeek
62
        setCurrentWeek($selectedWeek, false);
40 63
      }
41
    });
64
    }
65

  
66
    function hideEmptyDays() {
67
      let nbRows = 8;
68
      for(let i = 0; i < 7; ++i) {
69
        const $days = $(prefix + `--day-item[data-weekday=${i}]`, $cell);
70
        const $activities = $(prefix + '--activity-item', $days);
71
        if(!$activities.length) {
72
          $days.css('display', 'none');
73
          nbRows -= 1;
74
        }
75

  
76
        $slider.css('grid-template-rows', `repeat(${nbRows}, auto`);
77
      }
78
    }
79

  
80
    hideEmptyDays();
81
    new ResizeObserver(updateColumnsWidth).observe($weekList[0]);
82
    addEventListener('resize', updateColumnsWidth);
42 83
  }
84

  
43 85
  $(document).on('combo:cell-loaded', function(ev, cell) {
44
    init_agenda($('.weeklyagenda-cell', $(cell)).data('cell-id'));
86
    if ($('.weekly-agenda-cell', $(cell)).length) {
87
      initAgendaCell($(cell), '.weekly-agenda-cell');
88
    }
45 89
  });
46 90
});
combo/apps/family/templates/combo/family/weekly_agenda.html
8 8
</h2>
9 9
{% endif %}
10 10

  
11
<div class="weeklyagenda-cell weeklyagenda-cell-{{ cell.pk }}" data-cell-id="{{ cell.pk }}">
11
<div class="weekly-agenda-cell weekly-agenda-cell-{{ cell.pk }}" data-cell-id="{{ cell.pk }}">
12 12
{% with first_monday=json.data.0.date|date|adjust_to_week_monday last_item=json.data|last %}
13
{% with last_day=last_item.date|date|adjust_to_week_monday|add_days:6 %}
14
{% now 'Y-m-d' as now %}
15

  
16
{% spaceless %}
17
<button class="previous-week">←</button>
18
<ul>
19
{% for day in first_monday|iterate_days_until:last_day %}
20
{% if day.weekday == 0 %}
21
{% with sunday=day|add_days:6 %}
22
<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>
23
{% endwith %}
24
{% endif %}
25
<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>
26
  {% with day_str=day|date:"Y-m-d" %}
27
    {% for item in json.data %}
28
      {% if item.date == day_str %}
29

  
30
  <li class="activity {% if item.disabled %}disabled{% endif %}" data-status="{{ item.status }}">
31
    <span><span>{{ item.label }}</span></span>
32
   </li>
33

  
13
  {% with last_day=last_item.date|date|adjust_to_week_monday|add_days:6 %}
14
    {% now 'Y-m-W' as current_week %}
15
    {% spaceless %}
16
      <button class="weekly-agenda-cell--previous-week">←</button>
17
      <div class="weekly-agenda-cell--week-list">
18
        <div class="weekly-agenda-cell--slider">
19
          {% for day in first_monday|iterate_days_until:last_day %}
20
            {% if day.weekday == 0 %}
21
              {% with sunday=day|add_days:6 %}
22
                <div class="weekly-agenda-cell--week-title {% if current_week == day|date:'Y-m-W' %}current{% endif %}"
23
                     {% 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 %}>
24
                  {% 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 %}
25
                </div>
26
              {% endwith %}
27
            {% endif %}
28
            <div class="weekly-agenda-cell--day-item" data-weekday="{{ day.weekday }}">
29
              <div class="weekly-agenda-cell--day-title" >
30
                {{ day|date:"l d/m" }}
31
              </div>
32
              {% with day_str=day|date:"Y-m-d" %}
33
                {% for item in json.data %}
34
                  {% if item.date == day_str %}
35
                    <div class="weekly-agenda-cell--activity-item {% if item.disabled %}disabled{% endif %}">
36
                      <div class="weekly-agenda-cell--activity-status {{ item.status }}"/></div>
37
                      <div class="weekly-agenda-cell--activity-label"/>{{ item.label }}</div>
38
                    </div>
39
                  {% endif %}
40
                {% endfor %}
41
              {% endwith %}
42
              <div class="weekly-agenda-cell--day-no-activity">{% trans "No activity this week" %}</div>
43
            </div>
44
          {% endfor %}
45
        </div>
46
      </div>
47
      <button class="weekly-agenda-cell--next-week">→</button>
48
      {% if booking_form_url %}
49
      <a class="pk-button weekly-agenda-cell--edit-btn">
50
        {% trans "Update bookings" %}
51
      </a>
34 52
      {% endif %}
35
    {% endfor %}
53
    {% endspaceless %}
36 54
  {% endwith %}
37
{% if day.weekday == 6 %}
38
</ul>
39
{% 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 %}
40
<p class="no-activity">{% trans "No activity this week" %}</p>
41
</li>
42
{% endif %}
43
{% endfor %}
44
</ul>
45
<button class="next-week">→</button>
46
{% endspaceless %}
47
{% endwith %}
48 55
{% endwith %}
49 56
</div>
50 57
{% endif %}
tests/test_family.py
295 295
    with mock.patch('requests.Session.get') as requests_get:
296 296
        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
297 297
        result = cell.render(context)
298
        assert 'booking-btn' not in result
298
        assert 'pk-button weekly-agenda-cell--edit-btn' not in result
299
        assert 'data-edit-url' not in result
299 300

  
300 301
    cell.booking_form_url = 'http://example.com/foobar/'
301 302
    cell.save()
302 303
    with mock.patch('requests.Session.get') as requests_get:
303 304
        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
304 305
        result = cell.render(context)
305
        assert 'booking-btn' in result
306
        assert 'http://example.com/foobar/?current=2022-02-28' in result
306
        assert 'pk-button weekly-agenda-cell--edit-btn' in result
307
        assert 'data-edit-url="http://example.com/foobar/?current=2022-02-28"' in result
307 308

  
308 309
    cell.booking_form_url = 'http://example.com/foobar/?user={{ user_nameid }}'
309 310
    cell.save()
310 311
    with mock.patch('requests.Session.get') as requests_get:
311 312
        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
312 313
        result = cell.render(context)
313
        assert 'booking-btn' in result
314
        assert 'http://example.com/foobar/?user=xyz&current=2022-02-28' in result
314
        assert 'pk-button weekly-agenda-cell--edit-btn' in result
315
        assert 'data-edit-url="http://example.com/foobar/?user=xyz&current=2022-02-28"' in result
315
-